Skip to content

Commit 9c5d082

Browse files
authored
Merge pull request #713 from bnmajor/roi-multiscales
Add get_roi_multiscales method
2 parents f69cbec + 4d3e770 commit 9c5d082

File tree

3 files changed

+98
-36
lines changed

3 files changed

+98
-36
lines changed

examples/integrations/itk/SelectROI.ipynb

Lines changed: 51 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@
4545
"source": [
4646
"import itk\n",
4747
"import pooch\n",
48-
"from ngff_zarr import to_multiscales, ngff_image_to_itk_image, Methods\n",
49-
"from itkwidgets import view"
48+
"from ngff_zarr import ngff_image_to_itk_image\n",
49+
"from itkwidgets import view\n",
50+
"import zarr"
5051
]
5152
},
5253
{
@@ -235,8 +236,8 @@
235236
"name": "stdout",
236237
"output_type": "stream",
237238
"text": [
238-
"Slices for loaded scale: (slice(0, 250, None), slice(0, 350, None), slice(120, 173, None))\n",
239-
"Slices with default parameter: (slice(0, 250, None), slice(0, 350, None), slice(120, 173, None))\n"
239+
"Slices for loaded scale: (slice(0, 250, None), slice(0, 350, None), slice(122, 170, None))\n",
240+
"Slices with default parameter: (slice(0, 250, None), slice(0, 350, None), slice(122, 170, None))\n"
240241
]
241242
}
242243
],
@@ -246,6 +247,13 @@
246247
"print(f'Slices with default parameter: {default_roi_slices}')"
247248
]
248249
},
250+
{
251+
"cell_type": "markdown",
252+
"metadata": {},
253+
"source": [
254+
"#### The ROI region will return the physical, world coordinates for the selection"
255+
]
256+
},
249257
{
250258
"cell_type": "code",
251259
"execution_count": 10,
@@ -256,10 +264,8 @@
256264
},
257265
"outputs": [],
258266
"source": [
259-
"# Create a new viewer using only the data in the ROI determined above\n",
260-
"# get_roi_image will return a dict of ngff images with the keys representing the names for each\n",
261-
"# For our example we should have one entry under \"Image\"\n",
262-
"roi_image = viewer.get_roi_image(loaded_scale)"
267+
"# Get the selected region for the current level\n",
268+
"roi_region = viewer.get_roi_region()"
263269
]
264270
},
265271
{
@@ -274,7 +280,8 @@
274280
{
275281
"data": {
276282
"text/plain": [
277-
"NgffImage(data=dask.array<getitem, shape=(250, 350, 53), dtype=int16, chunksize=(128, 128, 45), chunktype=numpy.ndarray>, dims=['z', 'y', 'x'], scale={'z': 0.2734, 'y': 0.2734, 'x': 0.2734}, translation={'x': 25.973146382696534, 'y': -6.971699714660643, 'z': -6.971699953079224}, name='Image', axes_units={'z': None, 'y': None, 'x': None}, computed_callbacks=[])"
283+
"[{'x': 26.70976023219073, 'y': -6.971699714660643, 'z': -6.971699953079224},\n",
284+
" {'x': 39.35382616126007, 'y': 88.71824073791504, 'z': 61.378257513046265}]"
278285
]
279286
},
280287
"execution_count": 11,
@@ -283,14 +290,7 @@
283290
}
284291
],
285292
"source": [
286-
"roi_image"
287-
]
288-
},
289-
{
290-
"cell_type": "markdown",
291-
"metadata": {},
292-
"source": [
293-
"#### The ROI region will return the physical, world coordinates for the selection"
293+
"roi_region"
294294
]
295295
},
296296
{
@@ -303,8 +303,9 @@
303303
},
304304
"outputs": [],
305305
"source": [
306-
"# Get the selected region for the current level\n",
307-
"roi_region = viewer.get_roi_region()"
306+
"# get_roi_image will return an ngff image for the currently selected image\n",
307+
"# optionally a name can also be passed in to select a specific image if more than one image or label is loaded\n",
308+
"roi_image = viewer.get_roi_image(loaded_scale)"
308309
]
309310
},
310311
{
@@ -319,8 +320,7 @@
319320
{
320321
"data": {
321322
"text/plain": [
322-
"[{'x': 25.973146382696534, 'y': -6.971699714660643, 'z': -6.971699953079224},\n",
323-
" {'x': 40.14316091031731, 'y': 88.71824073791504, 'z': 61.378257513046265}]"
323+
"NgffImage(data=dask.array<getitem, shape=(250, 350, 48), dtype=int16, chunksize=(128, 128, 42), chunktype=numpy.ndarray>, dims=['z', 'y', 'x'], scale={'z': 0.2734, 'y': 0.2734, 'x': 0.2734}, translation={'x': 26.70976023219073, 'y': -6.971699714660643, 'z': -6.971699953079224}, name='Image', axes_units={'z': None, 'y': None, 'x': None}, computed_callbacks=[])"
324324
]
325325
},
326326
"execution_count": 13,
@@ -329,7 +329,7 @@
329329
}
330330
],
331331
"source": [
332-
"roi_region"
332+
"roi_image"
333333
]
334334
},
335335
{
@@ -342,8 +342,8 @@
342342
},
343343
"outputs": [],
344344
"source": [
345-
"# Create an itk image from the ngff image returned from get_roi_image\n",
346-
"itk_image = ngff_image_to_itk_image(roi_image)"
345+
"# Optionally we can also grab the ngff images for all scales\n",
346+
"roi_multiscales = viewer.get_roi_multiscale()"
347347
]
348348
},
349349
{
@@ -354,6 +354,30 @@
354354
"skip-execution"
355355
]
356356
},
357+
"outputs": [
358+
{
359+
"data": {
360+
"text/plain": [
361+
"Multiscales(images=[NgffImage(data=dask.array<rechunk-merge, shape=(250, 350, 48), dtype=int16, chunksize=(128, 128, 48), chunktype=numpy.ndarray>, dims=['z', 'y', 'x'], scale={'z': 0.2734, 'y': 0.2734, 'x': 0.2734}, translation={'x': 26.70976023219073, 'y': -6.971699714660643, 'z': -6.971699953079224}, name='Image', axes_units={'z': None, 'y': None, 'x': None}, computed_callbacks=[]), NgffImage(data=dask.array<rechunk-merge, shape=(250, 175, 48), dtype=int16, chunksize=(128, 128, 48), chunktype=numpy.ndarray>, dims=['z', 'y', 'x'], scale={'z': 0.2734, 'y': 0.5468, 'x': 0.2734}, translation={'z': -6.971699953079224, 'y': -6.8349997146606425, 'x': 26.70976023219073}, name='image', axes_units=None, computed_callbacks=[])], metadata=Metadata(axes=[Axis(name='z', type='space', unit=None), Axis(name='y', type='space', unit=None), Axis(name='x', type='space', unit=None)], datasets=[Dataset(path='scale0/Image', coordinateTransformations=[Scale(scale=[0.2734, 0.2734, 0.2734], type='scale'), Translation(translation=[-6.971699953079224, -6.971699714660643, 26.70976023219073], type='translation')]), Dataset(path='scale1/Image', coordinateTransformations=[Scale(scale=[0.2734, 0.5468, 0.2734], type='scale'), Translation(translation=[-6.971699953079224, -6.8349997146606425, 26.70976023219073], type='translation')])], coordinateTransformations=None, name='Image', version='0.4'), scale_factors=[{'x': 1, 'y': 2, 'z': 1}], method=<Methods.DASK_IMAGE_GAUSSIAN: 'dask_image_gaussian'>, chunks={'z': 128, 'y': 128, 'x': 128})"
362+
]
363+
},
364+
"execution_count": 15,
365+
"metadata": {},
366+
"output_type": "execute_result"
367+
}
368+
],
369+
"source": [
370+
"roi_multiscales"
371+
]
372+
},
373+
{
374+
"cell_type": "code",
375+
"execution_count": 16,
376+
"metadata": {
377+
"tags": [
378+
"skip-execution"
379+
]
380+
},
357381
"outputs": [
358382
{
359383
"data": {
@@ -402,7 +426,8 @@
402426
}
403427
],
404428
"source": [
405-
"viewer2 = view(itk_image, rotate=True)"
429+
"# Create a new viewer using only the data in the ROI determined above\n",
430+
"viewer2 = view(roi_multiscales, rotate=True)"
406431
]
407432
},
408433
{
@@ -416,7 +441,7 @@
416441
},
417442
{
418443
"cell_type": "code",
419-
"execution_count": 16,
444+
"execution_count": 17,
420445
"metadata": {
421446
"tags": [
422447
"skip-execution"

itkwidgets/cell_watcher.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -181,27 +181,35 @@ async def _execute_next_request(self):
181181
else:
182182
self.kernel._publish_status("idle")
183183

184-
self.current_request = None
185-
if self.all_getters_resolved:
184+
if not self.results:
185+
self.current_request = None
186+
if self.all_getters_resolved and not self._events.empty():
186187
# Continue processing the remaining queued tasks
187188
self.create_task(self.execute_next_request)
188189

189190
def update_namespace(self):
190191
# Update the namespace variables with the results from the getters
191192
# FIXME: This is a temporary "fix" and does not handle updating output
192193
keys = [k for k in self.shell.user_ns.keys()]
193-
for key in keys:
194-
value = self.shell.user_ns[key]
195-
if asyncio.isfuture(value) and (isinstance(value, FuturePromise) or isinstance(value, asyncio.Task)):
196-
# Getters/setters return futures
197-
# They should all be resolved now, so use the result
198-
self.shell.user_ns[key] = value.result()
199-
self.results.clear()
194+
try:
195+
for key in keys:
196+
value = self.shell.user_ns[key]
197+
if asyncio.isfuture(value) and (isinstance(value, FuturePromise) or isinstance(value, asyncio.Task)):
198+
# Getters/setters return futures
199+
# They should all be resolved now, so use the result
200+
self.shell.user_ns[key] = value.result()
201+
self.results.clear()
202+
except Exception as e:
203+
self.results.clear()
204+
self.abort_all = True
205+
self.create_task(self._execute_next_request)
206+
raise e
200207

201208
def _callback(self, *args, **kwargs):
202209
# After each getter/setter resolves check if they've all resolved
203210
if self.all_getters_resolved:
204211
self.update_namespace()
212+
self.current_request = None
205213
self.create_task(self.execute_next_request)
206214

207215
def post_run_cell(self, response):

itkwidgets/viewer.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@
88
from typing import List, Union, Tuple
99
from IPython.display import display, HTML
1010
from IPython.lib import backgroundjobs as bg
11-
from ngff_zarr import from_ngff_zarr, to_ngff_image, NgffImage
11+
from ngff_zarr import (
12+
from_ngff_zarr,
13+
to_ngff_image,
14+
Multiscales,
15+
NgffImage
16+
)
1217
import uuid
1318

1419
from ._method_types import deferred_methods
@@ -434,6 +439,30 @@ async def get_roi_image(self, scale: int = -1, name: str = 'Image') -> NgffImage
434439
)
435440
raise ValueError(f'No image data found for {name}.')
436441

442+
@fetch_value
443+
async def get_roi_multiscale(self, name: str = 'Image') -> Multiscales:
444+
"""Build and return a new Multiscales NgffImage for the ROI.
445+
446+
:param name: Name of the loaded image data to use. 'Image', the
447+
default, selects the first loaded image.
448+
:type name: str
449+
450+
:return: roi_multiscales
451+
:rtype: Multiscales NgffImage
452+
"""
453+
if store := self.stores.get(name):
454+
multiscales = from_ngff_zarr(store)
455+
scales = range(len(multiscales.images))
456+
images = [await self.get_roi_image(s) for s in scales]
457+
return Multiscales(
458+
images=images,
459+
metadata=multiscales.metadata,
460+
scale_factors=multiscales.scale_factors,
461+
method=multiscales.method,
462+
chunks=multiscales.chunks
463+
)
464+
raise ValueError(f'No image data found for {name}.')
465+
437466
@fetch_value
438467
async def get_roi_region(self):
439468
"""Get the current region of interest in world / physical space.

0 commit comments

Comments
 (0)