From 36ab68d448c4b594a33039e2ca566abc4c82546c Mon Sep 17 00:00:00 2001 From: Matt Einhorn Date: Tue, 9 Apr 2024 23:53:09 -0400 Subject: [PATCH 1/6] Support single z-stack tif file for input. --- brainglobe_utils/image_io/load.py | 32 +++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/brainglobe_utils/image_io/load.py b/brainglobe_utils/image_io/load.py index 33e5acf..3589310 100644 --- a/brainglobe_utils/image_io/load.py +++ b/brainglobe_utils/image_io/load.py @@ -665,14 +665,15 @@ def load_from_paths_sequence( def get_size_image_from_file_paths(file_path, file_extension="tif"): """ - Returns the size of an image (which is a list of 2D tiff files), - without loading the whole image. + Returns the size of an image (which is a list of 2D tiff files or a + single-file tif stack), without loading the whole image. Parameters ---------- file_path : str or pathlib.Path - Filepath of text file containing paths of all 2D files, or - filepath of a directory containing all 2D files. + Filepath of text file containing paths of all 2D files, a + filepath of a directory containing all 2D files, or a single + tiff file z-stack. file_extension : str, optional Optional file extension (if a directory is passed). @@ -683,6 +684,29 @@ def get_size_image_from_file_paths(file_path, file_extension="tif"): Dict of image sizes. """ file_path = Path(file_path) + if file_path.name.endswith(".tif") or file_path.name.endswith(".tiff"): + # read just the metadata + tiff = tifffile.TiffFile(file_path) + if not len(tiff.series): + raise ValueError( + f"Attempted to load {file_path} but couldn't read a z-stack" + ) + if len(tiff.series) != 1: + raise ValueError( + f"Attempted to load {file_path} but found multiple stacks" + ) + + shape = tiff.series[0].shape + axes = tiff.series[0].axes.lower() + # axes is e.g. "ZXY" + indices = {ax: i for i, ax in enumerate(axes)} + if set(axes) != {"x", "y", "z"}: + raise ValueError( + f"Attempted to load {file_path} but didn't find a xyz-stack. " + f"Found {axes} axes with shape {shape}") + + image_shape = {name: shape[i] for name, i in indices.items()} + return image_shape img_paths = get_sorted_file_paths(file_path, file_extension=file_extension) z_shape = len(img_paths) From 6aa5e395f450579d770e75c498d50c6e12a46bd0 Mon Sep 17 00:00:00 2001 From: Matt Einhorn Date: Tue, 9 Apr 2024 23:55:16 -0400 Subject: [PATCH 2/6] Fix commit hook. --- brainglobe_utils/image_io/load.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/brainglobe_utils/image_io/load.py b/brainglobe_utils/image_io/load.py index 3589310..ddfe32e 100644 --- a/brainglobe_utils/image_io/load.py +++ b/brainglobe_utils/image_io/load.py @@ -703,7 +703,8 @@ def get_size_image_from_file_paths(file_path, file_extension="tif"): if set(axes) != {"x", "y", "z"}: raise ValueError( f"Attempted to load {file_path} but didn't find a xyz-stack. " - f"Found {axes} axes with shape {shape}") + f"Found {axes} axes with shape {shape}" + ) image_shape = {name: shape[i] for name, i in indices.items()} return image_shape From c7e9de51f166e96393ecdfd088470ee1a0a34f8f Mon Sep 17 00:00:00 2001 From: Matt Einhorn Date: Mon, 22 Apr 2024 01:31:39 -0400 Subject: [PATCH 3/6] Apply suggestions from code review Co-authored-by: Kimberly Meechan <24316371+K-Meech@users.noreply.github.com> --- brainglobe_utils/image_io/load.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brainglobe_utils/image_io/load.py b/brainglobe_utils/image_io/load.py index ddfe32e..16b86e9 100644 --- a/brainglobe_utils/image_io/load.py +++ b/brainglobe_utils/image_io/load.py @@ -684,7 +684,7 @@ def get_size_image_from_file_paths(file_path, file_extension="tif"): Dict of image sizes. """ file_path = Path(file_path) - if file_path.name.endswith(".tif") or file_path.name.endswith(".tiff"): + if file_path.suffix in [".tif", ".tiff"]: # read just the metadata tiff = tifffile.TiffFile(file_path) if not len(tiff.series): From 8578cc89c916490dd48ab8aa680c32e599da4e3a Mon Sep 17 00:00:00 2001 From: Matt Einhorn Date: Mon, 22 Apr 2024 01:42:25 -0400 Subject: [PATCH 4/6] Apply review changes. --- brainglobe_utils/image_io/load.py | 39 +++++++++++++++---------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/brainglobe_utils/image_io/load.py b/brainglobe_utils/image_io/load.py index 16b86e9..f094b52 100644 --- a/brainglobe_utils/image_io/load.py +++ b/brainglobe_utils/image_io/load.py @@ -686,28 +686,27 @@ def get_size_image_from_file_paths(file_path, file_extension="tif"): file_path = Path(file_path) if file_path.suffix in [".tif", ".tiff"]: # read just the metadata - tiff = tifffile.TiffFile(file_path) - if not len(tiff.series): - raise ValueError( - f"Attempted to load {file_path} but couldn't read a z-stack" - ) - if len(tiff.series) != 1: - raise ValueError( - f"Attempted to load {file_path} but found multiple stacks" - ) + with tifffile.TiffFile(file_path) as tiff: + if not len(tiff.series): + raise ValueError( + f"Attempted to load {file_path} but didn't find a z-stack" + ) + if len(tiff.series) != 1: + raise ValueError( + f"Attempted to load {file_path} but found multiple stacks" + ) - shape = tiff.series[0].shape - axes = tiff.series[0].axes.lower() - # axes is e.g. "ZXY" - indices = {ax: i for i, ax in enumerate(axes)} - if set(axes) != {"x", "y", "z"}: - raise ValueError( - f"Attempted to load {file_path} but didn't find a xyz-stack. " - f"Found {axes} axes with shape {shape}" - ) + shape = tiff.series[0].shape + axes = tiff.series[0].axes.lower() + # axes is e.g. "zxy" + if set(axes) != {"x", "y", "z"}: + raise ValueError( + f"Attempted to load {file_path} but didn't find a " + f"xyz-stack. Found {axes} axes with shape {shape}" + ) - image_shape = {name: shape[i] for name, i in indices.items()} - return image_shape + image_shape = {ax: n for ax, n in zip(axes, shape)} + return image_shape img_paths = get_sorted_file_paths(file_path, file_extension=file_extension) z_shape = len(img_paths) From befb7489fdac34a39eb5a362e8150dbfa05bfdba Mon Sep 17 00:00:00 2001 From: Matt Einhorn Date: Mon, 22 Apr 2024 02:06:20 -0400 Subject: [PATCH 5/6] Add test and fix saving to set axes to zyx instead of default qyx. --- brainglobe_utils/image_io/save.py | 7 ++++++- tests/tests/test_image_io.py | 13 +++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/brainglobe_utils/image_io/save.py b/brainglobe_utils/image_io/save.py index 613b805..2115f07 100644 --- a/brainglobe_utils/image_io/save.py +++ b/brainglobe_utils/image_io/save.py @@ -58,7 +58,12 @@ def to_tiff(img_volume, dest_path, photometric="minisblack"): Use 'minisblack' (default) for grayscale and 'rgb' for rgb """ dest_path = Path(dest_path) - tifffile.imwrite(dest_path, img_volume, photometric=photometric) + tifffile.imwrite( + dest_path, + img_volume, + photometric=photometric, + metadata={"axes": "ZYX"}, + ) def to_tiffs(img_volume, path_prefix, path_suffix="", extension=".tif"): diff --git a/tests/tests/test_image_io.py b/tests/tests/test_image_io.py index e720cd5..0f38c39 100644 --- a/tests/tests/test_image_io.py +++ b/tests/tests/test_image_io.py @@ -350,6 +350,19 @@ def test_image_size_dir(tmp_path, array_3d): assert image_shape["z"] == array_3d.shape[0] +def test_image_size_tiff_stack(tmp_path, array_3d): + """ + Test that image size can be detected from a directory of 2D tiffs + """ + filename = tmp_path.joinpath("image_size_tiff_stack.tif") + save.save_any(array_3d, filename) + + image_shape = load.get_size_image_from_file_paths(filename) + assert image_shape["x"] == array_3d.shape[2] + assert image_shape["y"] == array_3d.shape[1] + assert image_shape["z"] == array_3d.shape[0] + + def test_image_size_txt(txt_path, array_3d): """ Test that image size can be detected from a text file containing the paths From ed40e386f091a28742acb6d41694014aa3a21f0a Mon Sep 17 00:00:00 2001 From: alessandrofelder Date: Fri, 3 May 2024 16:16:07 +0100 Subject: [PATCH 6/6] allow no axis metadata for 3D tiff files --- brainglobe_utils/image_io/load.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/brainglobe_utils/image_io/load.py b/brainglobe_utils/image_io/load.py index f094b52..2ad4969 100644 --- a/brainglobe_utils/image_io/load.py +++ b/brainglobe_utils/image_io/load.py @@ -698,15 +698,28 @@ def get_size_image_from_file_paths(file_path, file_extension="tif"): shape = tiff.series[0].shape axes = tiff.series[0].axes.lower() - # axes is e.g. "zxy" - if set(axes) != {"x", "y", "z"}: + + if len(shape) != 3: raise ValueError( f"Attempted to load {file_path} but didn't find a " - f"xyz-stack. Found {axes} axes with shape {shape}" + f"3-dimensional stack. Found {axes} axes " + f"with shape {shape}" ) - - image_shape = {ax: n for ax, n in zip(axes, shape)} - return image_shape + # axes is e.g. "zxy" + if set(axes) == {"x", "y", "z"}: + image_shape = {ax: n for ax, n in zip(axes, shape)} + return image_shape + else: # metadata does not specify axes as expected, + logging.debug( + f"Axis metadata is {axes}, " + "which is not the expected set of x,y,z in any order. " + "Assume z,y,x" + ) + image_shape = { + ax: n + for ax, n in zip(["z", "y", "x"], tiff.series[0].shape) + } + return image_shape img_paths = get_sorted_file_paths(file_path, file_extension=file_extension) z_shape = len(img_paths)