Skip to content

Commit 31df05d

Browse files
authored
Merge pull request #710 from bnmajor/roi-image
Roi image
2 parents 2c5e5e4 + 552ba12 commit 31df05d

File tree

3 files changed

+214
-85
lines changed

3 files changed

+214
-85
lines changed

examples/integrations/itk/SelectROI.ipynb

Lines changed: 130 additions & 78 deletions
Large diffs are not rendered by default.

itkwidgets/_initialization_params.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def parse_input_data(init_data_kwargs):
7373
return inputs
7474

7575

76-
def build_init_data(input_data):
76+
def build_init_data(input_data, stores):
7777
result= None
7878
for input_type in DATA_OPTIONS:
7979
data = input_data.pop(input_type, None)
@@ -83,12 +83,15 @@ def build_init_data(input_data):
8383
if render_type is RenderType.IMAGE:
8484
if input_type == 'label_image':
8585
result = _get_viewer_image(data, label=True)
86+
stores['LabelImage'] = result
8687
render_type = RenderType.LABELIMAGE
8788
elif input_type == 'fixed_image':
8889
result = _get_viewer_image(data)
90+
stores['Fixed'] = result
8991
render_type = RenderType.FIXEDIMAGE
9092
else:
9193
result = _get_viewer_image(data, label=False)
94+
stores['Image'] = result
9295
elif render_type is RenderType.POINT_SET:
9396
result = _get_viewer_point_set(data)
9497
if result is None:

itkwidgets/viewer.py

Lines changed: 80 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from inspect import isawaitable
66
from typing import List, Union, Tuple
77
from IPython.display import display, HTML
8+
from ngff_zarr import from_ngff_zarr, to_ngff_image, NgffImage
89
import uuid
910

1011
from ._type_aliases import Gaussians, Style, Image, PointSet
@@ -136,9 +137,10 @@ def __init__(
136137
self, ui_collapsed=True, rotate=False, ui="pydata-sphinx", **add_data_kwargs
137138
):
138139
"""Create a viewer."""
140+
self.stores = {}
139141
self.name = self.__str__()
140142
input_data = parse_input_data(add_data_kwargs)
141-
data = build_init_data(input_data)
143+
data = build_init_data(input_data, self.stores)
142144
if compare := input_data.get('compare'):
143145
data['compare'] = compare
144146
if ENVIRONMENT is not Env.HYPHA:
@@ -213,6 +215,7 @@ def set_image(self, image: Image, name: str = 'Image'):
213215
render_type = _detect_render_type(image, 'image')
214216
if render_type is RenderType.IMAGE:
215217
image = _get_viewer_image(image, label=False)
218+
self.stores[name] = image
216219
if ENVIRONMENT is Env.HYPHA:
217220
self.image = image
218221
svc_name = f'{self.workspace}/itkwidgets-server:data-set'
@@ -225,8 +228,28 @@ def set_image(self, image: Image, name: str = 'Image'):
225228
image = _get_viewer_point_set(image)
226229
self.viewer_rpc.itk_viewer.setPointSets(image)
227230
@fetch_value
228-
async def get_image(self):
229-
return await self.viewer_rpc.itk_viewer.getImage()
231+
async def get_image(self, name: str = 'Image') -> NgffImage:
232+
"""Get the full, highest resolution image.
233+
234+
:param name: Name of the loaded image data to use. 'Image', the
235+
default, selects the first loaded image.
236+
:type name: str
237+
238+
:return: image
239+
:rtype: NgffImage
240+
"""
241+
if store := self.stores.get(name):
242+
multiscales = from_ngff_zarr(store)
243+
loaded_image = multiscales.images[0]
244+
roi_data = loaded_image.data
245+
return to_ngff_image(
246+
roi_data,
247+
dims=loaded_image.dims,
248+
scale=loaded_image.scale,
249+
name=name,
250+
axes_units=loaded_image.axes_units
251+
)
252+
raise ValueError(f'No image data found for {name}.')
230253

