InsightSoftwareConsortium / itkwidgets

An elegant Python interface for visualization on the web platform to interactively generate insights into multidimensional images, point sets, and geometry.
https://itkwidgets.readthedocs.io/
Apache License 2.0
580 stars 83 forks source link

`ZeroDivisionError` when attempting to open ITK image #586

Open tbirdso opened 1 year ago

tbirdso commented 1 year ago

Background

Observed zero division error when trying to view 3D ITK image with itkwidgets.view in a Jupyter notebook.

Output

Cell:

itkwidgets.view(average_template_10)

Output:

---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
Cell In[4], line 1
----> 1 itkwidgets.view(average_template_10)

File ~\Anaconda3\envs\venv-itk\lib\site-packages\itkwidgets\viewer.py:436, in view(data, **kwargs)
    322 def view(data=None, **kwargs):
    323     """View the image and/or point sets.
    324 
    325     Creates and returns an ImJoy plugin ipywidget to visualize an image, and/or
   (...)
    434         properties on the object to change the visualization.
    435     """
--> 436     viewer = Viewer(data=data, **kwargs)
    438     return viewer

File ~\Anaconda3\envs\venv-itk\lib\site-packages\itkwidgets\viewer.py:136, in Viewer.__init__(self, ui_collapsed, rotate, ui, **add_data_kwargs)
    132 def __init__(
    133     self, ui_collapsed=True, rotate=False, ui="pydata-sphinx", **add_data_kwargs
    134 ):
    135     input_data = self.input_data(add_data_kwargs)
--> 136     data = self.init_data(input_data)
    137     """Create a viewer."""
    138     self.viewer_rpc = ViewerRPC(
    139         ui_collapsed=ui_collapsed, rotate=rotate, ui=ui, data=data
    140     )

File ~\Anaconda3\envs\venv-itk\lib\site-packages\itkwidgets\viewer.py:175, in Viewer.init_data(self, input_data)
    173         result = _get_viewer_image(data, label=True)
    174     else:
--> 175         result = _get_viewer_image(data, label=False)
    176 elif render_type is RenderType.POINT_SET:
    177     result = _get_viewer_point_sets(data)

File ~\Anaconda3\envs\venv-itk\lib\site-packages\itkwidgets\integrations\__init__.py:70, in _get_viewer_image(image, label)
     68         ngff_image = itk_image_to_ngff_image(image)
     69         multiscales = to_multiscales(ngff_image, method=method)
---> 70         to_ngff_zarr(store, multiscales, chunk_store=chunk_store)
     71         return store
     73 if HAVE_VTK:

File ~\Anaconda3\envs\venv-itk\lib\site-packages\ngff_zarr\to_ngff_zarr.py:61, in to_ngff_zarr(store, multiscales, compute, overwrite, chunk_store, **kwargs)
     59     path_group = root.create_group(path)
     60     path_group.attrs["_ARRAY_DIMENSIONS"] = image.dims
---> 61     arr = dask.array.to_zarr(
     62         arr,
     63         store,
     64         component=path,
     65         overwrite=overwrite,
     66         compute=compute,
     67         return_stored=True,
     68         **kwargs,
     69     )
     70     arrays.append(arr)
     72 zarr.consolidate_metadata(store)

File ~\Anaconda3\envs\venv-itk\lib\site-packages\dask\array\core.py:3687, in to_zarr(arr, url, component, storage_options, overwrite, region, compute, return_stored, **kwargs)
   3676 chunks = [c[0] for c in arr.chunks]
   3678 z = zarr.create(
   3679     shape=arr.shape,
   3680     chunks=chunks,
   (...)
   3685     **kwargs,
   3686 )
-> 3687 return arr.store(z, lock=False, compute=compute, return_stored=return_stored)

File ~\Anaconda3\envs\venv-itk\lib\site-packages\dask\array\core.py:1757, in Array.store(self, target, **kwargs)
   1755 @wraps(store)
   1756 def store(self, target, **kwargs):
-> 1757     r = store([self], [target], **kwargs)
   1759     if kwargs.get("return_stored", False):
   1760         r = r[0]

File ~\Anaconda3\envs\venv-itk\lib\site-packages\dask\array\core.py:1218, in store(***failed resolving arguments***)
   1216 if compute:
   1217     store_dlyds = [Delayed(k, store_dsk, layer=k[0]) for k in map_keys]
-> 1218     store_dlyds = persist(*store_dlyds, **kwargs)
   1219     store_dsk_2 = HighLevelGraph.merge(*[e.dask for e in store_dlyds])
   1220     load_store_dsk = retrieve_from_ooc(map_keys, store_dsk, store_dsk_2)

