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

Add a framesAsAxes parameter to the multisource. #957

Merged
merged 1 commit into from
Sep 6, 2022
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Improvements
- Reduce rest calls to get settings ([953](../../pull/953))
- Add an endpoint to delete all annotations in a folder ([954](../../pull/954))
- Support relabeling axes in the multi source ([957](../../pull/957))

### Bug Fixes
- Harden adding images to the item list ([955](../../pull/955))
Expand Down
5 changes: 3 additions & 2 deletions large_image/tilesource/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1424,9 +1424,10 @@ def _addMetadataFrameInformation(self, metadata, channels=None):
if key in frame and frame[key] + 1 > maxref.get(key, 0):
maxref[key] = frame[key] + 1
frame['Frame'] = idx
if idx and any(
if idx and (any(
frame.get(key) != metadata['frames'][idx - 1].get(key)
for key in refkeys if key != 'IndexC'):
for key in refkeys if key != 'IndexC') or not any(
metadata['frames'][idx].get(key) for key in refkeys)):
index += 1
frame['Index'] = index
if any(val > 1 for val in maxref.values()):
Expand Down
47 changes: 45 additions & 2 deletions sources/multi/large_image_source_multi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,20 @@
'type': 'integer',
'exclusiveMinimum': 0,
},
'framesAsAxes': {
'description':
'An object with keys as axes and values as strides to '
'interpret the source frames. This overrides the internal '
'metadata for frames.',
'type': 'object',
'patternProperties': {
'^(c|t|z|xy)$': {
'type': 'integer',
'exclusiveMinimum': 0,
}
},
'additionalProperties': False,
},
'position': {
'type': 'object',
'additionalProperties': False,
Expand Down Expand Up @@ -531,6 +545,33 @@ def _axisKey(self, source, value, key):
(value - len(vals) + source.get(key, 0)))
return axisKey

def _adjustFramesAsAxes(self, frames, idx, framesAsAxes):
"""
Given a dictionary of axes and strides, relabel the indices in a frame
as if it was based on those strides.

:param frames: a list of frames from the tile source.
:param idx: 0-based index of the frame to adjust.
:param framesAsAxes: dictionary of axes and strides to apply.
:returns: the adjusted frame record.
"""
axisRange = {}
slen = len(frames)
check = 1
for stride, axis in sorted([[v, k] for k, v in framesAsAxes.items()], reverse=True):
axisRange[axis] = slen // stride
slen = stride
check *= axisRange[axis]
if check != len(frames) and not hasattr(self, '_warnedAdjustFramesAsAxes'):
self.logger.warning('framesAsAxes strides do not use all frames.')
self._warnedAdjustFramesAsAxes = True
frame = frames[idx].copy()
for axis in ['c', 'z', 't', 'xy']:
frame.pop('Index' + axis.upper(), None)
for axis, stride in framesAsAxes.items():
frame['Index' + axis.upper()] = (idx // stride) % axisRange[axis]
return frame

def _addSourceToFrames(self, tsMeta, source, sourceIdx, frameDict):
"""
Add a source to the all appropriate frames.
Expand Down Expand Up @@ -561,6 +602,8 @@ def _addSourceToFrames(self, tsMeta, source, sourceIdx, frameDict):
for frameIdx, frame in enumerate(frames):
if 'frames' in source and frameIdx not in source['frames']:
continue
if source.get('framesAsAxes'):
frame = self._adjustFramesAsAxes(frames, frameIdx, source.get('framesAsAxes'))
fKey = self._axisKey(source, frameIdx, 'frame')
cIdx = frame.get('IndexC', 0)
zIdx = frame.get('IndexZ', 0)
Expand Down Expand Up @@ -588,8 +631,8 @@ def _addSourceToFrames(self, tsMeta, source, sourceIdx, frameDict):
'frame': frameIdx,
'kwargs': kwargs,
})
frameDict['axesAllowed'] = frameDict['axesAllowed'] and (
len(frames) <= 1 or 'IndexRange' in tsMeta)
frameDict['axesAllowed'] = (frameDict['axesAllowed'] and (
len(frames) <= 1 or 'IndexRange' in tsMeta)) or aKey != (0, 0, 0, 0)
frameDict['byAxes'].setdefault(aKey, [])
frameDict['byAxes'][aKey].append({
'sourcenum': sourceIdx,
Expand Down
5 changes: 3 additions & 2 deletions sources/tifffile/large_image_source_tifffile/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,8 +317,9 @@ def getMetadata(self):
for idx in range(self._framecount):
frame = {'Frame': idx}
for axis, (basis, _pos, count) in self._basis.items():
frame['Index' + (axis.upper() if axis.upper() != 'P' else 'XY')] = (
idx // basis) % count
if axis != 'I':
frame['Index' + (axis.upper() if axis.upper() != 'P' else 'XY')] = (
idx // basis) % count
frames.append(frame)
self._addMetadataFrameInformation(result, getattr(self, '_channels', None))
if any(v != self._seriesShape[0] for v in self._seriesShape):
Expand Down
32 changes: 32 additions & 0 deletions test/test_source_multi.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,35 @@ def testMultiBand():
assert len(metadata['bands']) == 6
image, mimeType = source.getThumbnail(encoding='PNG')
assert image[:len(utilities.PNGHeader)] == utilities.PNGHeader


def testFramesAsAxes():
baseSource = {'sources': [{
'sourceName': 'test', 'path': '__none__', 'params': {
'sizeX': 1000, 'sizeY': 1000, 'frames': 60}}]}
source = large_image_source_multi.open(json.dumps(baseSource))
tileMetadata = source.getMetadata()
assert len(tileMetadata['frames']) == 60
assert 'IndexZ' not in tileMetadata['frames'][0]

asAxesSource1 = {'sources': [{
'sourceName': 'test', 'path': '__none__', 'params': {
'sizeX': 1000, 'sizeY': 1000, 'frames': 60},
'framesAsAxes': {'c': 1, 'z': 5}}]}
source = large_image_source_multi.open(json.dumps(asAxesSource1))
tileMetadata = source.getMetadata()
assert len(tileMetadata['frames']) == 60
assert 'IndexZ' in tileMetadata['frames'][0]
assert tileMetadata['IndexRange']['IndexC'] == 5
assert tileMetadata['IndexRange']['IndexZ'] == 12

asAxesSource1 = {'sources': [{
'sourceName': 'test', 'path': '__none__', 'params': {
'sizeX': 1000, 'sizeY': 1000, 'frames': 60},
'framesAsAxes': {'c': 1, 'z': 7}}]}
source = large_image_source_multi.open(json.dumps(asAxesSource1))
tileMetadata = source.getMetadata()
assert len(tileMetadata['frames']) == 56
assert 'IndexZ' in tileMetadata['frames'][0]
assert tileMetadata['IndexRange']['IndexC'] == 7
assert tileMetadata['IndexRange']['IndexZ'] == 8