From a65b72fa909b7cb0bef5d6c22e09de1ac69b65ed Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Tue, 8 Oct 2024 15:05:03 +0200 Subject: [PATCH 01/24] Add tests for `axes` metadata --- lib/galaxy/datatypes/images.py | 35 +++++++++++++++ test/functional/tools/sample_tool_conf.xml | 1 + .../tools/validation_image_metadata.xml | 44 +++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 test/functional/tools/validation_image_metadata.xml diff --git a/lib/galaxy/datatypes/images.py b/lib/galaxy/datatypes/images.py index e61efeaca05a..8a217c025ab8 100644 --- a/lib/galaxy/datatypes/images.py +++ b/lib/galaxy/datatypes/images.py @@ -11,6 +11,12 @@ import numpy as np import tifffile +try: + import PIL + import PIL.Image +except ImportError: + PIL = None + from galaxy.datatypes.binary import Binary from galaxy.datatypes.metadata import ( FileParameter, @@ -50,6 +56,14 @@ class Image(data.Data): edam_format = "format_3547" file_ext = "" + MetadataElement( + name="axes", + desc="Axes of the image data", + readonly=True, + visible=True, + optional=True, + ) + def __init__(self, **kwd): super().__init__(**kwd) self.image_formats = [self.file_ext.upper()] @@ -73,6 +87,26 @@ def handle_dataset_as_image(self, hda: DatasetProtocol) -> str: base64_image_data = base64.b64encode(f.read()).decode("utf-8") return f"![{name}](data:image/{self.file_ext};base64,{base64_image_data})" + def set_meta( + self, dataset: DatasetProtocol, overwrite: bool = True, metadata_tmp_files_dir: Optional[str] = None, **kwd + ) -> None: + """ + Try to populate the metadata of the image using a generic image loading library (pillow), if available. + + If an image has two axes, they are assumed to be ``YX``. If an image has three axes, they are assumed to be ``YXC``. + """ + if PIL is not None: + try: + with PIL.Image.open(dataset.get_file_name()) as im: + im_arr = np.array(im) +# dataset.metadata.dtype = str(im_arr.dtype) + if im_arr.ndim == 2: + dataset.metadata.axes = 'YX' + elif im_arr.ndim == 3: + dataset.metadata.axes = 'YXC' + except PIL.UnidentifiedImageError: + pass + class Jpg(Image): edam_format = "format_3579" @@ -112,6 +146,7 @@ def set_meta( ) with tifffile.TiffFile(dataset.get_file_name()) as tif: offsets = [page.offset for page in tif.pages] + dataset.metadata.axes = tif.series[0].axes.upper() with open(offsets_file.get_file_name(), "w") as f: json.dump(offsets, f) dataset.metadata.offsets = offsets_file diff --git a/test/functional/tools/sample_tool_conf.xml b/test/functional/tools/sample_tool_conf.xml index bb9d7568f600..e0b0f5bfadbf 100644 --- a/test/functional/tools/sample_tool_conf.xml +++ b/test/functional/tools/sample_tool_conf.xml @@ -149,6 +149,7 @@ + diff --git a/test/functional/tools/validation_image_metadata.xml b/test/functional/tools/validation_image_metadata.xml new file mode 100644 index 000000000000..c39aa395ff57 --- /dev/null +++ b/test/functional/tools/validation_image_metadata.xml @@ -0,0 +1,44 @@ + + testfile + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 8c9ba3868cca8f2f37d7f6a7b000e528da2ef01c Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Tue, 8 Oct 2024 15:08:57 +0200 Subject: [PATCH 02/24] Reduce boilerplate code in tests --- .../tools/validation_image_metadata.xml | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/test/functional/tools/validation_image_metadata.xml b/test/functional/tools/validation_image_metadata.xml index c39aa395ff57..0bc407887537 100644 --- a/test/functional/tools/validation_image_metadata.xml +++ b/test/functional/tools/validation_image_metadata.xml @@ -1,4 +1,14 @@ + + + + + + + + + + testfile ]]> @@ -24,21 +34,13 @@ - - - - - + - - - - - + From 29dc831afacb7ceb984536fc4cba287634f3234c Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Tue, 8 Oct 2024 15:37:25 +0200 Subject: [PATCH 03/24] Add `dtype` metadata and tests --- lib/galaxy/datatypes/images.py | 11 ++++++++++- .../tools/validation_image_metadata.xml | 16 ++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/galaxy/datatypes/images.py b/lib/galaxy/datatypes/images.py index 8a217c025ab8..555d18e45e43 100644 --- a/lib/galaxy/datatypes/images.py +++ b/lib/galaxy/datatypes/images.py @@ -64,6 +64,14 @@ class Image(data.Data): optional=True, ) + MetadataElement( + name="dtype", + desc="Data type of the image pixels or voxels", + readonly=True, + visible=True, + optional=True, + ) + def __init__(self, **kwd): super().__init__(**kwd) self.image_formats = [self.file_ext.upper()] @@ -99,7 +107,7 @@ def set_meta( try: with PIL.Image.open(dataset.get_file_name()) as im: im_arr = np.array(im) -# dataset.metadata.dtype = str(im_arr.dtype) + dataset.metadata.dtype = str(im_arr.dtype) if im_arr.ndim == 2: dataset.metadata.axes = 'YX' elif im_arr.ndim == 3: @@ -147,6 +155,7 @@ def set_meta( with tifffile.TiffFile(dataset.get_file_name()) as tif: offsets = [page.offset for page in tif.pages] dataset.metadata.axes = tif.series[0].axes.upper() + dataset.metadata.dtype = str(tif.series[0].dtype) with open(offsets_file.get_file_name(), "w") as f: json.dump(offsets, f) dataset.metadata.offsets = offsets_file diff --git a/test/functional/tools/validation_image_metadata.xml b/test/functional/tools/validation_image_metadata.xml index 0bc407887537..16247ac4f66a 100644 --- a/test/functional/tools/validation_image_metadata.xml +++ b/test/functional/tools/validation_image_metadata.xml @@ -13,6 +13,7 @@ cat /dev/null > testfile ]]> + @@ -22,9 +23,16 @@ - + - --> + + + + + + + @@ -34,12 +42,16 @@ + + + + From d350ccad88a9f306c17c7c23a8f9d3babf0fcc0b Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Tue, 8 Oct 2024 15:48:57 +0200 Subject: [PATCH 04/24] Add `num_unique_values` metadata and tests --- lib/galaxy/datatypes/images.py | 14 ++++++++++++++ .../functional/tools/validation_image_metadata.xml | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/lib/galaxy/datatypes/images.py b/lib/galaxy/datatypes/images.py index 555d18e45e43..a30546bec1c0 100644 --- a/lib/galaxy/datatypes/images.py +++ b/lib/galaxy/datatypes/images.py @@ -72,6 +72,14 @@ class Image(data.Data): optional=True, ) + MetadataElement( + name="num_unique_values", + desc="Number of unique values in the image data (e.g., should be 2 for binary images)", + readonly=True, + visible=True, + optional=True, + ) + def __init__(self, **kwd): super().__init__(**kwd) self.image_formats = [self.file_ext.upper()] @@ -108,6 +116,7 @@ def set_meta( with PIL.Image.open(dataset.get_file_name()) as im: im_arr = np.array(im) dataset.metadata.dtype = str(im_arr.dtype) + dataset.metadata.num_unique_values = str(len(np.unique(im))) if im_arr.ndim == 2: dataset.metadata.axes = 'YX' elif im_arr.ndim == 3: @@ -146,6 +155,9 @@ class Tiff(Image): def set_meta( self, dataset: DatasetProtocol, overwrite: bool = True, metadata_tmp_files_dir: Optional[str] = None, **kwd ) -> None: + """ + Populate the metadata of the TIFF image using the tifffile library. + """ spec_key = "offsets" offsets_file = dataset.metadata.offsets if not offsets_file: @@ -153,9 +165,11 @@ def set_meta( dataset=dataset, metadata_tmp_files_dir=metadata_tmp_files_dir ) with tifffile.TiffFile(dataset.get_file_name()) as tif: + im_arr = tif.asarray() offsets = [page.offset for page in tif.pages] dataset.metadata.axes = tif.series[0].axes.upper() dataset.metadata.dtype = str(tif.series[0].dtype) + dataset.metadata.num_unique_values = str(len(np.unique(im_arr))) with open(offsets_file.get_file_name(), "w") as f: json.dump(offsets, f) dataset.metadata.offsets = offsets_file diff --git a/test/functional/tools/validation_image_metadata.xml b/test/functional/tools/validation_image_metadata.xml index 16247ac4f66a..c807a3f94acc 100644 --- a/test/functional/tools/validation_image_metadata.xml +++ b/test/functional/tools/validation_image_metadata.xml @@ -33,6 +33,16 @@ + + + + + + + + + + @@ -45,6 +55,8 @@ + + @@ -52,6 +64,8 @@ + + From 7fa6796ca439642656364e7b590feea335dc542f Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Tue, 8 Oct 2024 16:11:12 +0200 Subject: [PATCH 05/24] Add `width` and `height` metadata and tests --- lib/galaxy/datatypes/images.py | 25 +++++++++++++++++++ .../tools/validation_image_metadata.xml | 20 +++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/lib/galaxy/datatypes/images.py b/lib/galaxy/datatypes/images.py index a30546bec1c0..786ca0bd7784 100644 --- a/lib/galaxy/datatypes/images.py +++ b/lib/galaxy/datatypes/images.py @@ -80,6 +80,22 @@ class Image(data.Data): optional=True, ) + MetadataElement( + name="width", + desc="Width of the image (in pixels)", + readonly=True, + visible=True, + optional=True, + ) + + MetadataElement( + name="height", + desc="Height of the image (in pixels)", + readonly=True, + visible=True, + optional=True, + ) + def __init__(self, **kwd): super().__init__(**kwd) self.image_formats = [self.file_ext.upper()] @@ -117,6 +133,8 @@ def set_meta( im_arr = np.array(im) dataset.metadata.dtype = str(im_arr.dtype) dataset.metadata.num_unique_values = str(len(np.unique(im))) + dataset.metadata.width = str(im_arr.shape[1]) + dataset.metadata.height = str(im_arr.shape[0]) if im_arr.ndim == 2: dataset.metadata.axes = 'YX' elif im_arr.ndim == 3: @@ -169,11 +187,18 @@ def set_meta( offsets = [page.offset for page in tif.pages] dataset.metadata.axes = tif.series[0].axes.upper() dataset.metadata.dtype = str(tif.series[0].dtype) + dataset.metadata.width = str(Tiff._get_axis_size(im_arr, dataset.metadata.axes, 'X')) + dataset.metadata.height = str(Tiff._get_axis_size(im_arr, dataset.metadata.axes, 'Y')) dataset.metadata.num_unique_values = str(len(np.unique(im_arr))) with open(offsets_file.get_file_name(), "w") as f: json.dump(offsets, f) dataset.metadata.offsets = offsets_file + @staticmethod + def _get_axis_size(im_arr: "np.typing.NDArray", axes: str, axis: str) -> int: + idx = axes.find(axis) + return im_arr.shape[idx] if idx >= 0 else 0 + class OMETiff(Tiff): file_ext = "ome.tiff" diff --git a/test/functional/tools/validation_image_metadata.xml b/test/functional/tools/validation_image_metadata.xml index c807a3f94acc..903b18bc883c 100644 --- a/test/functional/tools/validation_image_metadata.xml +++ b/test/functional/tools/validation_image_metadata.xml @@ -43,6 +43,20 @@ + + + + + + + + + + + + + + @@ -57,6 +71,10 @@ + + + + @@ -66,6 +84,8 @@ + + From 24140398faffdce57a0aba49ff89bd9ccc04bb48 Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Tue, 8 Oct 2024 16:21:48 +0200 Subject: [PATCH 06/24] Add `channels` metadata and tests --- lib/galaxy/datatypes/images.py | 15 ++++++++++-- .../tools/validation_image_metadata.xml | 24 +++++++++++++++---- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/lib/galaxy/datatypes/images.py b/lib/galaxy/datatypes/images.py index 786ca0bd7784..32e6ecb020c6 100644 --- a/lib/galaxy/datatypes/images.py +++ b/lib/galaxy/datatypes/images.py @@ -96,6 +96,14 @@ class Image(data.Data): optional=True, ) + MetadataElement( + name="channels", + desc="Number of channels of the image", + readonly=True, + visible=True, + optional=True, + ) + def __init__(self, **kwd): super().__init__(**kwd) self.image_formats = [self.file_ext.upper()] @@ -136,9 +144,11 @@ def set_meta( dataset.metadata.width = str(im_arr.shape[1]) dataset.metadata.height = str(im_arr.shape[0]) if im_arr.ndim == 2: - dataset.metadata.axes = 'YX' + dataset.metadata.axes = "YX" + dataset.metadata.channels = "0" elif im_arr.ndim == 3: - dataset.metadata.axes = 'YXC' + dataset.metadata.axes = "YXC" + dataset.metadata.channels = str(im_arr.shape[2]) except PIL.UnidentifiedImageError: pass @@ -189,6 +199,7 @@ def set_meta( dataset.metadata.dtype = str(tif.series[0].dtype) dataset.metadata.width = str(Tiff._get_axis_size(im_arr, dataset.metadata.axes, 'X')) dataset.metadata.height = str(Tiff._get_axis_size(im_arr, dataset.metadata.axes, 'Y')) + dataset.metadata.channels = str(Tiff._get_axis_size(im_arr, dataset.metadata.axes.replace('S', 'C'), 'C')) dataset.metadata.num_unique_values = str(len(np.unique(im_arr))) with open(offsets_file.get_file_name(), "w") as f: json.dump(offsets, f) diff --git a/test/functional/tools/validation_image_metadata.xml b/test/functional/tools/validation_image_metadata.xml index 903b18bc883c..419e865eca81 100644 --- a/test/functional/tools/validation_image_metadata.xml +++ b/test/functional/tools/validation_image_metadata.xml @@ -57,6 +57,16 @@ + + + + + + + + + + @@ -71,10 +81,13 @@ - - - - + + + + + + + @@ -86,7 +99,10 @@ + + + From ac29d17d18f2b836331f3d7c93f474c28470c7c0 Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Tue, 8 Oct 2024 16:34:59 +0200 Subject: [PATCH 07/24] Add `depth` and `frames` metadata and tests --- lib/galaxy/datatypes/images.py | 26 ++++++++++++++++--- .../tools/validation_image_metadata.xml | 20 ++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/lib/galaxy/datatypes/images.py b/lib/galaxy/datatypes/images.py index 32e6ecb020c6..a795df760325 100644 --- a/lib/galaxy/datatypes/images.py +++ b/lib/galaxy/datatypes/images.py @@ -104,6 +104,22 @@ class Image(data.Data): optional=True, ) + MetadataElement( + name="depth", + desc="Depth of the image (number of slices)", + readonly=True, + visible=True, + optional=True, + ) + + MetadataElement( + name="frames", + desc="Number of frames in the image sequence (number of time steps)", + readonly=True, + visible=True, + optional=True, + ) + def __init__(self, **kwd): super().__init__(**kwd) self.image_formats = [self.file_ext.upper()] @@ -143,6 +159,8 @@ def set_meta( dataset.metadata.num_unique_values = str(len(np.unique(im))) dataset.metadata.width = str(im_arr.shape[1]) dataset.metadata.height = str(im_arr.shape[0]) + dataset.metadata.depth = "0" + dataset.metadata.frames = "0" if im_arr.ndim == 2: dataset.metadata.axes = "YX" dataset.metadata.channels = "0" @@ -197,9 +215,11 @@ def set_meta( offsets = [page.offset for page in tif.pages] dataset.metadata.axes = tif.series[0].axes.upper() dataset.metadata.dtype = str(tif.series[0].dtype) - dataset.metadata.width = str(Tiff._get_axis_size(im_arr, dataset.metadata.axes, 'X')) - dataset.metadata.height = str(Tiff._get_axis_size(im_arr, dataset.metadata.axes, 'Y')) - dataset.metadata.channels = str(Tiff._get_axis_size(im_arr, dataset.metadata.axes.replace('S', 'C'), 'C')) + dataset.metadata.width = str(Tiff._get_axis_size(im_arr, dataset.metadata.axes, "X")) + dataset.metadata.height = str(Tiff._get_axis_size(im_arr, dataset.metadata.axes, "Y")) + dataset.metadata.channels = str(Tiff._get_axis_size(im_arr, dataset.metadata.axes.replace("S", "C"), "C")) + dataset.metadata.depth = str(Tiff._get_axis_size(im_arr, dataset.metadata.axes, "Z")) + dataset.metadata.frames = str(Tiff._get_axis_size(im_arr, dataset.metadata.axes, "T")) dataset.metadata.num_unique_values = str(len(np.unique(im_arr))) with open(offsets_file.get_file_name(), "w") as f: json.dump(offsets, f) diff --git a/test/functional/tools/validation_image_metadata.xml b/test/functional/tools/validation_image_metadata.xml index 419e865eca81..c63f8ec4732e 100644 --- a/test/functional/tools/validation_image_metadata.xml +++ b/test/functional/tools/validation_image_metadata.xml @@ -67,6 +67,20 @@ + + + + + + + + + + + + + + @@ -88,6 +102,10 @@ + + + + @@ -101,6 +119,8 @@ + + From 438658078832b162665faf80d3d3ed74421a0d74 Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Tue, 8 Oct 2024 17:08:42 +0200 Subject: [PATCH 08/24] Fix mypy check --- lib/galaxy/datatypes/images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/datatypes/images.py b/lib/galaxy/datatypes/images.py index a795df760325..e6e9bf8cc420 100644 --- a/lib/galaxy/datatypes/images.py +++ b/lib/galaxy/datatypes/images.py @@ -15,7 +15,7 @@ import PIL import PIL.Image except ImportError: - PIL = None + PIL = None # type: ignore[assignment, unused-ignore] from galaxy.datatypes.binary import Binary from galaxy.datatypes.metadata import ( From e03046413cedba63e988efa00a402a1ebb4c4019 Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Tue, 8 Oct 2024 22:27:31 +0200 Subject: [PATCH 09/24] Fix support for TIFF files with unsupported compression formats --- lib/galaxy/datatypes/images.py | 24 +++++++++++++------ .../tools/validation_image_metadata.xml | 14 +++++++++++ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/lib/galaxy/datatypes/images.py b/lib/galaxy/datatypes/images.py index e6e9bf8cc420..2fbcbdbef90f 100644 --- a/lib/galaxy/datatypes/images.py +++ b/lib/galaxy/datatypes/images.py @@ -211,16 +211,26 @@ def set_meta( dataset=dataset, metadata_tmp_files_dir=metadata_tmp_files_dir ) with tifffile.TiffFile(dataset.get_file_name()) as tif: - im_arr = tif.asarray() offsets = [page.offset for page in tif.pages] + + # Populate the metadata fields that should be generally available dataset.metadata.axes = tif.series[0].axes.upper() dataset.metadata.dtype = str(tif.series[0].dtype) - dataset.metadata.width = str(Tiff._get_axis_size(im_arr, dataset.metadata.axes, "X")) - dataset.metadata.height = str(Tiff._get_axis_size(im_arr, dataset.metadata.axes, "Y")) - dataset.metadata.channels = str(Tiff._get_axis_size(im_arr, dataset.metadata.axes.replace("S", "C"), "C")) - dataset.metadata.depth = str(Tiff._get_axis_size(im_arr, dataset.metadata.axes, "Z")) - dataset.metadata.frames = str(Tiff._get_axis_size(im_arr, dataset.metadata.axes, "T")) - dataset.metadata.num_unique_values = str(len(np.unique(im_arr))) + + # Populate the metadata fields that require reading the image data + try: + im_arr = tif.asarray() + except ValueError: # Occurs if the compression of the TIFF file is unsupported + im_arr = None + if im_arr is not None: + dataset.metadata.width = str(Tiff._get_axis_size(im_arr, dataset.metadata.axes, "X")) + dataset.metadata.height = str(Tiff._get_axis_size(im_arr, dataset.metadata.axes, "Y")) + dataset.metadata.channels = str(Tiff._get_axis_size(im_arr, dataset.metadata.axes.replace("S", "C"), "C")) + dataset.metadata.depth = str(Tiff._get_axis_size(im_arr, dataset.metadata.axes, "Z")) + dataset.metadata.frames = str(Tiff._get_axis_size(im_arr, dataset.metadata.axes, "T")) + dataset.metadata.num_unique_values = str(len(np.unique(im_arr))) + + # Populate the "offsets" file and metadata field with open(offsets_file.get_file_name(), "w") as f: json.dump(offsets, f) dataset.metadata.offsets = offsets_file diff --git a/test/functional/tools/validation_image_metadata.xml b/test/functional/tools/validation_image_metadata.xml index c63f8ec4732e..034f962f864e 100644 --- a/test/functional/tools/validation_image_metadata.xml +++ b/test/functional/tools/validation_image_metadata.xml @@ -81,6 +81,19 @@ + + + + + + + + + + + + + @@ -106,6 +119,7 @@ + From de7b37bc9cd59e65210946f88609000e53811c98 Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Tue, 8 Oct 2024 22:33:58 +0200 Subject: [PATCH 10/24] Fix black linting --- lib/galaxy/datatypes/images.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/galaxy/datatypes/images.py b/lib/galaxy/datatypes/images.py index 2fbcbdbef90f..e10ad418555e 100644 --- a/lib/galaxy/datatypes/images.py +++ b/lib/galaxy/datatypes/images.py @@ -223,11 +223,12 @@ def set_meta( except ValueError: # Occurs if the compression of the TIFF file is unsupported im_arr = None if im_arr is not None: - dataset.metadata.width = str(Tiff._get_axis_size(im_arr, dataset.metadata.axes, "X")) - dataset.metadata.height = str(Tiff._get_axis_size(im_arr, dataset.metadata.axes, "Y")) - dataset.metadata.channels = str(Tiff._get_axis_size(im_arr, dataset.metadata.axes.replace("S", "C"), "C")) - dataset.metadata.depth = str(Tiff._get_axis_size(im_arr, dataset.metadata.axes, "Z")) - dataset.metadata.frames = str(Tiff._get_axis_size(im_arr, dataset.metadata.axes, "T")) + axes = dataset.metadata.axes.replace("S", "C") + dataset.metadata.width = str(Tiff._get_axis_size(im_arr, axes, "X")) + dataset.metadata.height = str(Tiff._get_axis_size(im_arr, axes, "Y")) + dataset.metadata.channels = str(Tiff._get_axis_size(im_arr, axes, "C")) + dataset.metadata.depth = str(Tiff._get_axis_size(im_arr, axes, "Z")) + dataset.metadata.frames = str(Tiff._get_axis_size(im_arr, axes, "T")) dataset.metadata.num_unique_values = str(len(np.unique(im_arr))) # Populate the "offsets" file and metadata field From f9251ee658f7509ce21f13d65d72588ca25f2a2a Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Tue, 8 Oct 2024 23:44:58 +0200 Subject: [PATCH 11/24] Add support for TIFF files with multiple series --- lib/galaxy/datatypes/images.py | 47 +++++++++++------- test-data/im9_multiseries.tif | Bin 0 -> 134656 bytes .../tools/validation_image_metadata.xml | 12 +++++ 3 files changed, 42 insertions(+), 17 deletions(-) create mode 100644 test-data/im9_multiseries.tif diff --git a/lib/galaxy/datatypes/images.py b/lib/galaxy/datatypes/images.py index e10ad418555e..c331739da26c 100644 --- a/lib/galaxy/datatypes/images.py +++ b/lib/galaxy/datatypes/images.py @@ -213,23 +213,36 @@ def set_meta( with tifffile.TiffFile(dataset.get_file_name()) as tif: offsets = [page.offset for page in tif.pages] - # Populate the metadata fields that should be generally available - dataset.metadata.axes = tif.series[0].axes.upper() - dataset.metadata.dtype = str(tif.series[0].dtype) - - # Populate the metadata fields that require reading the image data - try: - im_arr = tif.asarray() - except ValueError: # Occurs if the compression of the TIFF file is unsupported - im_arr = None - if im_arr is not None: - axes = dataset.metadata.axes.replace("S", "C") - dataset.metadata.width = str(Tiff._get_axis_size(im_arr, axes, "X")) - dataset.metadata.height = str(Tiff._get_axis_size(im_arr, axes, "Y")) - dataset.metadata.channels = str(Tiff._get_axis_size(im_arr, axes, "C")) - dataset.metadata.depth = str(Tiff._get_axis_size(im_arr, axes, "Z")) - dataset.metadata.frames = str(Tiff._get_axis_size(im_arr, axes, "T")) - dataset.metadata.num_unique_values = str(len(np.unique(im_arr))) + # Aggregate a list of values for each metadata field (one value for each series of the TIFF file) + metadata = { + key: [] for key in [ + "axes", "dtype", "width", "height", "channels", "depth", "frames", "num_unique_values", + ] + } + for series in tif.series: + + # Determine the metadata values that should be generally available + metadata["axes"].append(series.axes.upper()) + metadata["dtype"].append(series.dtype) + + # Determine the metadata values that require reading the image data + try: + im_arr = series.asarray() + except ValueError: # Occurs if the compression of the TIFF file is unsupported + im_arr = None + if im_arr is not None: + axes = metadata["axes"][-1].replace("S", "C") + metadata["width"].append(Tiff._get_axis_size(im_arr, axes, "X")) + metadata["height"].append(Tiff._get_axis_size(im_arr, axes, "Y")) + metadata["channels"].append(Tiff._get_axis_size(im_arr, axes, "C")) + metadata["depth"].append(Tiff._get_axis_size(im_arr, axes, "Z")) + metadata["frames"].append(Tiff._get_axis_size(im_arr, axes, "T")) + metadata["num_unique_values"].append(len(np.unique(im_arr))) + + # Populate the metadata fields based on the values determined above + for key, values in metadata.items(): + if len(values) > 0: + setattr(dataset.metadata, key, ",".join(str(value) for value in values)) # Populate the "offsets" file and metadata field with open(offsets_file.get_file_name(), "w") as f: diff --git a/test-data/im9_multiseries.tif b/test-data/im9_multiseries.tif new file mode 100644 index 0000000000000000000000000000000000000000..79df2fd85d0a41586a12fd0521b38e75b92f346b GIT binary patch literal 134656 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

Xtm{zr%K@n02(svkum;IGapY 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 + + + + + + + + + + + @@ -119,6 +130,7 @@ + From 119dc787b166879bebd1b69e6932f01b0ddabc16 Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Tue, 8 Oct 2024 23:47:14 +0200 Subject: [PATCH 12/24] Fix black linting --- lib/galaxy/datatypes/images.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/datatypes/images.py b/lib/galaxy/datatypes/images.py index c331739da26c..d8ceea87ea04 100644 --- a/lib/galaxy/datatypes/images.py +++ b/lib/galaxy/datatypes/images.py @@ -215,8 +215,16 @@ def set_meta( # Aggregate a list of values for each metadata field (one value for each series of the TIFF file) metadata = { - key: [] for key in [ - "axes", "dtype", "width", "height", "channels", "depth", "frames", "num_unique_values", + key: [] + for key in [ + "axes", + "dtype", + "width", + "height", + "channels", + "depth", + "frames", + "num_unique_values", ] } for series in tif.series: From 669e3dbd528fa79bb92d2ed302348fc31660ac93 Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Tue, 8 Oct 2024 23:50:18 +0200 Subject: [PATCH 13/24] Add type hint for mypy --- lib/galaxy/datatypes/images.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/datatypes/images.py b/lib/galaxy/datatypes/images.py index d8ceea87ea04..3674ae6fa5e2 100644 --- a/lib/galaxy/datatypes/images.py +++ b/lib/galaxy/datatypes/images.py @@ -5,7 +5,12 @@ import base64 import json import logging -from typing import Optional +from typing import ( + Any, + Dict, + List, + Optional, +) import mrcfile import numpy as np @@ -214,7 +219,7 @@ def set_meta( offsets = [page.offset for page in tif.pages] # Aggregate a list of values for each metadata field (one value for each series of the TIFF file) - metadata = { + metadata: Dict[str, List[Any]] = { key: [] for key in [ "axes", From 0f7f64c07a4221b8dbd578636471a088d6742c94 Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Wed, 9 Oct 2024 07:51:35 +0200 Subject: [PATCH 14/24] Fix tests --- test/functional/tools/validation_image_metadata.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functional/tools/validation_image_metadata.xml b/test/functional/tools/validation_image_metadata.xml index 6940be8e9ceb..ae0d5e8fac26 100644 --- a/test/functional/tools/validation_image_metadata.xml +++ b/test/functional/tools/validation_image_metadata.xml @@ -71,7 +71,7 @@ - + From 48b1808d0603dec39591857c0a09a03bea0551c6 Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Wed, 9 Oct 2024 08:28:19 +0200 Subject: [PATCH 15/24] Rename `series` -> `page` This is more consistent with the terminology used in https://github.com/cgohlke/tifffile/blob/8a25a0d4738390af0a1f693705f29875d88fc320/tifffile/tifffile.py#L4676 --- lib/galaxy/datatypes/images.py | 10 +++++----- .../{im9_multiseries.tif => im9_multipage.tif} | Bin test/functional/tools/validation_image_metadata.xml | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) rename test-data/{im9_multiseries.tif => im9_multipage.tif} (100%) diff --git a/lib/galaxy/datatypes/images.py b/lib/galaxy/datatypes/images.py index 3674ae6fa5e2..9eb6027fa2a3 100644 --- a/lib/galaxy/datatypes/images.py +++ b/lib/galaxy/datatypes/images.py @@ -218,7 +218,7 @@ def set_meta( with tifffile.TiffFile(dataset.get_file_name()) as tif: offsets = [page.offset for page in tif.pages] - # Aggregate a list of values for each metadata field (one value for each series of the TIFF file) + # Aggregate a list of values for each metadata field (one value for each page of the TIFF file) metadata: Dict[str, List[Any]] = { key: [] for key in [ @@ -232,15 +232,15 @@ def set_meta( "num_unique_values", ] } - for series in tif.series: + for page in tif.series: # Determine the metadata values that should be generally available - metadata["axes"].append(series.axes.upper()) - metadata["dtype"].append(series.dtype) + metadata["axes"].append(page.axes.upper()) + metadata["dtype"].append(page.dtype) # Determine the metadata values that require reading the image data try: - im_arr = series.asarray() + im_arr = page.asarray() except ValueError: # Occurs if the compression of the TIFF file is unsupported im_arr = None if im_arr is not None: diff --git a/test-data/im9_multiseries.tif b/test-data/im9_multipage.tif similarity index 100% rename from test-data/im9_multiseries.tif rename to test-data/im9_multipage.tif diff --git a/test/functional/tools/validation_image_metadata.xml b/test/functional/tools/validation_image_metadata.xml index ae0d5e8fac26..86213087bec0 100644 --- a/test/functional/tools/validation_image_metadata.xml +++ b/test/functional/tools/validation_image_metadata.xml @@ -81,8 +81,8 @@ - - + + @@ -130,7 +130,7 @@ - + From 611739834e5a6246440ad2dbd1ea090bb8256e72 Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Wed, 9 Oct 2024 08:45:38 +0200 Subject: [PATCH 16/24] Add test for empty TIFF file (no metadata available) --- test-data/im_empty.tif | Bin 0 -> 8 bytes test/functional/tools/validation_image_metadata.xml | 12 ++++++++++++ 2 files changed, 12 insertions(+) create mode 100644 test-data/im_empty.tif diff --git a/test-data/im_empty.tif b/test-data/im_empty.tif new file mode 100644 index 0000000000000000000000000000000000000000..997c69fe8f92889a927c5ad5de83611677d727a9 GIT binary patch literal 8 McmebD)M5Yu00m0`y#N3J literal 0 HcmV?d00001 diff --git a/test/functional/tools/validation_image_metadata.xml b/test/functional/tools/validation_image_metadata.xml index 86213087bec0..db382206f02f 100644 --- a/test/functional/tools/validation_image_metadata.xml +++ b/test/functional/tools/validation_image_metadata.xml @@ -105,6 +105,17 @@ + + + + + + + + + + + @@ -132,6 +143,7 @@ + From 685f6538dfa1aaeaac92f6a69e29e9c4590d4b03 Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Wed, 9 Oct 2024 09:21:23 +0200 Subject: [PATCH 17/24] Add test for corrupted TIFF file and fix metadata extraction for that case --- lib/galaxy/datatypes/images.py | 96 +++++++++--------- test-data/im_corrupted.tif | Bin 0 -> 4 bytes .../tools/validation_image_metadata.xml | 15 ++- 3 files changed, 61 insertions(+), 50 deletions(-) create mode 100644 test-data/im_corrupted.tif diff --git a/lib/galaxy/datatypes/images.py b/lib/galaxy/datatypes/images.py index 9eb6027fa2a3..c9d462a42b5f 100644 --- a/lib/galaxy/datatypes/images.py +++ b/lib/galaxy/datatypes/images.py @@ -215,52 +215,56 @@ def set_meta( offsets_file = dataset.metadata.spec[spec_key].param.new_file( dataset=dataset, metadata_tmp_files_dir=metadata_tmp_files_dir ) - with tifffile.TiffFile(dataset.get_file_name()) as tif: - offsets = [page.offset for page in tif.pages] - - # Aggregate a list of values for each metadata field (one value for each page of the TIFF file) - metadata: Dict[str, List[Any]] = { - key: [] - for key in [ - "axes", - "dtype", - "width", - "height", - "channels", - "depth", - "frames", - "num_unique_values", - ] - } - for page in tif.series: - - # Determine the metadata values that should be generally available - metadata["axes"].append(page.axes.upper()) - metadata["dtype"].append(page.dtype) - - # Determine the metadata values that require reading the image data - try: - im_arr = page.asarray() - except ValueError: # Occurs if the compression of the TIFF file is unsupported - im_arr = None - if im_arr is not None: - axes = metadata["axes"][-1].replace("S", "C") - metadata["width"].append(Tiff._get_axis_size(im_arr, axes, "X")) - metadata["height"].append(Tiff._get_axis_size(im_arr, axes, "Y")) - metadata["channels"].append(Tiff._get_axis_size(im_arr, axes, "C")) - metadata["depth"].append(Tiff._get_axis_size(im_arr, axes, "Z")) - metadata["frames"].append(Tiff._get_axis_size(im_arr, axes, "T")) - metadata["num_unique_values"].append(len(np.unique(im_arr))) - - # Populate the metadata fields based on the values determined above - for key, values in metadata.items(): - if len(values) > 0: - setattr(dataset.metadata, key, ",".join(str(value) for value in values)) - - # Populate the "offsets" file and metadata field - with open(offsets_file.get_file_name(), "w") as f: - json.dump(offsets, f) - dataset.metadata.offsets = offsets_file + try: + with tifffile.TiffFile(dataset.get_file_name()) as tif: + offsets = [page.offset for page in tif.pages] + + # Aggregate a list of values for each metadata field (one value for each page of the TIFF file) + metadata: Dict[str, List[Any]] = { + key: [] + for key in [ + "axes", + "dtype", + "width", + "height", + "channels", + "depth", + "frames", + "num_unique_values", + ] + } + for page in tif.series: + + # Determine the metadata values that should be generally available + metadata["axes"].append(page.axes.upper()) + metadata["dtype"].append(page.dtype) + + # Determine the metadata values that require reading the image data + try: + im_arr = page.asarray() + except ValueError: # Occurs if the compression of the TIFF file is unsupported + im_arr = None + if im_arr is not None: + axes = metadata["axes"][-1].replace("S", "C") + metadata["width"].append(Tiff._get_axis_size(im_arr, axes, "X")) + metadata["height"].append(Tiff._get_axis_size(im_arr, axes, "Y")) + metadata["channels"].append(Tiff._get_axis_size(im_arr, axes, "C")) + metadata["depth"].append(Tiff._get_axis_size(im_arr, axes, "Z")) + metadata["frames"].append(Tiff._get_axis_size(im_arr, axes, "T")) + metadata["num_unique_values"].append(len(np.unique(im_arr))) + + # Populate the metadata fields based on the values determined above + for key, values in metadata.items(): + if len(values) > 0: + setattr(dataset.metadata, key, ",".join(str(value) for value in values)) + + # Populate the "offsets" file and metadata field + with open(offsets_file.get_file_name(), "w") as f: + json.dump(offsets, f) + dataset.metadata.offsets = offsets_file + + except: # Corrupted/unsupported TIFF file structure + pass @staticmethod def _get_axis_size(im_arr: "np.typing.NDArray", axes: str, axis: str) -> int: diff --git a/test-data/im_corrupted.tif b/test-data/im_corrupted.tif new file mode 100644 index 0000000000000000000000000000000000000000..e5f09c679df4cb86e9612435d50b4c83260801dd GIT binary patch literal 4 LcmebD)M5Yt0#^XN literal 0 HcmV?d00001 diff --git a/test/functional/tools/validation_image_metadata.xml b/test/functional/tools/validation_image_metadata.xml index db382206f02f..85308415e5ec 100644 --- a/test/functional/tools/validation_image_metadata.xml +++ b/test/functional/tools/validation_image_metadata.xml @@ -92,7 +92,7 @@ - + @@ -105,8 +105,7 @@ - - + @@ -143,7 +142,6 @@ - @@ -161,6 +159,15 @@ + + + + + + + + + From 39a726bcdda81a971e0e7122a3c6c7a16c004d42 Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Wed, 9 Oct 2024 09:45:12 +0200 Subject: [PATCH 18/24] Fix linting --- lib/galaxy/datatypes/images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/datatypes/images.py b/lib/galaxy/datatypes/images.py index c9d462a42b5f..4808cffa20f4 100644 --- a/lib/galaxy/datatypes/images.py +++ b/lib/galaxy/datatypes/images.py @@ -263,7 +263,7 @@ def set_meta( json.dump(offsets, f) dataset.metadata.offsets = offsets_file - except: # Corrupted/unsupported TIFF file structure + except: # noqa: E722 # For arbitrary errors from deep inside the tifffile library pass @staticmethod From 4510f2747fc7358d9ceb4a4e475a9222270e09c5 Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Wed, 9 Oct 2024 10:13:32 +0200 Subject: [PATCH 19/24] Fix linting --- lib/galaxy/datatypes/images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/datatypes/images.py b/lib/galaxy/datatypes/images.py index 4808cffa20f4..6e27ea46275b 100644 --- a/lib/galaxy/datatypes/images.py +++ b/lib/galaxy/datatypes/images.py @@ -263,7 +263,7 @@ def set_meta( json.dump(offsets, f) dataset.metadata.offsets = offsets_file - except: # noqa: E722 # For arbitrary errors from deep inside the tifffile library + except BaseException: # For arbitrary errors from deep inside the tifffile library pass @staticmethod From b199061f9bb7af1662ee00a93b3c09a5df561ff5 Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Wed, 9 Oct 2024 11:24:50 +0200 Subject: [PATCH 20/24] Fix linting --- lib/galaxy/datatypes/images.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/galaxy/datatypes/images.py b/lib/galaxy/datatypes/images.py index 6e27ea46275b..45d96a8670d1 100644 --- a/lib/galaxy/datatypes/images.py +++ b/lib/galaxy/datatypes/images.py @@ -263,7 +263,20 @@ def set_meta( json.dump(offsets, f) dataset.metadata.offsets = offsets_file - except BaseException: # For arbitrary errors from deep inside the tifffile library + # Catch errors from deep inside the tifffile library + except ( + AttributeError, + IndexError, + KeyError, + NotImplementedError, + OSError, + PermissionError, + RuntimeError, + tifffile.OmeXmlError, + tifffile.TiffFileError, + TypeError, + ValueError, + ): pass @staticmethod From f2c874f3cf1aa9c4ea2fffb210d5b46ac38a9cc6 Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Wed, 9 Oct 2024 11:31:38 +0200 Subject: [PATCH 21/24] Fix linting --- lib/galaxy/datatypes/images.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/galaxy/datatypes/images.py b/lib/galaxy/datatypes/images.py index 45d96a8670d1..7d3522308ccd 100644 --- a/lib/galaxy/datatypes/images.py +++ b/lib/galaxy/datatypes/images.py @@ -268,9 +268,7 @@ def set_meta( AttributeError, IndexError, KeyError, - NotImplementedError, OSError, - PermissionError, RuntimeError, tifffile.OmeXmlError, tifffile.TiffFileError, From 1e6701f62129b69bf219279de83f4b947e53eb8f Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Wed, 9 Oct 2024 16:09:19 +0200 Subject: [PATCH 22/24] Reduce utilization of full image data --- lib/galaxy/datatypes/images.py | 48 +++++++++++-------- .../tools/validation_image_metadata.xml | 15 +++--- 2 files changed, 37 insertions(+), 26 deletions(-) diff --git a/lib/galaxy/datatypes/images.py b/lib/galaxy/datatypes/images.py index 7d3522308ccd..1c111a106d85 100644 --- a/lib/galaxy/datatypes/images.py +++ b/lib/galaxy/datatypes/images.py @@ -10,6 +10,7 @@ Dict, List, Optional, + Tuple, ) import mrcfile @@ -159,19 +160,26 @@ def set_meta( if PIL is not None: try: with PIL.Image.open(dataset.get_file_name()) as im: - im_arr = np.array(im) - dataset.metadata.dtype = str(im_arr.dtype) - dataset.metadata.num_unique_values = str(len(np.unique(im))) - dataset.metadata.width = str(im_arr.shape[1]) - dataset.metadata.height = str(im_arr.shape[0]) + + # Determine the metadata values that are available without loading the image data + dataset.metadata.width = str(im.size[1]) + dataset.metadata.height = str(im.size[0]) dataset.metadata.depth = "0" - dataset.metadata.frames = "0" - if im_arr.ndim == 2: + dataset.metadata.frames = str(getattr(im, 'n_frames', 0)) + dataset.metadata.num_unique_values = str(sum(val > 0 for val in im.histogram())) + + # Peek into a small 2x2 section of the image data + im_peek_arr = np.array(im.crop((0, 0, min((2, im.size[1])), min((2, im.size[0]))))) + + # Determine the remaining metadata values + dataset.metadata.dtype = str(im_peek_arr.dtype) + if im_peek_arr.ndim == 2: dataset.metadata.axes = "YX" dataset.metadata.channels = "0" - elif im_arr.ndim == 3: + elif im_peek_arr.ndim == 3: dataset.metadata.axes = "YXC" - dataset.metadata.channels = str(im_arr.shape[2]) + dataset.metadata.channels = str(im_peek_arr.shape[2]) + except PIL.UnidentifiedImageError: pass @@ -239,19 +247,19 @@ def set_meta( metadata["axes"].append(page.axes.upper()) metadata["dtype"].append(page.dtype) + axes = metadata["axes"][-1].replace("S", "C") + metadata["width"].append(Tiff._get_axis_size(page.shape, axes, "X")) + metadata["height"].append(Tiff._get_axis_size(page.shape, axes, "Y")) + metadata["channels"].append(Tiff._get_axis_size(page.shape, axes, "C")) + metadata["depth"].append(Tiff._get_axis_size(page.shape, axes, "Z")) + metadata["frames"].append(Tiff._get_axis_size(page.shape, axes, "T")) + # Determine the metadata values that require reading the image data try: im_arr = page.asarray() - except ValueError: # Occurs if the compression of the TIFF file is unsupported - im_arr = None - if im_arr is not None: - axes = metadata["axes"][-1].replace("S", "C") - metadata["width"].append(Tiff._get_axis_size(im_arr, axes, "X")) - metadata["height"].append(Tiff._get_axis_size(im_arr, axes, "Y")) - metadata["channels"].append(Tiff._get_axis_size(im_arr, axes, "C")) - metadata["depth"].append(Tiff._get_axis_size(im_arr, axes, "Z")) - metadata["frames"].append(Tiff._get_axis_size(im_arr, axes, "T")) metadata["num_unique_values"].append(len(np.unique(im_arr))) + except ValueError: # Occurs if the compression of the TIFF file is unsupported + pass # Populate the metadata fields based on the values determined above for key, values in metadata.items(): @@ -278,9 +286,9 @@ def set_meta( pass @staticmethod - def _get_axis_size(im_arr: "np.typing.NDArray", axes: str, axis: str) -> int: + def _get_axis_size(shape: Tuple[int, ...], axes: str, axis: str) -> int: idx = axes.find(axis) - return im_arr.shape[idx] if idx >= 0 else 0 + return shape[idx] if idx >= 0 else 0 class OMETiff(Tiff): diff --git a/test/functional/tools/validation_image_metadata.xml b/test/functional/tools/validation_image_metadata.xml index 85308415e5ec..1e01a4556bb1 100644 --- a/test/functional/tools/validation_image_metadata.xml +++ b/test/functional/tools/validation_image_metadata.xml @@ -78,6 +78,9 @@ + + + @@ -97,13 +100,13 @@ + + + + + - - - - - @@ -156,7 +159,7 @@ - + From 6459225941101274b42a62b7d53b843b0484774a Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Wed, 9 Oct 2024 16:22:08 +0200 Subject: [PATCH 23/24] `make format` --- lib/galaxy/datatypes/images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/galaxy/datatypes/images.py b/lib/galaxy/datatypes/images.py index 1c111a106d85..f46c3bad63f1 100644 --- a/lib/galaxy/datatypes/images.py +++ b/lib/galaxy/datatypes/images.py @@ -165,7 +165,7 @@ def set_meta( dataset.metadata.width = str(im.size[1]) dataset.metadata.height = str(im.size[0]) dataset.metadata.depth = "0" - dataset.metadata.frames = str(getattr(im, 'n_frames', 0)) + dataset.metadata.frames = str(getattr(im, "n_frames", 0)) dataset.metadata.num_unique_values = str(sum(val > 0 for val in im.histogram())) # Peek into a small 2x2 section of the image data From f3e20d9018357b4c4ea3badc60241adbb11f6287 Mon Sep 17 00:00:00 2001 From: Leonid Kostrykin Date: Thu, 10 Oct 2024 11:13:44 +0200 Subject: [PATCH 24/24] Fix for corrupted TIF images --- lib/galaxy/datatypes/images.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/galaxy/datatypes/images.py b/lib/galaxy/datatypes/images.py index f46c3bad63f1..fa871b2f7041 100644 --- a/lib/galaxy/datatypes/images.py +++ b/lib/galaxy/datatypes/images.py @@ -5,6 +5,7 @@ import base64 import json import logging +import struct from typing import ( Any, Dict, @@ -278,6 +279,7 @@ def set_meta( KeyError, OSError, RuntimeError, + struct.error, tifffile.OmeXmlError, tifffile.TiffFileError, TypeError,