diff --git a/CHANGELOG.md b/CHANGELOG.md index 30c47468f..625bf8ab9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Better report if rasterized vector files are geospatial ([#1769](../../pull/1769)) - Provide some latitude in vips multiframe detection ([#1770](../../pull/1770)) +- Don't read multiplane ndpi files with openslide ([#1772](../../pull/1772)) ## 1.30.6 diff --git a/sources/openslide/large_image_source_openslide/__init__.py b/sources/openslide/large_image_source_openslide/__init__.py index 1750fd5b4..9c3bdedc2 100644 --- a/sources/openslide/large_image_source_openslide/__init__.py +++ b/sources/openslide/large_image_source_openslide/__init__.py @@ -94,6 +94,13 @@ def __init__(self, path, **kwargs): # noqa tifftools.Tag.ICCProfile.value]['data']] except Exception: pass + if hasattr(self, '_tiffinfo'): + for ifd in self._tiffinfo['ifds']: + if (tifftools.Tag.NDPI_FOCAL_PLANE.value in ifd['tags'] and + ifd['tags'][tifftools.Tag.NDPI_FOCAL_PLANE.value]['data'][0] != 0): + msg = ('File will not be opened via OpenSlide; ' + 'non-zero focal planes would be missed.') + raise TileSourceError(msg) svsAvailableLevels = self._getAvailableLevels(self._largeImagePath) if not len(svsAvailableLevels): diff --git a/sources/tifffile/large_image_source_tifffile/__init__.py b/sources/tifffile/large_image_source_tifffile/__init__.py index 420a845c2..dc4203a42 100644 --- a/sources/tifffile/large_image_source_tifffile/__init__.py +++ b/sources/tifffile/large_image_source_tifffile/__init__.py @@ -430,6 +430,20 @@ def _handle_svs(self): except Exception: pass + def _handle_internal_ndpi(self, intmeta): + try: + ndpi = intmeta.pop('65449') + intmeta['ndpi'] = {} + for line in ndpi.replace('\r', '\n').split('\n'): + if '=' in line: + key, value = line.split('=', 1) + key = key.strip() + value = value.strip() + if key and key not in intmeta['ndpi'] and value: + intmeta['ndpi'][key] = value + except Exception: + pass + def getNativeMagnification(self): """ Get the magnification at a particular level. @@ -499,6 +513,10 @@ def getInternalMetadata(self, **kwargs): json.dumps(result[key][subkey]) except Exception: del result[key][subkey] + for key in dir(self._tf): + if (key.startswith('is_') and hasattr(self, '_handle_internal_' + key[3:]) and + getattr(self._tf, key)): + getattr(self, '_handle_internal_' + key[3:])(result) if hasattr(self, '_xml') and 'xml' not in result: result.pop('ImageDescription', None) result['xml'] = self._xml diff --git a/test/datastore.py b/test/datastore.py index cdf940a26..af5e9b624 100644 --- a/test/datastore.py +++ b/test/datastore.py @@ -113,7 +113,9 @@ '18f6378f-433c-42bf-9373-1ff9c808c118.dcm': 'sha512:36432183380eb7d44417a2210a19d550527abd1181255e19ed5c1d17695d8bb8ca42f5b426a63fa73b84e0e17b770401a377ae0c705d0ed7fdf30d571ef60e2d', # noqa 'a131592c-a069-4aa7-8031-398654aa8a3d.dcm': 'sha512:99bd3da4b8e11ce7b4f7ed8a294ed0c37437320667a06c40c383f4b29be85fe8e6094043e0600bee0ba879f2401de4c57285800a4a23da2caf2eb94e5b847ee0', # noqa # Synthetic newer ndpi with binary data and nonblank image labelled as RGB - 'synthetic_ndpi_2025.ndpi': 'sha512:28752f35790b62fb712eb93f370be4e2f44799d44d6bb1f34fea9d3a6f01c7b49b2c510fc34ebaa829cb1beef0bc5b12d26a4cb3060835ff49aada7d99f61e44', # noqa + 'synthetic_ndpi_2025.ndpi': 'sha512:b9b2c420cde9fd988786afc02efb761a7d425ce542cc68f10f5878bdc7177d61952e0d52508501c82d5664a07a87e7486ec4bb0b6634c556400cfc91fc3f52ec', # noqa + # Synthetic ndpi with multiple focal planes + 'synthetic_ndpi_multiplane_2025.ndpi': 'sha512:1025d6ddd74070d0bb2c3ab398bc5e6ae05390651f84df22c37e3dbe547bb16b4eba0a0a3cefc7ac824ff2e87320782b676b2049a602bc0d5dbb105bbc94e888', # noqa # Synthetic uint16 untiled tiff that can be read with the tiff source 'synthetic_untiled_16.tiff': 'sha512:f4773fcfa749ba9c2db25319c9e8ad8586dd148de4366dae0393a3703906dace9f11233eafdb24418b598170d6372ef1ca861bf8d7a8212cac21a0eb8636ee77', # noqa # DICOM with int16 data diff --git a/test/test_source_base.py b/test/test_source_base.py index 7a689531f..92c33621e 100644 --- a/test/test_source_base.py +++ b/test/test_source_base.py @@ -63,7 +63,7 @@ 'openjpeg': {'read': r'\.(jp2)$'}, 'openslide': { 'read': r'\.(ptif|svs|ndpi|tif.*|qptiff|dcm)$', - 'noread': r'(oahu|DDX58_AXL|huron\.image2_jpeg2k|landcover_sample|d042-353\.crop|US_Geo\.|extraoverview|imagej|bad_axes|synthetic_untiled|indica|tcia.*dcm)', # noqa + 'noread': r'(oahu|DDX58_AXL|huron\.image2_jpeg2k|landcover_sample|d042-353\.crop|US_Geo\.|extraoverview|imagej|bad_axes|synthetic_untiled|indica|tcia.*dcm|multiplane.*ndpi)', # noqa 'skip': r'nokeyframe\.ome\.tiff|TCGA-55.*\.ome\.tiff$', 'skipTiles': r'one_layer_missing', },