Skip to content

Commit

Permalink
Change how extensions and fallback priorities interact
Browse files Browse the repository at this point in the history
This increases the priority of matching file extensions and mimetypes
above non-matching values, even if the fallback is high priority.
  • Loading branch information
manthey committed Jun 6, 2023
1 parent 736a82d commit eb7e920
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 eb7e920

Please sign in to comment.