Skip to content

Commit

Permalink
Merge pull request #1192 from girder/fallback-priority
Browse files Browse the repository at this point in the history
Change how extensions and fallback priorities interact
  • Loading branch information
manthey authored Jun 6, 2023
2 parents 736a82d + eb7e920 commit a7b672e
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 17 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Change Log

## 1.22.3

### Changes
- Change how extensions and fallback priorities interact ([#1192](../../pull/1192))

## 1.22.2

### Changes
Expand Down
35 changes: 24 additions & 11 deletions large_image/tilesource/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,17 @@ def loadTileSources(entryPointName='large_image.source', sourceDict=AvailableTil
'Failed to loaded tile source %s' % entryPoint.name)


def getSortedSourceList(availableSources, pathOrUri, *args, **kwargs):
def getSortedSourceList(availableSources, pathOrUri, mimeType=None, *args, **kwargs):
"""
Get an ordered list of sources where earlier sources are more likely to
work for a specified path or uri.
:param availableSources: an ordered dictionary of sources to try.
:param pathOrUri: either a file path or a fixed source via
large_image://<source>.
:returns: a list of (clash, priority, sourcename) for sources where
sourcename is a key in availableSources.
:param mimeType: the mimetype of the file, if known.
:returns: a list of (clash, fallback, priority, sourcename) for sources
where sourcename is a key in availableSources.
"""
uriWithoutProtocol = str(pathOrUri).split('://', 1)[-1]
isLargeImageUri = str(pathOrUri).startswith('large_image://')
Expand All @@ -84,8 +85,14 @@ def getSortedSourceList(availableSources, pathOrUri, *args, **kwargs):
for sourceName in availableSources:
sourceExtensions = availableSources[sourceName].extensions
priority = sourceExtensions.get(None, SourcePriority.MANUAL)
fallback = True
if (mimeType and getattr(availableSources[sourceName], 'mimeTypes', None) and
mimeType in availableSources[sourceName].mimeTypes):
fallback = False
priority = min(priority, availableSources[sourceName].mimeTypes[mimeType])
for ext in extensions:
if ext in sourceExtensions:
fallback = False
priority = min(priority, sourceExtensions[ext])
if isLargeImageUri and sourceName == uriWithoutProtocol:
priority = SourcePriority.NAMED
Expand All @@ -94,11 +101,11 @@ def getSortedSourceList(availableSources, pathOrUri, *args, **kwargs):
propertiesClash = any(
getattr(availableSources[sourceName], k, False) != v
for k, v in properties.items())
sourceList.append((propertiesClash, priority, sourceName))
sourceList.append((propertiesClash, fallback, priority, sourceName))
return sourceList


def getSourceNameFromDict(availableSources, pathOrUri, *args, **kwargs):
def getSourceNameFromDict(availableSources, pathOrUri, mimeType=None, *args, **kwargs):
"""
Get a tile source based on a ordered dictionary of known sources and a path
name or URI. Additional parameters are passed to the tile source and can
Expand All @@ -107,11 +114,12 @@ def getSourceNameFromDict(availableSources, pathOrUri, *args, **kwargs):
:param availableSources: an ordered dictionary of sources to try.
:param pathOrUri: either a file path or a fixed source via
large_image://<source>.
:param mimeType: the mimetype of the file, if known.
:returns: the name of a tile source that can read the input, or None if
there is no such source.
"""
sourceList = getSortedSourceList(availableSources, pathOrUri, *args, **kwargs)
for _clash, _priority, sourceName in sorted(sourceList):
sourceList = getSortedSourceList(availableSources, pathOrUri, mimeType, *args, **kwargs)
for _clash, _fallback, _priority, sourceName in sorted(sourceList):
if availableSources[sourceName].canRead(pathOrUri, *args, **kwargs):
return sourceName

Expand Down Expand Up @@ -173,18 +181,23 @@ def canRead(*args, **kwargs):
return False


def canReadList(*args, **kwargs):
def canReadList(pathOrUri, mimeType=None, *args, **kwargs):
"""
Check if large_image can read a path or uri via each source.
:param pathOrUri: either a file path or a fixed source via
large_image://<source>.
:param mimeType: the mimetype of the file, if known.
:returns: A list of tuples of (source name, canRead).
"""
if not len(AvailableTileSources):
loadTileSources()
sourceList = getSortedSourceList(AvailableTileSources, *args, **kwargs)
sourceList = getSortedSourceList(
AvailableTileSources, pathOrUri, mimeType, *args, **kwargs)
result = []
for _clash, _priority, sourceName in sorted(sourceList):
result.append((sourceName, AvailableTileSources[sourceName].canRead(*args, **kwargs)))
for _clash, _fallback, _priority, sourceName in sorted(sourceList):
result.append((sourceName, AvailableTileSources[sourceName].canRead(
pathOrUri, *args, **kwargs)))
return result


Expand Down
3 changes: 3 additions & 0 deletions test/datastore.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
# Tiff with floating point pixels
# Source: d042-353.crop.small.float32.tif
'd042-353.crop.small.float32.tif': 'sha512:ae05dbe6f3330c912893b203b55db27b0fdf3222a0e7f626d372c09668334494d07dc1d35533670cfac51b588d2292eeee7431317741fdb4cbb281c28a289115', # noqa
# JPEG with progressive compression and restart markers
# Source: d042-353.crop.small.jpg
'd042-353.crop.small.jpg': 'sha512:1353646637c1fae266b87312698aa39eca0311222c3a1399b60efdc13bfe55e2f3db59da005da945dd7e9e816f31ccd18846dd72744faac75215074c3d87414f', # noqa
# Tiff with JP2k compression
# Source: huron.image2_jpeg2k.tif
'huron.image2_jpeg2k.tif': 'sha512:eaba877079c86f0603b2a636a44d57832cdafe6d43a449121f575f0d43f69b8a17fa619301b066ece1c11050b41d687400b27407c404d827fd2c132d99e669ae', # noqa
Expand Down
14 changes: 8 additions & 6 deletions test/test_source_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@
},
'dummy': {'any': True, 'skipTiles': r''},
'gdal': {
'read': r'\.(jpeg|jp2|ptif|scn|svs|tif.*|qptiff)$',
'read': r'\.(jpg|jpeg|jp2|ptif|scn|svs|tif.*|qptiff)$',
'noread': r'(huron\.image2_jpeg2k|sample_jp2k_33003|TCGA-DU-6399|\.(ome.tiff|nc)$)',
'skipTiles': r'\.*nc$',
},
'mapnik': {
'read': r'\.(jpeg|jp2|ptif|nc|scn|svs|tif.*|qptiff)$',
'read': r'\.(jpg|jpeg|jp2|ptif|nc|scn|svs|tif.*|qptiff)$',
'noread': r'(huron\.image2_jpeg2k|sample_jp2k_33003|TCGA-DU-6399|\.(ome.tiff)$)',
# we should only test this with a projection
'skipTiles': r'',
Expand All @@ -64,12 +64,12 @@
'skipTiles': r'one_layer_missing',
},
'pil': {
'read': r'\.(jpeg|png|tif.*)$',
'noread': r'(G10-3|JK-kidney|d042-353|huron|one_layer_missing|US_Geo|extraoverview' + (
'read': r'\.(jpg|jpeg|png|tif.*)$',
'noread': r'(G10-3|JK-kidney|d042-353.*tif|huron|one_layer_missing|US_Geo|extraoverview' + (
r'|sample.*ome' if sys.version_info < (3, 7) else r'') + r')',
},
'rasterio': {
'read': r'\.(jpeg|jp2|ptif|scn|svs|tif.*|qptiff)$',
'read': r'\.(jpg|jpeg|jp2|ptif|scn|svs|tif.*|qptiff)$',
'noread': r'(huron\.image2_jpeg2k|sample_jp2k_33003|TCGA-DU-6399|\.(ome.tiff|nc)$)',
'python': sys.version_info >= (3, 8),
},
Expand All @@ -82,7 +82,7 @@
'skipTiles': r'(sample_image\.ptif|one_layer_missing_tiles)'},
'tifffile': {
'read': r'',
'noread': r'\.(nc|nd2|yml|yaml|json|czi|png|jpeg|jp2|dcm)$',
'noread': r'\.(nc|nd2|yml|yaml|json|czi|png|jpg|jpeg|jp2|dcm)$',
'python': sys.version_info >= (3, 7),
},
'vips': {
Expand Down Expand Up @@ -113,9 +113,11 @@ def testCanRead():
testDir = os.path.dirname(os.path.realpath(__file__))
imagePath = os.path.join(testDir, 'test_files', 'yb10kx5k.png')
assert large_image.canRead(imagePath) is False
assert large_image.canRead(imagePath, mimeType='image/png') is False

imagePath = datastore.fetch('sample_image.ptif')
assert large_image.canRead(imagePath) is True
assert large_image.canRead(imagePath, mimeType='image/png') is True


@pytest.mark.parametrize('source', [k for k, v in SourceAndFiles.items() if not v.get('any')])
Expand Down

0 comments on commit a7b672e

Please sign in to comment.