diff --git a/examples/integrations/itk/SelectROI.ipynb b/examples/integrations/itk/SelectROI.ipynb new file mode 100644 index 00000000..43ab1e4b --- /dev/null +++ b/examples/integrations/itk/SelectROI.ipynb @@ -0,0 +1,389 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Try this notebook in Google Colab, Binder or SageMaker!\n", + "\n", + "\n", + "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/InsightSoftwareConsortium/itkwidgets/blob/main/examples/integrations/itk/SelectROI.ipynb)\n", + "[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/InsightSoftwareConsortium/itkwidgets/HEAD?labpath=examples%2Fintegrations%2Fitk%2FSelectROI.ipynb)\n", + "[![Open In SageMaker Studio Lab](https://studiolab.sagemaker.aws/studiolab.svg)](https://studiolab.sagemaker.aws/import/github.com/InsightSoftwareConsortium/itkwidgets/blob/main/examples/integrations/itk/SelectROI.ipynb)" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Install dependencies for this example\n", + "import sys\n", + "\n", + "!{sys.executable} -m pip install -q pooch tqdm \"itk-io>=5.3.0\" \"itkwidgets[all]>=1.0a40\"" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "import itk\n", + "import pooch\n", + "from ngff_zarr import to_multiscales, to_ngff_image, Methods\n", + "from itkwidgets import view" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "file_name = pooch.retrieve('https://data.kitware.com/api/v1/file/564a5b078d777f7522dbfaa6/download',\n", + " fname='005_32months_T2_RegT1_Reg2Atlas_ManualBrainMask_Stripped.nrrd',\n", + " known_hash='a4a5739d5484f48653404763f807ff2b0e1900209cce029d912c2f92797207b6',\n", + " progressbar=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "image = itk.imread(file_name)\n", + "ngff_image = to_ngff_image(image)\n", + "multiscales = to_multiscales(ngff_image, method=Methods.DASK_IMAGE_GAUSSIAN)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": [ + "window.connectPlugin && window.connectPlugin(\"4f840812-c9e5-4904-863f-8ab6e363730d\")" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "viewer = view(multiscales)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "#### Use the cropping planes to select the region of interest, then run the following cells" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### There will be one or more resolution scales available depending on the size of the data. The lower the scale the higher the resolution, with zero being the highest resolution." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "tags": [ + "skip-execution" + ] + }, + "outputs": [], + "source": [ + "# Find the scale that we currently have loaded\n", + "loaded_scale = viewer.get_current_scale()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "tags": [ + "skip-execution" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "loaded_scale" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### The ROI region will return the physical, world coordinates for the selection" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "tags": [ + "skip-execution" + ] + }, + "outputs": [], + "source": [ + "# Get the selected region for the current level\n", + "roi_region = viewer.get_roi_region()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "tags": [ + "skip-execution" + ] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[{'x': -0.5, 'y': 112.67456075344046, 'z': -0.5},\n", + " {'x': 210.5527196395318, 'y': 196.21163395335645, 'z': 249.5}]" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "roi_region" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### By default the information for the current scale is returned when requesting the ROI slices. You can also pass a value in to explicitly request a scale." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "tags": [ + "skip-execution" + ] + }, + "outputs": [], + "source": [ + "# Request the slice information for the ROI\n", + "default_roi_slices = viewer.get_roi_slice()\n", + "roi_slices = viewer.get_roi_slice(loaded_scale)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### The results should match!\n", + "#### *Note*: The data is streamed in in chunks and the current resolution will improve as each scale becomes available. If you find that the slices do not match check the currently loaded scale (either programmatically or with the UI) - it may have improved since you last checked!" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "tags": [ + "skip-execution" + ] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Slices for loaded scale: (slice(0, 249, None), slice(112, 197, None), slice(0, 211, None))\n", + "Slices with default parameter: (slice(0, 249, None), slice(112, 197, None), slice(0, 211, None))\n" + ] + } + ], + "source": [ + "# The results should match\n", + "print(f'Slices for loaded scale: {roi_slices}')\n", + "print(f'Slices with default parameter: {default_roi_slices}')" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "tags": [ + "skip-execution" + ] + }, + "outputs": [], + "source": [ + "# Create a new viewer using only the data in the ROI determined above\n", + "loaded_image = multiscales.images[loaded_scale]\n", + "roi_data = loaded_image.data[roi_slices]\n", + "roi_image = to_ngff_image(\n", + " roi_data,\n", + " dims=loaded_image.dims,\n", + " scale=loaded_image.scale,\n", + " translation=roi_region[0])" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "tags": [ + "skip-execution" + ] + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " \n", + " " + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": [ + "window.connectPlugin && window.connectPlugin(\"4f840812-c9e5-4904-863f-8ab6e363730d\")" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "viewer2 = view(roi_image, rotate=True)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.4" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/integrations/itk/select_roi.gif b/examples/integrations/itk/select_roi.gif new file mode 100644 index 00000000..49c42191 Binary files /dev/null and b/examples/integrations/itk/select_roi.gif differ diff --git a/itkwidgets/viewer.py b/itkwidgets/viewer.py index 0263aa59..d9330043 100644 --- a/itkwidgets/viewer.py +++ b/itkwidgets/viewer.py @@ -1,5 +1,6 @@ import asyncio import functools +import numpy as np from imjoy_rpc import api from inspect import isawaitable from typing import List, Union, Tuple @@ -200,6 +201,13 @@ def set_background_color(self, bgColor: List[float]): async def get_background_color(self): return await self.viewer_rpc.itk_viewer.getBackgroundColor() + @fetch_value + def set_cropping_planes(self, cropping_planes): + self.viewer_rpc.itk_viewer.setCroppingPlanes(cropping_planes) + @fetch_value + async def get_cropping_planes(self): + return await self.viewer_rpc.itk_viewer.getCroppingPlanes() + @fetch_value def set_image(self, image: Image, name: str = 'Image'): render_type = _detect_render_type(image, 'image') @@ -305,6 +313,23 @@ async def get_image_volume_scattering_blend(self): return await self.viewer_rpc.itk_viewer.getImageVolumeScatteringBlend() @fetch_value + async def get_current_scale(self): + return await self.viewer_rpc.itk_viewer.getLoadedScale() + + @fetch_value + async def get_roi_region(self): + bounds = await self.viewer_rpc.itk_viewer.getCroppedImageWorldBounds() + x0, x1, y0, y1, z0, z1 = bounds + return [{ 'x': x0, 'y': y0, 'z': z0 }, { 'x': x1, 'y': y1, 'z': z1 }] + + @fetch_value + async def get_roi_slice(self, scale=-1): + idxs = await self.viewer_rpc.itk_viewer.getCroppedIndexBounds(scale) + x0, x1 = idxs['x'] + y0, y1 = idxs['y'] + z0, z1 = idxs['z'] + return np.index_exp[z0:z1, y0:y1, x0:x1] + def compare_images(self, fixed_image: Union[str, Image], moving_image: Union[str, Image], method: str = None, image_mix: float = None, checkerboard: bool = None, pattern: Union[Tuple[int, int], Tuple[int, int, int]] = None, swap_image_order: bool = None): # image args may be image name or image object fixed_name = 'Fixed'