File ~\Anaconda3\envs\venv-itk\lib\site-packages\dask\base.py:899, in persist(traverse, optimize_graph, scheduler, *args, **kwargs)
    896     keys.extend(a_keys)
    897     postpersists.append((rebuild, a_keys, state))
--> 899 results = schedule(dsk, keys, **kwargs)
    900 d = dict(zip(keys, results))
    901 results2 = [r({k: d[k] for k in ks}, *s) for r, ks, s in postpersists]

File ~\Anaconda3\envs\venv-itk\lib\site-packages\dask\threaded.py:89, in get(dsk, keys, cache, num_workers, pool, **kwargs)
     86     elif isinstance(pool, multiprocessing.pool.Pool):
     87         pool = MultiprocessingPoolExecutor(pool)
---> 89 results = get_async(
     90     pool.submit,
     91     pool._max_workers,
     92     dsk,
     93     keys,
     94     cache=cache,
     95     get_id=_thread_get_id,
     96     pack_exception=pack_exception,
     97     **kwargs,
     98 )
    100 # Cleanup pools associated to dead threads
    101 with pools_lock:

File ~\Anaconda3\envs\venv-itk\lib\site-packages\dask\local.py:511, in get_async(submit, num_workers, dsk, result, cache, get_id, rerun_exceptions_locally, pack_exception, raise_exception, callbacks, dumps, loads, chunksize, **kwargs)
    509         _execute_task(task, data)  # Re-execute locally
    510     else:
--> 511         raise_exception(exc, tb)
    512 res, worker_id = loads(res_info)
    513 state["cache"][key] = res

File ~\Anaconda3\envs\venv-itk\lib\site-packages\dask\local.py:319, in reraise(exc, tb)
    317 if exc.__traceback__ is not tb:
    318     raise exc.with_traceback(tb)
--> 319 raise exc

File ~\Anaconda3\envs\venv-itk\lib\site-packages\dask\local.py:224, in execute_task(key, task_info, dumps, loads, get_id, pack_exception)
    222 try:
    223     task, data = loads(task_info)
--> 224     result = _execute_task(task, data)
    225     id = get_id()
    226     result = dumps((result, id))

File ~\Anaconda3\envs\venv-itk\lib\site-packages\dask\core.py:119, in _execute_task(arg, cache, dsk)
    115     func, args = arg[0], arg[1:]
    116     # Note: Don't assign the subtask results to a variable. numpy detects
    117     # temporaries by their reference count and can execute certain
    118     # operations in-place.
--> 119     return func(*(_execute_task(a, cache) for a in args))
    120 elif not ishashable(arg):
    121     return arg

File ~\Anaconda3\envs\venv-itk\lib\site-packages\dask\array\core.py:4356, in store_chunk(x, out, index, lock, return_stored)
   4353 def store_chunk(
   4354     x: ArrayLike, out: ArrayLike, index: slice, lock: Any, return_stored: bool
   4355 ):
-> 4356     return load_store_chunk(x, out, index, lock, return_stored, False)

File ~\Anaconda3\envs\venv-itk\lib\site-packages\dask\array\core.py:4338, in load_store_chunk(x, out, index, lock, return_stored, load_stored)
   4336 if x is not None:
   4337     if is_arraylike(x):
-> 4338         out[index] = x
   4339     else:
   4340         out[index] = np.asanyarray(x)

File ~\Anaconda3\envs\venv-itk\lib\site-packages\zarr\core.py:1353, in Array.__setitem__(self, selection, value)
   1351     self.vindex[selection] = value
   1352 else:
-> 1353     self.set_basic_selection(pure_selection, value, fields=fields)

File ~\Anaconda3\envs\venv-itk\lib\site-packages\zarr\core.py:1448, in Array.set_basic_selection(self, selection, value, fields)
   1446     return self._set_basic_selection_zd(selection, value, fields=fields)
   1447 else:
-> 1448     return self._set_basic_selection_nd(selection, value, fields=fields)

File ~\Anaconda3\envs\venv-itk\lib\site-packages\zarr\core.py:1746, in Array._set_basic_selection_nd(self, selection, value, fields)
   1742 def _set_basic_selection_nd(self, selection, value, fields=None):
   1743     # implementation of __setitem__ for array with at least one dimension
   1744 
   1745     # setup indexer
-> 1746     indexer = BasicIndexer(selection, self)
   1748     self._set_selection(indexer, value, fields=fields)

File ~\Anaconda3\envs\venv-itk\lib\site-packages\zarr\indexing.py:342, in BasicIndexer.__init__(self, selection, array)
    339     dim_indexer = IntDimIndexer(dim_sel, dim_len, dim_chunk_len)
    341 elif is_slice(dim_sel):
