From f161c33724eb1e89699e55b30185f9fd53d3e699 Mon Sep 17 00:00:00 2001 From: David Manthey Date: Wed, 22 Jul 2020 13:21:38 -0400 Subject: [PATCH 1/5] Support reading OME Tiff files from sub-ifds. I anticipate generalizing this to all tiff files that are so constructed eventually. --- .../large_image_source_ometiff/__init__.py | 29 +++++++++++------ .../tiff/large_image_source_tiff/__init__.py | 17 ++++++++++ .../large_image_source_tiff/tiff_reader.py | 32 ++++++++++++++----- 3 files changed, 60 insertions(+), 18 deletions(-) diff --git a/sources/ometiff/large_image_source_ometiff/__init__.py b/sources/ometiff/large_image_source_ometiff/__init__.py index efd261c68..3cb9eac5c 100644 --- a/sources/ometiff/large_image_source_ometiff/__init__.py +++ b/sources/ometiff/large_image_source_ometiff/__init__.py @@ -282,23 +282,32 @@ def getNativeMagnification(self): @methodcache() def getTile(self, x, y, z, pilImageAllowed=False, numpyAllowed=False, sparseFallback=False, **kwargs): - if (z < 0 or z >= len(self._omeLevels) or self._omeLevels[z] is None or - kwargs.get('frame') in (None, 0, '0', '')): + if (z < 0 or z >= len(self._omeLevels) or ( + self._omeLevels[z] is not None and kwargs.get('frame') in (None, 0, '0', ''))): return super(OMETiffFileTileSource, self).getTile( x, y, z, pilImageAllowed=pilImageAllowed, numpyAllowed=numpyAllowed, sparseFallback=sparseFallback, **kwargs) - frame = int(kwargs['frame']) + frame = int(kwargs.get('frame') or 0) if frame < 0 or frame >= len(self._omebase['TiffData']): raise TileSourceException('Frame does not exist') - dirnum = int(self._omeLevels[z]['TiffData'][frame].get('IFD', frame)) - if dirnum in self._directoryCache: - dir = self._directoryCache[dirnum] + subdir = None + if self._omeLevels[z] is not None: + dirnum = int(self._omeLevels[z]['TiffData'][frame].get('IFD', frame)) else: - if len(self._directoryCache) >= self._directoryCacheMaxSize: - self._directoryCache = {} - dir = TiledTiffDirectory(self._getLargeImagePath(), dirnum, mustBeTiled=None) - self._directoryCache[dirnum] = dir + dirnum = int(self._omeLevels[-1]['TiffData'][frame].get('IFD', frame)) + subdir = self.levels - 1 - z + dir = self._getDirFromCache(dirnum, subdir) + if subdir: + scale = int(2 ** subdir) + if (dir is None or + dir.tileWidth != self.tileWidth or dir.tileHeight != self.tileHeight or + abs(dir.imageWidth * scale - self.sizeX) > scale or + abs(dir.imageHeight * scale - self.sizeY) > scale): + return super(OMETiffFileTileSource, self).getTile( + x, y, z, pilImageAllowed=pilImageAllowed, + numpyAllowed=numpyAllowed, sparseFallback=sparseFallback, + **kwargs) try: tile = dir.getTile(x, y) format = 'JPEG' diff --git a/sources/tiff/large_image_source_tiff/__init__.py b/sources/tiff/large_image_source_tiff/__init__.py index c190be69e..ed571de38 100644 --- a/sources/tiff/large_image_source_tiff/__init__.py +++ b/sources/tiff/large_image_source_tiff/__init__.py @@ -349,6 +349,23 @@ def getTile(self, x, y, z, pilImageAllowed=False, numpyAllowed=False, numpyAllowed=numpyAllowed, sparseFallback=sparseFallback, exception=e, **kwargs) + def _getDirFromCache(self, dirnum, subdir=None): + if not hasattr(self, '_directoryCache'): + self._directoryCache = {} + self._directoryCacheMaxSize = max(20, self.levels * 3) + key = (dirnum, subdir) + result = self._directoryCache.get(key) + if result is None: + if len(self._directoryCache) >= self._directoryCacheMaxSize: + self._directoryCache = {} + try: + result = TiledTiffDirectory( + self._getLargeImagePath(), dirnum, mustBeTiled=None, subDirectoryNum=subdir) + except IOTiffException: + result = None + self._directoryCache[key] = result + return result + def getTileIOTiffException(self, x, y, z, pilImageAllowed=False, numpyAllowed=False, sparseFallback=False, exception=None, **kwargs): diff --git a/sources/tiff/large_image_source_tiff/tiff_reader.py b/sources/tiff/large_image_source_tiff/tiff_reader.py index 51d3dacb6..9ae6fc701 100644 --- a/sources/tiff/large_image_source_tiff/tiff_reader.py +++ b/sources/tiff/large_image_source_tiff/tiff_reader.py @@ -99,21 +99,23 @@ class ValidationTiffException(TiffException): class TiledTiffDirectory(object): CoreFunctions = [ - 'SetDirectory', 'GetField', 'LastDirectory', 'GetMode', 'IsTiled', - 'IsByteSwapped', 'IsUpSampled', 'IsMSB2LSB', 'NumberOfStrips' + 'SetDirectory', 'SetSubDirectory', 'GetField', + 'LastDirectory', 'GetMode', 'IsTiled', 'IsByteSwapped', 'IsUpSampled', + 'IsMSB2LSB', 'NumberOfStrips', ] - def __init__(self, filePath, directoryNum, mustBeTiled=True): + def __init__(self, filePath, directoryNum, mustBeTiled=True, subDirectoryNum=0): """ Create a new reader for a tiled image file directory in a TIFF file. :param filePath: A path to a TIFF file on disk. :type filePath: str :param directoryNum: The number of the TIFF image file directory to - open. + open. :type directoryNum: int :param mustBeTiled: if True, only tiled images validate. If False, only non-tiled images validate. None validates both. + :param subDirectoryNum: if set, the number of the TIFF subdirectory. :raises: InvalidOperationTiffException or IOTiffException or ValidationTiffException """ @@ -127,10 +129,10 @@ def __init__(self, filePath, directoryNum, mustBeTiled=True): self._tiffFile = None self._tileLock = threading.RLock() - self._open(filePath, directoryNum) + self._open(filePath, directoryNum, subDirectoryNum) self._loadMetadata() config.getConfig('logger').debug( - 'TiffDirectory %d Information %r', directoryNum, self._tiffInfo) + 'TiffDirectory %d:%d Information %r', directoryNum, subDirectoryNum, self._tiffInfo) try: self._validate() except ValidationTiffException: @@ -140,7 +142,7 @@ def __init__(self, filePath, directoryNum, mustBeTiled=True): def __del__(self): self._close() - def _open(self, filePath, directoryNum): + def _open(self, filePath, directoryNum, subDirectoryNum=0): """ Open a TIFF file to a given file and IFD number. @@ -148,6 +150,8 @@ def _open(self, filePath, directoryNum): :type filePath: str :param directoryNum: The number of the TIFF IFD to be used. :type directoryNum: int + :param subDirectoryNum: The number of the TIFF sub-IFD to be used. + :type subDirectoryNum: int :raises: InvalidOperationTiffException or IOTiffException """ self._close() @@ -176,6 +180,18 @@ def _open(self, filePath, directoryNum): self._tiffFile.close() raise IOTiffException( 'Could not set TIFF directory to %d' % directoryNum) + self._subDirectoryNum = subDirectoryNum + if self._subDirectoryNum: + subifds = self._tiffFile.GetField('subifd') + if (subifds is None or self._subDirectoryNum < 1 or + self._subDirectoryNum > len(subifds)): + raise IOTiffException( + 'Could not set TIFF subdirectory to %d' % subDirectoryNum) + subifd = subifds[self._subDirectoryNum - 1] + if self._tiffFile.SetSubDirectory(subifd) != 1: + self._tiffFile.close() + raise IOTiffException( + 'Could not set TIFF subdirectory to %d' % subDirectoryNum) def _close(self): if self._tiffFile: @@ -277,7 +293,7 @@ def _loadMetadata(self): 'Loading field "%s" in directory number %d resulted in TypeError - "%s"', field, self._directoryNum, err) - for func in self.CoreFunctions[2:]: + for func in self.CoreFunctions[3:]: if hasattr(self._tiffFile, func): value = getattr(self._tiffFile, func)() if value: From 3a375c3ea014a6e7dafddade1d4a0ff2c144772f Mon Sep 17 00:00:00 2001 From: David Manthey Date: Thu, 23 Jul 2020 08:33:14 -0400 Subject: [PATCH 2/5] Add a test case. --- test/data/sample.subifd.ome.tif.sha512 | 1 + test/test_source_ometiff.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100755 test/data/sample.subifd.ome.tif.sha512 diff --git a/test/data/sample.subifd.ome.tif.sha512 b/test/data/sample.subifd.ome.tif.sha512 new file mode 100755 index 000000000..e4774c67d --- /dev/null +++ b/test/data/sample.subifd.ome.tif.sha512 @@ -0,0 +1 @@ +35ec252c94b1ad0b9d5bd42c89c1d15c83065d6734100d6f596237ff36e8d4495bcfed2c9ea24ab0b4a35aef59871da429dbd48faf0232219dc4391215ba59ce diff --git a/test/test_source_ometiff.py b/test/test_source_ometiff.py index e02761bd0..2dbd267d9 100644 --- a/test/test_source_ometiff.py +++ b/test/test_source_ometiff.py @@ -29,6 +29,24 @@ def testTilesFromOMETiff(): utilities.checkTilesZXY(source, tileMetadata) +def testTilesFromOMETiffWithSubIFD(): + imagePath = utilities.externaldata('data/sample.subifd.ome.tif.sha512') + source = large_image_source_ometiff.OMETiffFileTileSource(imagePath) + tileMetadata = source.getMetadata() + + assert tileMetadata['tileWidth'] == 256 + assert tileMetadata['tileHeight'] == 256 + assert tileMetadata['sizeX'] == 2106 + assert tileMetadata['sizeY'] == 2016 + assert tileMetadata['levels'] == 5 + assert len(tileMetadata['frames']) == 3 + assert tileMetadata['frames'][1]['Frame'] == 1 + assert tileMetadata['frames'][1]['Index'] == 0 + assert tileMetadata['frames'][1]['IndexC'] == 1 + assert tileMetadata['IndexRange'] == {'IndexC': 3} + utilities.checkTilesZXY(source, tileMetadata) + + def testTilesFromStripOMETiff(): imagePath = utilities.externaldata('data/DDX58_AXL_EGFR_well2_XY01.ome.tif.sha512') source = large_image_source_ometiff.OMETiffFileTileSource(imagePath) From b6cacfdea66b3b2c2a705a2de265bf17116699f2 Mon Sep 17 00:00:00 2001 From: David Manthey Date: Thu, 23 Jul 2020 09:20:53 -0400 Subject: [PATCH 3/5] Remove some duplicate code. --- sources/ometiff/large_image_source_ometiff/__init__.py | 2 -- test/test_source_ometiff.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/sources/ometiff/large_image_source_ometiff/__init__.py b/sources/ometiff/large_image_source_ometiff/__init__.py index 3cb9eac5c..1e1e29333 100644 --- a/sources/ometiff/large_image_source_ometiff/__init__.py +++ b/sources/ometiff/large_image_source_ometiff/__init__.py @@ -122,8 +122,6 @@ def __init__(self, path, **kwargs): TiledTiffDirectory(largeImagePath, 0, mustBeTiled=None) if entry else None for entry in self._omeLevels] - self._directoryCache = {} - self._directoryCacheMaxSize = max(20, len(self._omebase['TiffData']) * 3) self.tileWidth = base.tileWidth self.tileHeight = base.tileHeight self.levels = len(self._tiffDirectories) diff --git a/test/test_source_ometiff.py b/test/test_source_ometiff.py index 2dbd267d9..3b0b0e074 100644 --- a/test/test_source_ometiff.py +++ b/test/test_source_ometiff.py @@ -31,7 +31,7 @@ def testTilesFromOMETiff(): def testTilesFromOMETiffWithSubIFD(): imagePath = utilities.externaldata('data/sample.subifd.ome.tif.sha512') - source = large_image_source_ometiff.OMETiffFileTileSource(imagePath) + source = large_image_source_ometiff.OMETiffFileTileSource(imagePath, frame=1) tileMetadata = source.getMetadata() assert tileMetadata['tileWidth'] == 256 From 1adba5ca74233f9dd87c3b492877022245bc083d Mon Sep 17 00:00:00 2001 From: David Manthey Date: Fri, 24 Jul 2020 08:31:37 -0400 Subject: [PATCH 4/5] Speed up scanning tiff files. This especially speeds up scanning OME Tiff files that we can't ultimately read and have a lot of images. --- .../tiff/large_image_source_tiff/__init__.py | 90 ++++++++++++------- .../large_image_source_tiff/tiff_reader.py | 16 ++-- test/test_cached_tiles.py | 10 +-- 3 files changed, 71 insertions(+), 45 deletions(-) diff --git a/sources/tiff/large_image_source_tiff/__init__.py b/sources/tiff/large_image_source_tiff/__init__.py index ed571de38..a2ddb9b90 100644 --- a/sources/tiff/large_image_source_tiff/__init__.py +++ b/sources/tiff/large_image_source_tiff/__init__.py @@ -83,41 +83,12 @@ def __init__(self, path, **kwargs): super(TiffFileTileSource, self).__init__(path, **kwargs) largeImagePath = self._getLargeImagePath() - lastException = None - # Associated images are smallish TIFF images that have an image - # description and are not tiled. They have their own TIFF directory. - # Individual TIFF images can also have images embedded into their - # directory as tags (this is a vendor-specific method of adding more - # images into a file) -- those are stored in the individual - # directories' _embeddedImages field. - self._associatedImages = {} + try: + alldir = self._scanDirectories() + except (ValidationTiffException, TiffException) as exc: + alldir = [] + lastException = exc - # Query all know directories in the tif file. Only keep track of - # directories that contain tiled images. - alldir = [] - for directoryNum in itertools.count(): # pragma: no branch - try: - td = TiledTiffDirectory(largeImagePath, directoryNum) - except ValidationTiffException as exc: - lastException = exc - self._addAssociatedImage(largeImagePath, directoryNum) - continue - except TiffException as exc: - if not lastException: - lastException = exc - break - if not td.tileWidth or not td.tileHeight: - continue - # Calculate the tile level, where 0 is a single tile, 1 is up to a - # set of 2x2 tiles, 2 is 4x4, etc. - level = int(math.ceil(math.log(max( - float(td.imageWidth) / td.tileWidth, - float(td.imageHeight) / td.tileHeight)) / math.log(2))) - if level < 0: - continue - # Store information for sorting with the directory. - alldir.append((level > 0, td.tileWidth * td.tileHeight, level, - td.imageWidth * td.imageHeight, directoryNum, td)) # If there are no tiled images, raise an exception. if not len(alldir): msg = "File %s didn't meet requirements for tile source: %s" % ( @@ -162,6 +133,57 @@ def __init__(self, path, **kwargs): self.sizeX = highest.imageWidth self.sizeY = highest.imageHeight + def _scanDirectories(self): + largeImagePath = self._getLargeImagePath() + lastException = None + # Associated images are smallish TIFF images that have an image + # description and are not tiled. They have their own TIFF directory. + # Individual TIFF images can also have images embedded into their + # directory as tags (this is a vendor-specific method of adding more + # images into a file) -- those are stored in the individual + # directories' _embeddedImages field. + self._associatedImages = {} + + dir = None + # Query all know directories in the tif file. Only keep track of + # directories that contain tiled images. + alldir = [] + associatedDirs = [] + for directoryNum in itertools.count(): # pragma: no branch + try: + if dir is None: + dir = TiledTiffDirectory(largeImagePath, directoryNum, validate=False) + else: + dir._setDirectory(directoryNum) + dir._loadMetadata() + dir._validate() + except ValidationTiffException as exc: + lastException = exc + associatedDirs.append(directoryNum) + continue + except TiffException as exc: + if not lastException: + lastException = exc + break + if not dir.tileWidth or not dir.tileHeight: + continue + # Calculate the tile level, where 0 is a single tile, 1 is up to a + # set of 2x2 tiles, 2 is 4x4, etc. + level = int(math.ceil(math.log(max( + float(dir.imageWidth) / dir.tileWidth, + float(dir.imageHeight) / dir.tileHeight)) / math.log(2))) + if level < 0: + continue + td, dir = dir, None + # Store information for sorting with the directory. + alldir.append((level > 0, td.tileWidth * td.tileHeight, level, + td.imageWidth * td.imageHeight, directoryNum, td)) + if not alldir and lastException: + raise lastException + for directoryNum in associatedDirs: + self._addAssociatedImage(largeImagePath, directoryNum) + return alldir + def _addAssociatedImage(self, largeImagePath, directoryNum, mustBeTiled=False, topImage=None): """ Check if the specified TIFF directory contains an image with a sensible diff --git a/sources/tiff/large_image_source_tiff/tiff_reader.py b/sources/tiff/large_image_source_tiff/tiff_reader.py index 9ae6fc701..f11231571 100644 --- a/sources/tiff/large_image_source_tiff/tiff_reader.py +++ b/sources/tiff/large_image_source_tiff/tiff_reader.py @@ -104,7 +104,7 @@ class TiledTiffDirectory(object): 'IsMSB2LSB', 'NumberOfStrips', ] - def __init__(self, filePath, directoryNum, mustBeTiled=True, subDirectoryNum=0): + def __init__(self, filePath, directoryNum, mustBeTiled=True, subDirectoryNum=0, validate=True): """ Create a new reader for a tiled image file directory in a TIFF file. @@ -115,14 +115,15 @@ def __init__(self, filePath, directoryNum, mustBeTiled=True, subDirectoryNum=0): :type directoryNum: int :param mustBeTiled: if True, only tiled images validate. If False, only non-tiled images validate. None validates both. + :type mustBeTiled: bool :param subDirectoryNum: if set, the number of the TIFF subdirectory. + :type subDirectoryNum: int + :param validate: if False, don't validate that images can be read. + :type mustBeTiled: bool :raises: InvalidOperationTiffException or IOTiffException or ValidationTiffException """ - # TODO how many to keep in the cache - # create local cache to store Jpeg tables and - # getTileByteCountsType - + # create local cache to store Jpeg tables and getTileByteCountsType self.cache = LRUCache(10) self._mustBeTiled = mustBeTiled @@ -134,7 +135,8 @@ def __init__(self, filePath, directoryNum, mustBeTiled=True, subDirectoryNum=0): config.getConfig('logger').debug( 'TiffDirectory %d:%d Information %r', directoryNum, subDirectoryNum, self._tiffInfo) try: - self._validate() + if validate: + self._validate() except ValidationTiffException: self._close() raise @@ -174,7 +176,9 @@ def _open(self, filePath, directoryNum, subDirectoryNum=0): hasattr(self._tiffFile, func.lower())): setattr(self._tiffFile, func, getattr( self._tiffFile, func.lower())) + self._setDirectory(directoryNum, subDirectoryNum) + def _setDirectory(self, directoryNum, subDirectoryNum=0): self._directoryNum = directoryNum if self._tiffFile.SetDirectory(self._directoryNum) != 1: self._tiffFile.close() diff --git a/test/test_cached_tiles.py b/test/test_cached_tiles.py index d094d527c..87ad6652f 100644 --- a/test/test_cached_tiles.py +++ b/test/test_cached_tiles.py @@ -132,20 +132,20 @@ def countInit(*args, **kwargs): self.delCount = 0 source = large_image.getTileSource(imagePath) assert source is not None - assert self.initCount == 14 - assert self.delCount < 14 + assert self.initCount == 12 + assert self.delCount < 12 # Create another source; we shouldn't init it again, as it should be # cached. source = large_image.getTileSource(imagePath) assert source is not None - assert self.initCount == 14 - assert self.delCount < 14 + assert self.initCount == 12 + assert self.delCount < 12 source = None # Clear the cache to free references and force garbage collection cachesClear() gc.collect(2) cachesClear() - assert self.delCount == 14 + assert self.delCount == 12 class TestMemcachedCache(LargeImageCachedTilesTest): From 3e24804651246542dc8eb8552db8fda13b698487 Mon Sep 17 00:00:00 2001 From: David Manthey Date: Wed, 29 Jul 2020 16:02:54 -0400 Subject: [PATCH 5/5] Read more OME Tiff files. --- .../large_image_source_ometiff/__init__.py | 24 +++++++++++++------ .../large_image_source_tiff/tiff_reader.py | 3 ++- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/sources/ometiff/large_image_source_ometiff/__init__.py b/sources/ometiff/large_image_source_ometiff/__init__.py index 1e1e29333..d93eff6f7 100644 --- a/sources/ometiff/large_image_source_ometiff/__init__.py +++ b/sources/ometiff/large_image_source_ometiff/__init__.py @@ -114,7 +114,7 @@ def __init__(self, path, **kwargs): self._omeLevels = [omebylevel.get(key) for key in range(max(omebylevel.keys()) + 1)] if base._tiffInfo.get('istiled'): self._tiffDirectories = [ - TiledTiffDirectory(largeImagePath, int(entry['TiffData'][0]['IFD'])) + TiledTiffDirectory(largeImagePath, int(entry['TiffData'][0].get('IFD', 0))) if entry else None for entry in self._omeLevels] else: @@ -182,7 +182,7 @@ def _checkForOMEZLoop(self, largeImagePath): info['Image']['Pixels']['PlanesFromZloop'] = 'true' info['Image']['Pixels']['SizeZ'] = str(zloop) - def _parseOMEInfo(self): + def _parseOMEInfo(self): # noqa if isinstance(self._omeinfo['Image'], dict): self._omeinfo['Image'] = [self._omeinfo['Image']] for img in self._omeinfo['Image']: @@ -190,20 +190,28 @@ def _parseOMEInfo(self): img['Pixels']['TiffData'] = [img['Pixels']['TiffData']] if isinstance(img['Pixels'].get('Plane'), dict): img['Pixels']['Plane'] = [img['Pixels']['Plane']] + if isinstance(img['Pixels'].get('Channels'), dict): + img['Pixels']['Channels'] = [img['Pixels']['Channels']] try: self._omebase = self._omeinfo['Image'][0]['Pixels'] + if isinstance(self._omebase.get('Plane'), dict): + self._omebase['Plane'] = [self._omebase['Plane']] if ((not len(self._omebase['TiffData']) or len(self._omebase['TiffData']) == 1) and - len(self._omebase['Plane'])): + (len(self._omebase.get('Plane', [])) or + len(self._omebase.get('Channel', [])))): if not len(self._omebase['TiffData']) or self._omebase['TiffData'][0] == {}: - self._omebase['TiffData'] = self._omebase['Plane'] + self._omebase['TiffData'] = self._omebase.get( + 'Plane', self._omebase.get('Channel')) elif (int(self._omebase['TiffData'][0].get('PlaneCount', 0)) == - len(self._omebase['Plane'])): - planes = copy.deepcopy(self._omebase['Plane']) + len(self._omebase.get('Plane', self._omebase.get('Channel', [])))): + planes = copy.deepcopy(self._omebase.get('Plane', self._omebase.get('Channel'))) for idx, plane in enumerate(planes): plane['IFD'] = plane.get( 'IFD', int(self._omebase['TiffData'][0].get('IFD', 0)) + idx) self._omebase['TiffData'] = planes + if isinstance(self._omebase['TiffData'], dict): + self._omebase['TiffData'] = [self._omebase['TiffData']] if len({entry.get('UUID', {}).get('FileName', '') for entry in self._omebase['TiffData']}) > 1: raise TileSourceException('OME Tiff references multiple files') @@ -211,8 +219,10 @@ def _parseOMEInfo(self): int(self._omebase['SizeT']) * int(self._omebase['SizeZ']) or len(self._omebase['TiffData']) != len( self._omebase.get('Plane', self._omebase['TiffData']))): - raise TileSourceException('OME Tiff contains frames that contain multiple planes') + raise TileSourceException( + 'OME Tiff contains frames that contain multiple planes') except (KeyError, ValueError, IndexError): + print('B') raise TileSourceException('OME Tiff does not contain an expected record') def getMetadata(self): diff --git a/sources/tiff/large_image_source_tiff/tiff_reader.py b/sources/tiff/large_image_source_tiff/tiff_reader.py index f11231571..2fbc3d747 100644 --- a/sources/tiff/large_image_source_tiff/tiff_reader.py +++ b/sources/tiff/large_image_source_tiff/tiff_reader.py @@ -133,7 +133,8 @@ def __init__(self, filePath, directoryNum, mustBeTiled=True, subDirectoryNum=0, self._open(filePath, directoryNum, subDirectoryNum) self._loadMetadata() config.getConfig('logger').debug( - 'TiffDirectory %d:%d Information %r', directoryNum, subDirectoryNum, self._tiffInfo) + 'TiffDirectory %d:%d Information %r', + directoryNum, subDirectoryNum or 0, self._tiffInfo) try: if validate: self._validate()