Skip to content

Commit

Permalink
Merge pull request #783 from girder/tile-frames-quad-info
Browse files Browse the repository at this point in the history
Add options to get frame quad info from python
  • Loading branch information
manthey authored Feb 17, 2022
2 parents aa38c5f + a662812 commit 81349e5
Show file tree
Hide file tree
Showing 9 changed files with 660 additions and 181 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

## Version 1.11.1

### Improvements
- Add options to get frame quad info from python ([#783](../../pull/783))

## Version 1.11.0

### Improvements
Expand Down
47 changes: 47 additions & 0 deletions girder/girder_large_image/models/image_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,26 @@ def getThumbnail(self, item, checkAndCreate=False, width=None, height=None, **kw

def _getAndCacheImageOrData(
self, item, imageFunc, checkAndCreate, keydict, pickleCache=False, **kwargs):
"""
Get a file associated with an image that can be generated by a
function.
:param item: the idem to process.
:param imageFunc: the function to call to generate a file.
:param checkAndCreate: False to return the data, creating and caching
it if needed. True to return True if the data is already in cache,
or to create the data, cache, and return it if not. 'nosave' to
return data from the cache if present, or generate the data but do
not return it if not in the cache. 'check' to just return True or
False to report if it is in the cache.
:param keydict: a dictionary of values to use for the cache key.
:param pickleCache: if True, the results of the function are pickled to
preserver them. If Fales, the results can be saved as a file
directly.
:params **kwargs: passed to the tile source and to the imageFunc. May
contain contentDisposition to determine how results are returned.
:returns:
"""
if 'fill' in keydict and (keydict['fill']).lower() == 'none':
del keydict['fill']
keydict = {k: v for k, v in keydict.items() if v is not None and not k.startswith('_')}
Expand All @@ -339,6 +359,8 @@ def _getAndCacheImageOrData(
data = File().open(existing).read()
return pickle.loads(data), 'application/octet-stream'
return File().download(existing, contentDisposition=contentDisposition)
if checkAndCreate == 'check':
return False
tileSource = self._loadTileSource(item, **kwargs)
result = getattr(tileSource, imageFunc)(**kwargs)
if result is None:
Expand Down Expand Up @@ -539,3 +561,28 @@ def getAssociatedImage(self, item, imageKey, checkAndCreate=False, *args, **kwar
keydict = dict(kwargs, imageKey=imageKey)
return self._getAndCacheImageOrData(
item, 'getAssociatedImage', checkAndCreate, keydict, imageKey=imageKey, **kwargs)

def _scheduleTileFrames(self, item, tileFramesList, user):
"""
Schedule generating tile frames in a local job.
:param item: the item.
:param tileFramesList: a list of dictionary of parameters to pass to
the tileFrames method.
:param user: the user owning the job.
"""
job = Job().createLocalJob(
module='large_image_tasks.tasks',
function='cache_tile_frames_job',
kwargs={
'itemId': str(item['_id']),
'tileFramesList': tileFramesList,
},
title='Cache tileFrames',
type='large_image_cache_tile_frames',
user=user,
public=True,
asynchronous=True,
)
Job().scheduleJob(job)
return job
147 changes: 117 additions & 30 deletions girder/girder_large_image/rest/tiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import cherrypy

import large_image
from girder.api import access, filter_logging
from girder.api.describe import Description, autoDescribeRoute, describeRoute
from girder.api.rest import filtermodel, loadmodel, setRawResponse, setResponseHeader
Expand Down Expand Up @@ -106,6 +107,8 @@ def __init__(self, apiRoot):
apiRoot.item.route('GET', (':itemId', 'tiles', 'thumbnail'), self.getTilesThumbnail)
apiRoot.item.route('GET', (':itemId', 'tiles', 'region'), self.getTilesRegion)
apiRoot.item.route('GET', (':itemId', 'tiles', 'tile_frames'), self.tileFrames)
apiRoot.item.route('GET', (':itemId', 'tiles', 'tile_frames', 'quad_info'),
self.tileFramesQuadInfo)
apiRoot.item.route('GET', (':itemId', 'tiles', 'pixel'), self.getTilesPixel)
apiRoot.item.route('GET', (':itemId', 'tiles', 'histogram'), self.getHistogram)
apiRoot.item.route('GET', (':itemId', 'tiles', 'bands'), self.getBandInformation)
Expand Down Expand Up @@ -1086,9 +1089,37 @@ def getAssociatedImageMetadata(self, item, image, params):
result['info'] = pilImage.info
return result

_tileFramesParams = [
('framesAcross', int),
('frameList', str),
('left', float, 'region', 'left'),
('top', float, 'region', 'top'),
('right', float, 'region', 'right'),
('bottom', float, 'region', 'bottom'),
('regionWidth', float, 'region', 'width'),
('regionHeight', float, 'region', 'height'),
('units', str, 'region', 'units'),
('unitsWH', str, 'region', 'unitsWH'),
('width', int, 'output', 'maxWidth'),
('height', int, 'output', 'maxHeight'),
('fill', str),
('magnification', float, 'scale', 'magnification'),
('mm_x', float, 'scale', 'mm_x'),
('mm_y', float, 'scale', 'mm_y'),
('exact', bool, 'scale', 'exact'),
('frame', int),
('encoding', str),
('jpegQuality', int),
('jpegSubsampling', int),
('tiffCompression', str),
('style', str),
('resample', 'boolOrInt'),
('contentDisposition', str),
('contentDispositionFileName', str)
]

@describeRoute(
Description('Get any region of a large image item, optionally scaling '
'it.')
Description('Composite thumbnails of multiple frames into a single image.')
.param('itemId', 'The ID of the item.', paramType='path')
.param('framesAcross', 'How many frames across', required=False, dataType='int')
.param('frameList', 'Comma-separated list of frames', required=False)
Expand Down Expand Up @@ -1180,34 +1211,7 @@ def tileFrames(self, item, params):
checkAndCreate = False if cache else 'nosave'
_adjustParams(params)

params = self._parseParams(params, True, [
('framesAcross', int),
('frameList', str),
('left', float, 'region', 'left'),
('top', float, 'region', 'top'),
('right', float, 'region', 'right'),
('bottom', float, 'region', 'bottom'),
('regionWidth', float, 'region', 'width'),
('regionHeight', float, 'region', 'height'),
('units', str, 'region', 'units'),
('unitsWH', str, 'region', 'unitsWH'),
('width', int, 'output', 'maxWidth'),
('height', int, 'output', 'maxHeight'),
('fill', str),
('magnification', float, 'scale', 'magnification'),
('mm_x', float, 'scale', 'mm_x'),
('mm_y', float, 'scale', 'mm_y'),
('exact', bool, 'scale', 'exact'),
('frame', int),
('encoding', str),
('jpegQuality', int),
('jpegSubsampling', int),
('tiffCompression', str),
('style', str),
('resample', 'boolOrInt'),
('contentDisposition', str),
('contentDispositionFileName', str)
])
params = self._parseParams(params, True, self._tileFramesParams)
_handleETag('tileFrames', item, params)
if 'frameList' in params:
params['frameList'] = [
Expand Down Expand Up @@ -1244,3 +1248,86 @@ def stream():
return stream
setRawResponse()
return regionData

@describeRoute(
Description('Get parameters for using tile_frames as background sprite images.')
.param('itemId', 'The ID of the item.', paramType='path')
.param('format', 'Optional format parameters, such as "encoding=JPEG&'
'jpegQuality=85&jpegSubsampling=1". If specified, these '
'replace the defaults.', required=False)
.param('query', 'Addition query parameters that would be passed to '
'tile endpoints, such as style.', required=False)
.param('frameBase', 'Starting frame number (default 0)',
required=False, dataType='int')
.param('frameStride', 'Only use every frameStride frame of the image '
'(default 1)', required=False, dataType='int')
.param('frameGroup', 'Group frames when using multiple textures to '
'keep boundaries at a multiple of the group size number.',
required=False, dataType='int')
.param('frameGroupFactor', 'Ignore grouping if the resultant images '
'would be more than this factor smaller than without grouping '
'(default 4)', required=False, dataType='int')
.param('frameGroupStride', 'Reorder frames based on the to stride.',
required=False, dataType='int')
.param('maxTextureSize', 'Maximum texture size in either dimension. '
'This should be the smaller of a desired value and of the '
'intended graphics environment texture buffer (default 16384).',
required=False, dataType='int')
.param('maxTextures', 'Maximum number of textures to use (default 1).',
required=False, dataType='int')
.param('maxTotalTexturePixels', 'Limit the total area of all combined '
'textures (default 2**30).',
required=False, dataType='int')
.param('alignment', 'Individual frame alignment within a texture. '
'Used to avoid jpeg artifacts from crossing frames (default '
'16).',
required=False, dataType='int')
.param('maxFrameSize', 'If specified, frames will never be larger '
'than this, even if the texture size allows it (default None).',
required=False, dataType='int')
.param('cache', 'Report on or request caching the resultant frames. '
'Scheduling creates a local job.',
required=False,
enum=['none', 'report', 'schedule'])
.errorResponse('ID was invalid.')
.errorResponse('Read access was denied for the item.', 403)
)
@access.public(cookie=True)
@loadmodel(model='item', map={'itemId': 'item'}, level=AccessType.READ)
def tileFramesQuadInfo(self, item, params):
metadata = self.imageItemModel.getMetadata(item)
options = self._parseParams(params, False, [
('format', str),
('query', str),
('frameBase', int),
('frameStride', int),
('frameGroup', int),
('frameGroupFactor', int),
('frameGroupStride', int),
('maxTextureSize', int),
('maxTextures', int),
('maxTotalTexturePixels', int),
('alignment', int),
('maxFrameSize', int),
])
for key in {'format', 'query'}:
if key in options:
options[key] = dict(urllib.parse.parse_qsl(options[key]))
result = large_image.tilesource.utilities.getTileFramesQuadInfo(metadata, options)
if params.get('cache') in {'report', 'schedule'}:
needed = []
result['cached'] = []
for src in result['src']:
tfParams = self._parseParams(src, False, self._tileFramesParams)
if 'frameList' in tfParams:
tfParams['frameList'] = [
int(f.strip()) for f in str(tfParams['frameList']).lstrip(
'[').rstrip(']').split(',')]
result['cached'].append(self.imageItemModel.tileFrames(
item, checkAndCreate='check', **tfParams))
if not result['cached'][-1]:
needed.append(tfParams)
if params.get('cache') == 'schedule' and not all(result['cached']):
result['scheduledJob'] = str(self.imageItemModel._scheduleTileFrames(
item, needed, self.getCurrentUser())['_id'])
return result
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ var GeojsImageViewerWidget = ImageViewerWidget.extend({
// maxTextures: 16,
// maxTotalTexturePixels: 256 * 1024 * 1024,
baseUrl: this._getTileUrl('{z}', '{x}', '{y}').split('/tiles/')[0] + '/tiles',
restRequest: restRequest,
restUrl: 'item/' + this.itemId + '/tiles',
query: 'cache=true'
});
this._layer.setFrameQuad(0);
Expand Down
Loading

0 comments on commit 81349e5

Please sign in to comment.