Skip to content

Commit

Permalink
Merge pull request #1572 from girder/create-large-images
Browse files Browse the repository at this point in the history
Create new large images for each item in a folder
  • Loading branch information
manthey authored Jul 16, 2024
2 parents f58e07c + 5ae4647 commit 4b1841d
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 2 deletions.
70 changes: 68 additions & 2 deletions girder/girder_large_image/rest/large_image_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,14 @@
from girder.api import access
from girder.api.describe import Description, autoDescribeRoute, describeRoute
from girder.api.rest import Resource
from girder.constants import SortDir, TokenScope
from girder.constants import AccessType, SortDir, TokenScope
from girder.exceptions import RestException
from girder.models.file import File
from girder.models.folder import Folder
from girder.models.item import Item
from girder.models.setting import Setting
from large_image import cache_util
from large_image.exceptions import TileGeneralError
from large_image.exceptions import TileGeneralError, TileSourceError

from .. import constants, girder_tilesource
from ..models.image_item import ImageItem
Expand Down Expand Up @@ -255,6 +256,7 @@ def __init__(self):
self.route('GET', ('histograms',), self.countHistograms)
self.route('DELETE', ('histograms',), self.deleteHistograms)
self.route('DELETE', ('tiles', 'incomplete'), self.deleteIncompleteTiles)
self.route('PUT', ('folder', ':id', 'tiles'), self.createLargeImages)

@describeRoute(
Description('Clear tile source caches to release resources and file handles.'),
Expand Down Expand Up @@ -445,6 +447,70 @@ def _deleteCachedImages(self, spec, associatedImages=False, imageKey=None):
removed += 1
return removed

@access.user(scope=TokenScope.DATA_WRITE)
@autoDescribeRoute(
Description('Create new large images for all items within a folder.')
.notes('Does not work for items with multiple files and skips over items with '
'existing or unfinished large images.')
.modelParam('id', 'The ID of the folder.', model=Folder, level=AccessType.WRITE,
required=True)
.param('force', 'Whether creation job(s) should be forced for each large image.',
required=False, default=False, dataType='boolean')
.param('localJobs', 'Whether the job(s) created should be local.', required=False,
default=False, dataType='boolean')
.param('recurse', 'Whether child folders should be recursed.', required=False,
default=False, dataType='boolean')
.errorResponse('ID was invalid.')
.errorResponse('Write access was denied for the folder.', 403),
)
def createLargeImages(self, folder, params):
user = self.getCurrentUser()
createJobs = 'always' if self.boolParam('force', params, default=False) else True
return self.createImagesRecurseOption(folder=folder, createJobs=createJobs, user=user,
recurse=params.get('recurse'),
localJobs=params.get('localJobs'))

def createImagesRecurseOption(self, folder, createJobs, user, recurse, localJobs):
result = {'childFoldersRecursed': 0,
'itemsSkipped': 0,
'largeImagesCreated': 0,
'largeImagesRemovedAndRecreated': 0,
'totalItems': 0}
if recurse:
for childFolder in Folder().childFolders(parent=folder, parentType='folder'):
result['childFoldersRecursed'] += 1
childResult = self.createImagesRecurseOption(folder=childFolder,
createJobs=createJobs, user=user,
recurse=recurse, localJobs=localJobs)
for key in childResult:
result[key] += childResult[key]
for item in Folder().childItems(folder=folder):
result['totalItems'] += 1
if item.get('largeImage'):
if item['largeImage'].get('expected'):
result['itemsSkipped'] += 1
else:
try:
ImageItem().getMetadata(item)
result['itemsSkipped'] += 1
continue
except (TileSourceError, KeyError):
previousFileId = item['largeImage'].get('originalId',
item['largeImage']['fileId'])
ImageItem().delete(item)
ImageItem().createImageItem(item, File().load(user=user, id=previousFileId),
createJob=createJobs, localJob=localJobs)
result['largeImagesRemovedAndRecreated'] += 1
else:
files = list(Item().childFiles(item=item, limit=2))
if len(files) == 1:
ImageItem().createImageItem(item, files[0], createJob=createJobs,
localJob=localJobs)
result['largeImagesCreated'] += 1
else:
result['itemsSkipped'] += 1
return result

@describeRoute(
Description('Remove large images from items where the large image job '
'incomplete.')
Expand Down
31 changes: 31 additions & 0 deletions girder/test_girder/test_large_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,37 @@ def testThumbnailFileJob(server, admin, user, fsAssetstore):
Setting().set(constants.PluginSettings.LARGE_IMAGE_MAX_THUMBNAIL_FILES, 0)


@pytest.mark.usefixtures('unbindLargeImage')
@pytest.mark.plugin('large_image')
def testFolderCreateImages(server, admin, user, fsAssetstore):
file = utilities.uploadExternalFile('sample_image.ptif', admin, fsAssetstore)
itemId = file['itemId']
item = Item().load(itemId, user=admin)
folderId = str(item['folderId'])
# Remove the large image from this item
ImageItem().delete(item)
# Ask to make all items in this folder large images
resp = server.request(
method='PUT', path=f'/large_image/folder/{folderId}/tiles', user=admin)
assert utilities.respStatus(resp) == 200
assert resp.json['largeImagesCreated'] == 1
item = Item().load(itemId, user=admin)
# Check that this item became a large image again
assert 'largeImage' in item
# Hitting the endpoint again should skip the item
resp = server.request(
method='PUT', path=f'/large_image/folder/{folderId}/tiles', user=admin)
assert utilities.respStatus(resp) == 200
assert resp.json['itemsSkipped'] == 1
# If the item's source isn't working, it should be recreated.
item['largeImage']['sourceName'] = 'unknown'
Item().updateItem(item)
resp = server.request(
method='PUT', path=f'/large_image/folder/{folderId}/tiles', user=admin)
assert utilities.respStatus(resp) == 200
assert resp.json['largeImagesRemovedAndRecreated'] == 1


@pytest.mark.singular()
@pytest.mark.usefixtures('unbindLargeImage')
@pytest.mark.plugin('large_image')
Expand Down

0 comments on commit 4b1841d

Please sign in to comment.