231254
@fetch_value
232255
def set_image_blend_mode(self, mode: str):
@@ -323,6 +346,36 @@ async def get_current_scale(self):
323346
"""
324347
return await self.viewer_rpc.itk_viewer.getLoadedScale()
325348

349+
@fetch_value
350+
async def get_roi_image(self, scale: int = -1, name: str = 'Image') -> NgffImage:
351+
"""Get the image for the current ROI.
352+
353+
:param scale: scale of the primary image to get the slices for the
354+
current roi. -1, the default, uses the current scale.
355+
:type scale: int
356+
:param name: Name of the loaded image data to use. 'Image', the
357+
default, selects the first loaded image.
358+
:type name: str
359+
360+
:return: roi_image
361+
:rtype: NgffImage
362+
"""
363+
roi_slices = await self.get_roi_slice(scale)
364+
roi_region = await self.get_roi_region()
365+
if store := self.stores.get(name):
366+
multiscales = from_ngff_zarr(store)
367+
loaded_image = multiscales.images[scale]
368+
roi_data = loaded_image.data[roi_slices]
369+
return to_ngff_image(
370+
roi_data,
371+
dims=loaded_image.dims,
372+
scale=loaded_image.scale,
373+
translation=roi_region[0],
374+
name=name,
375+
axes_units=loaded_image.axes_units
376+
)
377+
raise ValueError(f'No image data found for {name}.')
378+
326379
@fetch_value
327380
async def get_roi_region(self):
328381
"""Get the current region of interest in world / physical space.
@@ -339,7 +392,7 @@ async def get_roi_region(self):
339392
return [{ 'x': x0, 'y': y0, 'z': z0 }, { 'x': x1, 'y': y1, 'z': z1 }]
340393

341394
@fetch_value
342-
async def get_roi_slice(self, scale=-1):
395+
async def get_roi_slice(self, scale: int = -1):
343396
"""Get the current region of interest as Python slice objects for the
344397
current resolution of the primary image. The result is in the order:
345398
@@ -395,6 +448,7 @@ def set_label_image(self, label_image: Image):
395448
render_type = _detect_render_type(label_image, 'image')
396449
if render_type is RenderType.IMAGE:
397450
label_image = _get_viewer_image(label_image, label=True)
451+
self.stores['LabelImage'] = label_image
398452
if ENVIRONMENT is Env.HYPHA:
399453
self.label_image = label_image
400454
svc_name = f"{self.workspace}/itkwidgets-server:data-set"
@@ -407,8 +461,24 @@ def set_label_image(self, label_image: Image):
407461
label_image = _get_viewer_point_set(label_image)
408462
self.viewer_rpc.itk_viewer.setPointSets(label_image)
409463
@fetch_value
410-
async def get_label_image(self):
411-
return await self.viewer_rpc.itk_viewer.getLabelImage()
464+
async def get_label_image(self) -> NgffImage:
465+
"""Get the full, highest resolution label image.
466+
467+
:return: label_image
468+
:rtype: NgffImage
469+
"""
470+
if store := self.stores.get('LabelImage'):
471+
multiscales = from_ngff_zarr(store)
472+
loaded_image = multiscales.images[0]
473+
roi_data = loaded_image.data
474+
return to_ngff_image(
475+
roi_data,
476+
dims=loaded_image.dims,
477+
scale=loaded_image.scale,
478+
name='LabelImage',
479+
axes_units=loaded_image.axes_units
480+
)
481+
raise ValueError(f'No label image data found.')
412482

413483
@fetch_value
414484
def set_label_image_blend(self, blend: float):
@@ -452,6 +522,10 @@ def set_layer_visibility(self, visible: bool, name: str):
452522
async def get_layer_visibility(self, name: str):
453523
return await self.viewer_rpc.itk_viewer.getLayerVisibility(name)
454524

525+
@fetch_value
526+
def get_loaded_image_names(self):
527+
return list(self.stores.keys())
528+
455529
@fetch_value
456530
def add_point_set(self, pointSet: PointSet):
457531
pointSet = _get_viewer_point_set(pointSet)

0 commit comments

Comments
 (0)