Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change how extensions and fallback priorities interact #1192

Merged
merged 1 commit into from
Jun 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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