From 5d2b92521d1aca49f7b96ee3d89a785dab112d54 Mon Sep 17 00:00:00 2001 From: Hamdah Shafqat Abbasi <74803092+hamshkhawar@users.noreply.github.com> Date: Fri, 19 Jul 2024 10:12:56 -0400 Subject: [PATCH] Image dimension stacking (#495) * new plugin * refactored code and added pytests * adding plugin json and correcting documentation * fixed documentation * fixed documentation * fixed documentation * fixed documentation * fixed documentation * updating docker base container * added multiple pattern support * added docstring * fix plugin name and test fixture * test cli * fixing multipattern test * fix slow test * fix slow test --- .../.bumpversion.cfg | 29 +++ .../.dockerignore | 4 + .../image-dimension-stacking-tool/.gitignore | 2 + .../image-dimension-stacking-tool/Dockerfile | 21 ++ .../image-dimension-stacking-tool/README.md | 50 +++++ formats/image-dimension-stacking-tool/VERSION | 1 + .../build-docker.sh | 4 + .../image-dimension-stacking-tool/plugin.json | 62 ++++++ .../pyproject.toml | 42 ++++ .../run-plugin.sh | 22 ++ .../image_dimension_stacking/__init__.py | 2 + .../image_dimension_stacking/__main__.py | 111 ++++++++++ .../dimension_stacking.py | 196 ++++++++++++++++++ .../tests/__init__.py | 1 + .../tests/conftest.py | 127 ++++++++++++ .../tests/test_cli.py | 71 +++++++ .../tests/test_dimension_stacking.py | 73 +++++++ 17 files changed, 818 insertions(+) create mode 100644 formats/image-dimension-stacking-tool/.bumpversion.cfg create mode 100644 formats/image-dimension-stacking-tool/.dockerignore create mode 100644 formats/image-dimension-stacking-tool/.gitignore create mode 100644 formats/image-dimension-stacking-tool/Dockerfile create mode 100644 formats/image-dimension-stacking-tool/README.md create mode 100644 formats/image-dimension-stacking-tool/VERSION create mode 100644 formats/image-dimension-stacking-tool/build-docker.sh create mode 100644 formats/image-dimension-stacking-tool/plugin.json create mode 100644 formats/image-dimension-stacking-tool/pyproject.toml create mode 100644 formats/image-dimension-stacking-tool/run-plugin.sh create mode 100644 formats/image-dimension-stacking-tool/src/polus/images/formats/image_dimension_stacking/__init__.py create mode 100644 formats/image-dimension-stacking-tool/src/polus/images/formats/image_dimension_stacking/__main__.py create mode 100644 formats/image-dimension-stacking-tool/src/polus/images/formats/image_dimension_stacking/dimension_stacking.py create mode 100644 formats/image-dimension-stacking-tool/tests/__init__.py create mode 100644 formats/image-dimension-stacking-tool/tests/conftest.py create mode 100644 formats/image-dimension-stacking-tool/tests/test_cli.py create mode 100644 formats/image-dimension-stacking-tool/tests/test_dimension_stacking.py diff --git a/formats/image-dimension-stacking-tool/.bumpversion.cfg b/formats/image-dimension-stacking-tool/.bumpversion.cfg new file mode 100644 index 000000000..926e4c0f0 --- /dev/null +++ b/formats/image-dimension-stacking-tool/.bumpversion.cfg @@ -0,0 +1,29 @@ +[bumpversion] +current_version = 0.1.1-dev +commit = False +tag = False +parse = (?P\d+)\.(?P\d+)\.(?P\d+)(\-(?P[a-z]+)(?P\d+))? +serialize = + {major}.{minor}.{patch}-{release}{dev} + {major}.{minor}.{patch} + +[bumpversion:part:release] +optional_value = _ +first_value = dev +values = + dev + _ + +[bumpversion:part:dev] + +[bumpversion:file:pyproject.toml] +search = version = "{current_version}" +replace = version = "{new_version}" + +[bumpversion:file:VERSION] + +[bumpversion:file:README.md] + +[bumpversion:file:plugin.json] + +[bumpversion:file:src/polus/images/formats/image_dimension_stacking/__init__.py] diff --git a/formats/image-dimension-stacking-tool/.dockerignore b/formats/image-dimension-stacking-tool/.dockerignore new file mode 100644 index 000000000..7c603f814 --- /dev/null +++ b/formats/image-dimension-stacking-tool/.dockerignore @@ -0,0 +1,4 @@ +.venv +out +tests +__pycache__ diff --git a/formats/image-dimension-stacking-tool/.gitignore b/formats/image-dimension-stacking-tool/.gitignore new file mode 100644 index 000000000..c9c7ae717 --- /dev/null +++ b/formats/image-dimension-stacking-tool/.gitignore @@ -0,0 +1,2 @@ +poetry.lock +out diff --git a/formats/image-dimension-stacking-tool/Dockerfile b/formats/image-dimension-stacking-tool/Dockerfile new file mode 100644 index 000000000..45071e4c3 --- /dev/null +++ b/formats/image-dimension-stacking-tool/Dockerfile @@ -0,0 +1,21 @@ +FROM polusai/bfio:2.3.6 + +# environment variables defined in polusai/bfio +ENV EXEC_DIR="/opt/executables" +ENV POLUS_IMG_EXT=".ome.tif" +ENV POLUS_TAB_EXT=".csv" +ENV POLUS_LOG="INFO" + +# Work directory defined in the base container +WORKDIR ${EXEC_DIR} + +COPY pyproject.toml ${EXEC_DIR} +COPY VERSION ${EXEC_DIR} +COPY README.md ${EXEC_DIR} +COPY src ${EXEC_DIR}/src + +RUN pip3 install ${EXEC_DIR} --no-cache-dir + +# Default command. Additional arguments are provided through the command line +ENTRYPOINT ["python3", "-m", "polus.images.formats.image_dimension_stacking"] +CMD ["--help"] diff --git a/formats/image-dimension-stacking-tool/README.md b/formats/image-dimension-stacking-tool/README.md new file mode 100644 index 000000000..97e356680 --- /dev/null +++ b/formats/image-dimension-stacking-tool/README.md @@ -0,0 +1,50 @@ +# Image dimension stacking(0.1.1-dev) + +This plugin leverages the [filepattern](https://filepattern2.readthedocs.io/en/latest/Home.html) library and employs the filepattern `groupBy` functionality to enable the matching of image filenames, facilitating their stacking into multi-dimensional images. + +The filepattern must include the variables `c`, `t`, and `z`. If all these variables are present in the pattern, the plugin will group images according to the order `z, c, t`. If only one variable is present in the file pattern, the plugin will group images according to that variable. + + +Currently, the plugin supports the following dimensions and user can choose the relevant variable for the `groupBy` input argument. +1. `tubhiswt_z{z:d+}_c{c:d+}_t{t:d+}.ome.tif`\ + Images are grouped based on `z` variable +2. `tubhiswt_.*_.*_t{t:d+}.ome.tif`\ + Images are grouped based on `t` variable +3. `00001_01_{c:d+}.ome.tif`\ + Images are grouped based on `c` variable + +#### Note: +Filename patterns may consist of any other filepattern variables, combined with other valid regular expression arguments, excluding the `groupBy` variable. + +For more information on WIPP, visit the +[official WIPP page](https://isg.nist.gov/deepzoomweb/software/wipp). + +## Building + +To build the Docker image for the conversion plugin, run +`./build-docker.sh`. + +## Install WIPP Plugin + +If WIPP is running, navigate to the plugins page and add a new plugin. Paste the +contents of `plugin.json` into the pop-up window and submit. + +## Options + +This plugin takes three input argument and one output argument: + +| Name | Description | I/O | Type | +|---------------|-------------------------|--------|--------| +| `--inpDir` | Input image collection | Input | Collection | +| `--filePattern` | Pattern to parse image files | Input | String | +| `--outDir` | Output image collection | Output | Collection | +| `--preview` | Generate a JSON file to view outputs | Output | Boolean | + +### Run the Docker Container + +```bash +docker run -v /path/to/data:/data polusai/image-dimension-stacking-plugin:0.1.1-dev \ + --inpDir "Path/To/Data" \ + --filePattern "tubhiswt_C1-z{z:d+}.ome.tif" \ + --outDir "Path/To/Output/Dir" +``` diff --git a/formats/image-dimension-stacking-tool/VERSION b/formats/image-dimension-stacking-tool/VERSION new file mode 100644 index 000000000..c9927239f --- /dev/null +++ b/formats/image-dimension-stacking-tool/VERSION @@ -0,0 +1 @@ +0.1.1-dev diff --git a/formats/image-dimension-stacking-tool/build-docker.sh b/formats/image-dimension-stacking-tool/build-docker.sh new file mode 100644 index 000000000..0b0506259 --- /dev/null +++ b/formats/image-dimension-stacking-tool/build-docker.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +version=$(", + "Hamdah Shafqat abbasi " + ] +readme = "README.md" +packages = [{include = "polus", from = "src"}] + + +[tool.poetry.dependencies] +python = ">=3.9,<3.12" +bfio = {version = "2.3.3", extras = ["all"]} +typer = "^0.7.0" +tqdm = "^4.66.1" +filepattern = "^2.0.4" +pydantic = "^1.10.4" +preadator="0.4.0.dev2" + + +[[tool.poetry.source]] +name = "test" +url = "https://test.pypi.org/simple/" +default = false +secondary = true + +[tool.poetry.group.dev.dependencies] +bump2version = "^1.0.1" +flake8 = "^6.0.0" +pre-commit = "^3.2.1" +flake8-docstrings = "^1.7.0" +black = "^23.3.0" +mypy = "^1.1.1" +pytest = "^7.2.2" +ruff = "^0.0.270" +scikit-image = "^0.22.0" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/formats/image-dimension-stacking-tool/run-plugin.sh b/formats/image-dimension-stacking-tool/run-plugin.sh new file mode 100644 index 000000000..a1cc45c10 --- /dev/null +++ b/formats/image-dimension-stacking-tool/run-plugin.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +version=$( None: + """Generate preview of the plugin outputs.""" + with Path.open(Path(out_dir, "preview.json"), "w") as jfile: + out_json: dict[str, Any] = { + "filepattern": file_pattern, + "outDir": [], + } + + fps = fp.FilePattern(out_dir, file_pattern) + out_name = fps.output_name() + out_json["outDir"].append(out_name) + json.dump(out_json, jfile, indent=2) + + +@app.command() +def main( + inp_dir: Path = typer.Option( + ..., + "--inpDir", + "-i", + help="Path to input directory containing binary images.", + ), + file_pattern: str = typer.Option( + ".*", + "--filePattern", + "-f", + help="Filename pattern used to separate data.", + ), + out_dir: Path = typer.Option( + ..., + "--outDir", + "-o", + help="Output collection.", + ), + preview: bool = typer.Option( + False, + "--preview", + "-p", + help="Generate preview of expected outputs.", + ), +) -> None: + """Image dimension stacking plugin.""" + logger.info(f"--inpDir: {inp_dir}") + logger.info(f"--filePattern: {file_pattern}") + logger.info(f"--outDir: {out_dir}") + + if not inp_dir.exists(): + msg = "inpDir does not exist" + raise ValueError(msg, inp_dir) + + if not out_dir.exists(): + msg = "outDir does not exist" + raise ValueError(msg, out_dir) + + fps = fp.FilePattern(inp_dir, file_pattern) + list_val = ["c", "t", "z"] + variables = sorted([f for f in fps.get_variables() if f in list_val]) + + if len(variables) == 0: + msg = "Could not detect c, t or z variables in a pattern" + raise ValueError(msg) + + if variables == list_val or variables == ["z"]: + group_by = "z" + + if variables == ["c", "t"] or variables == ["c"]: + group_by = "c" + + if variables == ["t"]: + group_by = "t" + + if preview: + generate_preview(out_dir=out_dir, file_pattern=file_pattern) + + st.dimension_stacking( + inp_dir=inp_dir, + file_pattern=file_pattern, + group_by=group_by, + out_dir=out_dir, + ) + + +if __name__ == "__main__": + app() diff --git a/formats/image-dimension-stacking-tool/src/polus/images/formats/image_dimension_stacking/dimension_stacking.py b/formats/image-dimension-stacking-tool/src/polus/images/formats/image_dimension_stacking/dimension_stacking.py new file mode 100644 index 000000000..52dd237a2 --- /dev/null +++ b/formats/image-dimension-stacking-tool/src/polus/images/formats/image_dimension_stacking/dimension_stacking.py @@ -0,0 +1,196 @@ +"""Image dimension stacking package.""" +import logging +import re +import time +from concurrent.futures import as_completed +from multiprocessing import cpu_count +from pathlib import Path + +import filepattern as fp +import numpy as np +import preadator +from bfio import BioReader +from bfio import BioWriter +from tqdm import tqdm + +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + +chunk_size = 1024 + +num_workers = max([cpu_count(), 2]) + + +# Units for conversion +UNITS = { + "m": 10**9, + "cm": 10**7, + "mm": 10**6, + "µm": 10**3, + "nm": 1, + "Å": 10**-1, +} + + +def z_distance(file: Path) -> tuple[float, str]: + """Get physical z-distance. + + This estimates zdistance if not provided by averaging physical distances of x and y. + + Args: + file : Path to input image file + Returns: + A tuple of float and string values. + """ + # Get some basic info about the files to stack + with BioReader(file) as br: + # Get the physical z-distance if available, set to physical x if not + ps_z = br.ps_z + + # If the z-distances are undefined, average the x and y together + if None in ps_z: + # Get the size and units for x and y + x_val, xunits = br.ps_x + y_val, yunits = br.ps_y + + x_units = xunits.value + y_units = yunits.value + + # Convert x and y values to the same units and average + z_val = (x_val * UNITS[x_units] + y_val * UNITS[y_units]) / 2 + + # Set z units to the smaller of the units between x and y + z_units = x_units if UNITS[x_units] < UNITS[y_units] else y_units + + # Convert z to the proper unit scale + z_val /= UNITS[z_units] + ps_z = (z_val, z_units) + + if not ps_z: + msg = f"Unable to find physical z-size {ps_z}" + raise ValueError( + msg, + ) + + return ps_z + + +def write_image_stack(file: Path, di: int, group_by: str, bw: BioWriter) -> None: + """Write image stack. + + This function writes stacked images of either dimensions (z, c, t). + + Args: + file : Path to input image file + di : Index of dimension + group_by : A single string variable to group filenames by + bw : bfio.BioWriter. + + """ + with BioReader(file, max_workers=num_workers) as br: + for t in range(br.T): + for c in range(br.C): + for z in range(br.Z): + for y in range(0, br.Y, chunk_size): + y_max = min([br.Y, y + chunk_size]) + for x in range(0, br.X, chunk_size): + x_max = min([br.X, x + chunk_size]) + if group_by == "c": + tile = br[y:y_max, x:x_max, 0, c : c + 1, 0] + + bw[y:y_max, x:x_max, 0, di : di + 1, 0] = tile + if group_by == "t": + tile = br[y:y_max, x:x_max, 0, t : t + 1, 0] + bw[y:y_max, x:x_max, 0, 0, di : di + 1] = tile + + if group_by == "z": + tile = br[y:y_max, x:x_max, z : z + 1, 0, 0] + bw[y:y_max, x:x_max, di : di + 1, 0, 0] = tile + + +def dimension_stacking( + inp_dir: Path, + file_pattern: str, + group_by: str, + out_dir: Path, +) -> None: + """Image dimension stacking. + + This function enables to write stack image of dimensions (z, c, t). + inp_dir : Path to input directory containing images + file_pattern : Pattern to parse image files + group_by : A single string variable to group filenames by + out_dir : Path to output directory. + + """ + fps = fp.FilePattern(inp_dir, file_pattern) + groups = [fi[0] for fi, _ in fps(group_by=group_by)] + dimensions = [v for t in groups for v in t if isinstance(v, int)] + dim_min = min(dimensions) + dim_max = max(dimensions) + replace_value = f"({dim_min}-{dim_max})" + + # Get the number of layers to stack + dim_size = len(dimensions) + + group_range = np.unique([len(f) for gp, f in fps(group_by=group_by)])[0] + + for gi in range(0, group_range): + images = [f2[gi][1][0].name for f1, f2 in fps(group_by=group_by)] + input_files = [f2[gi][1][0] for f1, f2 in fps(group_by=group_by)] + pattern = fp.infer_pattern(files=images) + out_name = re.sub(r"\{(.*?)\}", replace_value, pattern) + with BioReader(input_files[0]) as br: + metadata = br.metadata + with BioWriter( + out_dir.joinpath(out_name), + metadata=metadata, + max_workers=num_workers, + ) as bw: + # Adjust the dimensions before writing + if group_by == "c": + bw.C = dim_size + if group_by == "t": + bw.T = dim_size + if group_by == "z": + bw.Z = dim_size + bw.ps_z = z_distance(Path(input_files[0])) + + for file, di in zip(input_files, range(0, dim_size)): + starttime = time.time() + + with preadator.ProcessManager( + name=f"Stacking images of {group_by} dimensions", + num_processes=num_workers, + threads_per_process=4, + ) as pm: + threads = [] + for file, di in zip( # noqa: PLW2901 + input_files, + range(0, dim_size), + ): + thread = pm.submit_thread( + write_image_stack, + file, + di=di, + group_by=group_by, + bw=bw, + ) + threads.append(thread) + pm.join_threads() + + for f in tqdm( + as_completed(threads), + total=len(threads), + mininterval=5, + desc=f"Stacking images of {group_by} dimensions", + initial=0, + unit_scale=True, + colour="cyan", + ): + f.result() + + endtime = (time.time() - starttime) / 60 + logger.info( + f"Total time taken for execution: {endtime:.4f} minutes", + ) diff --git a/formats/image-dimension-stacking-tool/tests/__init__.py b/formats/image-dimension-stacking-tool/tests/__init__.py new file mode 100644 index 000000000..39483ede7 --- /dev/null +++ b/formats/image-dimension-stacking-tool/tests/__init__.py @@ -0,0 +1 @@ +"""Image dimension stacking package.""" diff --git a/formats/image-dimension-stacking-tool/tests/conftest.py b/formats/image-dimension-stacking-tool/tests/conftest.py new file mode 100644 index 000000000..fdaef3712 --- /dev/null +++ b/formats/image-dimension-stacking-tool/tests/conftest.py @@ -0,0 +1,127 @@ +"""Test fixtures. + +Set up all data used in tests. +""" +import shutil +import tempfile +from pathlib import Path +from typing import Union + +import numpy as np +import pytest +from bfio import BioReader +from bfio import BioWriter +from skimage import filters +from skimage import io + + +def clean_directories() -> None: + """Remove all temporary directories.""" + for d in Path(".").cwd().iterdir(): + if d.is_dir() and d.name.startswith("tmp"): + shutil.rmtree(d) + + +def pytest_addoption(parser: pytest.Parser) -> None: + """Add options to pytest.""" + parser.addoption( + "--slow", + action="store_true", + dest="slow", + default=False, + help="run tests that download large data files", + ) + + +@pytest.fixture() +def output_directory() -> Union[str, Path]: + """Create output directory.""" + return Path(tempfile.mkdtemp(dir=Path.cwd())) + + +@pytest.fixture() +def inp_dir() -> Union[str, Path]: + """Create input directory.""" + return Path(tempfile.mkdtemp(dir=Path.cwd())) + + +@pytest.fixture( + params=[ + ("c", "image_x01_y01_c{c:d+}.ome.tif"), + ("z", "image_x01_y01_z{z:d+}.ome.tif"), + ("t", "image_x01_y01_t{t:d+}.ome.tif"), + ], +) +def get_params(request: pytest.FixtureRequest) -> pytest.FixtureRequest: + """To get the parameter of the fixture.""" + return request.param + + +@pytest.fixture() +def synthetic_images( + inp_dir: Union[str, Path], + get_params: pytest.FixtureRequest, +) -> tuple[Union[str, Path], str, str]: + """Generate random synthetic images.""" + image_sizes = 1024 + variable, pattern = get_params + for i in range(0, 10): + im = np.zeros((image_sizes, image_sizes)) + points = image_sizes * np.random.random((2, 10**2)) + im[(points[0]).astype(int), (points[1]).astype(int)] = 1 + im = filters.gaussian(im, sigma=image_sizes / (20.0 * 10)) + outname = f"image_x01_y01_{variable}{str(i).zfill(2)}.tif" + io.imsave(Path(inp_dir, outname), im) + + for inp in Path(inp_dir).iterdir(): + if inp.suffix == ".tif": + with BioReader(inp) as br: + img = br.read().squeeze() + outname = inp.stem + ".ome.tif" + with BioWriter( + file_path=Path(inp_dir, outname), + metadata=br.metadata, + ) as bw: + bw[:] = img + bw.close() + Path.unlink(inp) + + return inp_dir, variable, pattern + + +@pytest.fixture() +def synthetic_multi_images( + inp_dir: Union[str, Path], +) -> Union[str, Path]: + """Generate random synthetic images.""" + image_sizes = 1024 + + for i in range(0, 4): + im = np.zeros((image_sizes, image_sizes)) + points = image_sizes * np.random.random((2, 10**2)) + im[(points[0]).astype(int), (points[1]).astype(int)] = 1 + im = filters.gaussian(im, sigma=image_sizes / (20.0 * 10)) + outname_1 = f"tubhiswt_z00_c00_t{str(i).zfill(2)}.tif" + outname_2 = f"tubhiswt_z01_c00_t{str(i).zfill(2)}.tif" + outname_3 = f"tubhiswt_z00_c01_t{str(i).zfill(2)}.tif" + outname_4 = f"tubhiswt_z01_c01_t{str(i).zfill(2)}.tif" + + io.imsave(Path(inp_dir, outname_1), im) + io.imsave(Path(inp_dir, outname_2), im) + io.imsave(Path(inp_dir, outname_3), im) + io.imsave(Path(inp_dir, outname_4), im) + + for inp in Path(inp_dir).iterdir(): + if inp.suffix == ".tif": + with BioReader(inp) as br: + img = br.read().squeeze() + outname = inp.stem + ".ome.tif" + with BioWriter( + file_path=Path(inp_dir, outname), + metadata=br.metadata, + ) as bw: + bw[:] = img + bw.close() + Path.unlink(inp) + + return inp_dir diff --git a/formats/image-dimension-stacking-tool/tests/test_cli.py b/formats/image-dimension-stacking-tool/tests/test_cli.py new file mode 100644 index 000000000..81a32d325 --- /dev/null +++ b/formats/image-dimension-stacking-tool/tests/test_cli.py @@ -0,0 +1,71 @@ +"""Test Command line Tool.""" +from typer.testing import CliRunner +from polus.images.formats.image_dimension_stacking.__main__ import app +from pathlib import Path +from typing import Union +import pytest + + +def test_cli(synthetic_images: tuple[Union[str, Path]], output_directory: Path) -> None: + """Test the command line.""" + inp_dir, _, pattern = synthetic_images + + runner = CliRunner() + result = runner.invoke( + app, + [ + "--inpDir", + inp_dir, + "--filePattern", + pattern, + "--outDir", + output_directory, + ], + ) + + assert result.exit_code == 0 + + +@pytest.mark.skipif("not config.getoption('slow')") +def test_multipattern_cli( + synthetic_multi_images: Union[str, Path], output_directory: Path +) -> None: + """Test the command line.""" + inp_dir = synthetic_multi_images + pattern = "tubhiswt_z{z:d+}_c{c:d+}_t{t:d+}.ome.tif" + + runner = CliRunner() + result = runner.invoke( + app, + [ + "--inpDir", + inp_dir, + "--filePattern", + pattern, + "--outDir", + output_directory, + ], + ) + + assert result.exit_code == 0 + + +def test_short_cli( + synthetic_images: tuple[Union[str, Path]], output_directory: Path +) -> None: + """Test the short cli command line.""" + inp_dir, _, pattern = synthetic_images + runner = CliRunner() + result = runner.invoke( + app, + [ + "-i", + inp_dir, + "-f", + pattern, + "-o", + output_directory, + ], + ) + + assert result.exit_code == 0 diff --git a/formats/image-dimension-stacking-tool/tests/test_dimension_stacking.py b/formats/image-dimension-stacking-tool/tests/test_dimension_stacking.py new file mode 100644 index 000000000..2cd39dca4 --- /dev/null +++ b/formats/image-dimension-stacking-tool/tests/test_dimension_stacking.py @@ -0,0 +1,73 @@ +"""Testing of image dimension stacking.""" +from pathlib import Path +from typing import Union + +import polus.images.formats.image_dimension_stacking.dimension_stacking as ds +from bfio import BioReader +from bfio import BioWriter + +from .conftest import * # noqa:F403 +from .conftest import clean_directories + + +def test_dimension_stacking( + synthetic_images: tuple[Union[str, Path], str, str], + output_directory: Path, +) -> None: + """Test dimension stacking.""" + inp_dir, variable, pattern = synthetic_images + + ds.dimension_stacking( + inp_dir=inp_dir, + file_pattern=pattern, + group_by=variable, + out_dir=output_directory, + ) + + outfile = [ + f for f in output_directory.iterdir() if f"{variable}0(0-9).ome.tif" in f.name + ] + assert all(outfile) is True + assert len(outfile) == 1 + + total_dimensions = 10 + + br = BioReader(outfile[0]) + if variable == "c": + assert total_dimensions == br.C + if variable == "z": + assert total_dimensions == br.Z + if variable == "t": + assert total_dimensions == br.T + + +def test_write_image_stack( + synthetic_images: tuple[Union[str, Path], str, str], + output_directory: Path, +) -> None: + """Test writing stacked images.""" + inp_dir, variable, _ = synthetic_images + + for file in Path(inp_dir).iterdir(): + if file.name.endswith(".ome.tif"): + with BioReader(file) as br: + metadata = br.metadata + + with BioWriter( + output_directory.joinpath(file.name), + metadata=metadata, + ) as bw: + ds.write_image_stack(file=file, di=0, group_by=variable, bw=bw) + total_dimensions = 10 + assert len(list(output_directory.iterdir())) == total_dimensions + + +def test_z_distance(synthetic_images: tuple[Union[str, Path], str, str]) -> None: + """Test estimating z-distance.""" + inp_dir, _, _ = synthetic_images + distances = [] + for file in Path(inp_dir).iterdir(): + ps_z = ds.z_distance(file=file) + distances.append(ps_z) + assert all(distances) is not None + clean_directories()