--> 342     dim_indexer = SliceDimIndexer(dim_sel, dim_len, dim_chunk_len)
    344 else:
    345     raise IndexError('unsupported selection item for basic indexing; '
    346                      'expected integer or slice, got {!r}'
    347                      .format(type(dim_sel)))

File ~\Anaconda3\envs\venv-itk\lib\site-packages\zarr\indexing.py:176, in SliceDimIndexer.__init__(self, dim_sel, dim_len, dim_chunk_len)
    174 self.dim_chunk_len = dim_chunk_len
    175 self.nitems = max(0, ceildiv((self.stop - self.start), self.step))
--> 176 self.nchunks = ceildiv(self.dim_len, self.dim_chunk_len)

File ~\Anaconda3\envs\venv-itk\lib\site-packages\zarr\indexing.py:160, in ceildiv(a, b)
    159 def ceildiv(a, b):
--> 160     return math.ceil(a / b)

ZeroDivisionError: division by zero

Additional Information

The error does not appear and viewing does succeed when the image is cropped to a smaller size, i.e. 400x400x1000.

Image information printout:

Image (00000221E1D0D4E0)
  RTTI typeinfo:   class itk::Image<unsigned short,3>
  Reference Count: 1
  Modified Time: 427
  Debug: Off
  Object Name: 
  Observers: 
    none
  Source: (none)
  Source output name: (none)
  Release Data: Off
  Data Released: False
  Global Release Data: Off
  PipelineMTime: 237
  UpdateMTime: 426
  RealTimeStamp: 0 seconds 
  LargestPossibleRegion: 
    Dimension: 3
    Index: [0, 0, 0]
    Size: [1320, 800, 1140]
  BufferedRegion: 
    Dimension: 3
    Index: [0, 0, 0]
    Size: [1320, 800, 1140]
  RequestedRegion: 
    Dimension: 3
    Index: [0, 0, 0]
    Size: [1320, 800, 1140]
  Spacing: [10, 10, 10]
  Origin: [0, 0, 0]
  Direction: 
1 0 0
0 1 0
0 0 1

  IndexToPointMatrix: 
10 0 0
0 10 0
0 0 10

  PointToIndexMatrix: 
0.1 0 0
0 0.1 0
0 0 0.1

  Inverse Direction: 
1 0 0
0 1 0
0 0 1

  PixelContainer: 
    ImportImageContainer (00000221E5B729E0)
      RTTI typeinfo:   class itk::ImportImageContainer<unsigned __int64,unsigned short>
      Reference Count: 1
      Modified Time: 423
      Debug: Off
      Object Name: 
      Observers: 
        none
      Pointer: 00000221E8CF2040
      Container manages memory: true
      Size: 1203840000
      Capacity: 1203840000

Steps to reproduce

Download CCFv3 average_template_10.nrrd: http://download.alleninstitute.org/informatics-archive/current-release/mouse_ccf/average_template/

Load and view:

> import itk
> import itkwidgets
> image = itk.imread('path/to/average_template_10.nrrd')
> itkwidgets.view(image)

Platform Information

Windows 10

> python -m pip list | grep itk
itk                           5.3.0
itk-core                      5.3.0
itk-elastix                   0.15.0
itk-filtering                 5.3.0
itk-io                        5.3.0
itk-numerics                  5.3.0
itk-registration              5.3.0
itk-segmentation              5.3.0
itkwasm                       1.0b1
itkwidgets                    1.0a21
> python -m pip list | grep jupyter
imjoy-jupyter-extension       0.3.0
imjoy-jupyterlab-extension    0.1.13
jupyter-black                 0.3.3
jupyter-book                  0.13.1
jupyter-cache                 0.4.3
jupyter_client                7.4.7
jupyter_core                  5.1.0
jupyter-server                1.23.3
jupyter-server-proxy-windows  1.5.1
jupyter-sphinx                0.3.2
jupyterlab                    3.5.0
jupyterlab-pygments           0.2.2
jupyterlab_server             2.16.3
jupyterlab-widgets            3.0.3
sphinx-jupyterbook-latex      0.4.7
tbirdso commented 1 year ago

Another data point for reproducibility, observed ZeroDivisionError when trying to run view(label_image=label_img, ui_collapsed=True) in https://github.com/KitwareMedical/ITKUltrasound/blob/master/examples/SpectralDistributions.ipynb

Platform: Ubuntu 20.04 Python 3.8.8 ITK == v5.3rc04 itkwidgets == 1.0a21 zarr == 2.12.0 dask == 2022.8.0 dask-image == 2021.12.0

tbirdso commented 1 year ago

It appears that the issue is not present in itkwidgets==0.32.4, downgrading to that version is a sufficient workaround for now.