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

[Labels] IndexError after painting into 3D labels layer and toggling 3D #6518

Closed
psobolewskiPhD opened this issue Dec 6, 2023 · 3 comments · Fixed by #6520 or #6439
Closed

[Labels] IndexError after painting into 3D labels layer and toggling 3D #6518

psobolewskiPhD opened this issue Dec 6, 2023 · 3 comments · Fixed by #6520 or #6439
Assignees
Labels
bug Something isn't working priority-high High priority issue
Milestone

Comments

@psobolewskiPhD
Copy link
Member

🐛 Bug Report

If you paint into a 3D labels layer and then toggle to 3D you get a spam of IndexError: too many indices for array: array is 2-dimensional, but 3 were indexed

Traceback:

IndexError                                Traceback (most recent call last)
File ~/dev/napari/napari/components/viewer_model.py:521, in ViewerModel._update_status_bar_from_cursor(self=Viewer(camera=Camera(center=(63.5, 63.5, 63.5), ...ouse_drag_gen={}, _mouse_wheel_gen={}, keymap={}), event=None)
    519 active = self.layers.selection.active
    520 if active is not None:
--> 521     self.status = active.get_status(
        active = <Labels layer 'Labels' at 0x28f2d0e10>
        self.status = 'Ready'
        self = Viewer(camera=Camera(center=(63.5, 63.5, 63.5), zoom=5.855859374999999, angles=(0.0, 0.0, 89.99999999999999), perspective=0.0, mouse_pan=False, mouse_zoom=True), cursor=Cursor(position=(63.5, 93.98228837467208, 121.98842848087597), scaled=True, style=<CursorStyle.CIRCLE: 'circle'>, size=10.0), dims=Dims(ndim=3, ndisplay=3, order=(0, 1, 2), axis_labels=('0', '1', '2'), range=(RangeTuple(start=0.0, stop=127.0, step=1.0), RangeTuple(start=0.0, stop=127.0, step=1.0), RangeTuple(start=0.0, stop=127.0, step=1.0)), margin_left=(0.0, 0.0, 0.0), margin_right=(0.0, 0.0, 0.0), point=(63.0, 63.0, 63.0), last_used=0), grid=GridCanvas(stride=1, shape=(-1, -1), enabled=False), layers=[<Image layer 'binary_blobs' at 0x28f2d2ed0>, <Labels layer 'Labels' at 0x28f2d0e10>], help='use <6> for pan/zoom, use <7> for transform, use <1> for activate the label eraser, use <3> for activate the polygon tool, use <4> for activate the fill bucket, use <5> for pick mode', status='Ready', tooltip=Tooltip(visible=False, text=''), theme='dark', title='napari', mouse_over_canvas=True, mouse_move_callbacks=[], mouse_drag_callbacks=[], mouse_double_click_callbacks=[], mouse_wheel_callbacks=[<function dims_scroll at 0x177f5e520>], _persisted_mouse_event={}, _mouse_drag_gen={}, _mouse_wheel_gen={}, keymap={})
        self.cursor.position = (63.5, 93.98228837467208, 121.98842848087597)
        self.cursor = Cursor(position=(63.5, 93.98228837467208, 121.98842848087597), scaled=True, style=<CursorStyle.CIRCLE: 'circle'>, size=10.0)
        self.cursor._view_direction = <class 'numpy.ndarray'> (3,) float64
        self.dims = Dims(ndim=3, ndisplay=3, order=(0, 1, 2), axis_labels=('0', '1', '2'), range=(RangeTuple(start=0.0, stop=127.0, step=1.0), RangeTuple(start=0.0, stop=127.0, step=1.0), RangeTuple(start=0.0, stop=127.0, step=1.0)), margin_left=(0.0, 0.0, 0.0), margin_right=(0.0, 0.0, 0.0), point=(63.0, 63.0, 63.0), last_used=0)
    522         self.cursor.position,
    523         view_direction=self.cursor._view_direction,
    524         dims_displayed=list(self.dims.displayed),
    525         world=True,
    526     )
    528     self.help = active.help
    529     if self.tooltip.visible:

File ~/dev/napari/napari/layers/labels/labels.py:1717, in Labels.get_status(self=<Labels layer 'Labels'>, position=(63.5, 93.98228837467208, 121.98842848087597), view_direction=<class 'numpy.ndarray'> (3,) float64, dims_displayed=[0, 1, 2], world=True)
   1695 """Status message information of the data at a coordinate position.
   1696 
   1697 Parameters
   (...)
   1714     Dict containing a information that can be used in a status update.
   1715 """
   1716 if position is not None:
-> 1717     value = self.get_value(
        self = <Labels layer 'Labels' at 0x28f2d0e10>
        position = (63.5, 93.98228837467208, 121.98842848087597)
        view_direction = <class 'numpy.ndarray'> (3,) float64
        dims_displayed = [0, 1, 2]
        world = True
   1718         position,
   1719         view_direction=view_direction,
   1720         dims_displayed=dims_displayed,
   1721         world=world,
   1722     )
   1723 else:
   1724     value = None

File ~/dev/napari/napari/layers/base/base.py:1297, in Layer.get_value(self=<Labels layer 'Labels'>, position=<class 'numpy.ndarray'> (3,) float64, view_direction=<class 'numpy.ndarray'> (3,) float64, dims_displayed=[0, 1, 2], world=True)
   1290         view_direction = self._world_to_data_ray(view_direction)
   1291         start_point, end_point = self.get_ray_intersections(
   1292             position=position,
   1293             view_direction=view_direction,
   1294             dims_displayed=dims_displayed,
   1295             world=False,
   1296         )
-> 1297         value = self._get_value_3d(
        self = <Labels layer 'Labels' at 0x28f2d0e10>
        start_point = <class 'numpy.ndarray'> (3,) float64
        end_point = <class 'numpy.ndarray'> (3,) float64
        dims_displayed = [0, 1, 2]
   1298             start_point=start_point,
   1299             end_point=end_point,
   1300             dims_displayed=dims_displayed,
   1301         )
   1302 else:
   1303     value = self._get_value(position)

File ~/dev/napari/napari/layers/labels/labels.py:1245, in Labels._get_value_3d(self=<Labels layer 'Labels'>, start_point=<class 'numpy.ndarray'> (3,) float64, end_point=<class 'numpy.ndarray'> (3,) float64, dims_displayed=[0, 1, 2])
   1221 def _get_value_3d(
   1222     self,
   1223     start_point: Optional[np.ndarray],
   1224     end_point: Optional[np.ndarray],
   1225     dims_displayed: List[int],
   1226 ) -> Optional[int]:
   1227     """Get the first non-background value encountered along a ray.
   1228 
   1229     Parameters
   (...)
   1242         non-zero value is not encountered, returns 0 (the background value).
   1243     """
   1244     return (
-> 1245         self._get_value_ray(
        self = <Labels layer 'Labels' at 0x28f2d0e10>
        start_point = <class 'numpy.ndarray'> (3,) float64
        end_point = <class 'numpy.ndarray'> (3,) float64
        dims_displayed = [0, 1, 2]
   1246             start_point=start_point,
   1247             end_point=end_point,
   1248             dims_displayed=dims_displayed,
   1249         )
   1250         or 0
   1251     )

File ~/dev/napari/napari/layers/labels/labels.py:1213, in Labels._get_value_ray(self=<Labels layer 'Labels'>, start_point=<class 'numpy.ndarray'> (3,) float64, end_point=<class 'numpy.ndarray'> (3,) float64, dims_displayed=[0, 1, 2])
   1207 bounding_box[:, 1] += 1
   1209 clamped = clamp_point_to_bounding_box(
   1210     sample_points,
   1211     bounding_box,
   1212 ).astype(int)
-> 1213 values = im_slice[tuple(clamped.T)]
        im_slice = <class 'numpy.ndarray'> (128, 128) int64
        clamped = <class 'numpy.ndarray'> (256, 3) int64
   1214 nonzero_indices = np.flatnonzero(values)
   1215 if len(nonzero_indices > 0):
   1216     # if a nonzer0 value was found, return the first one

IndexError: too many indices for array: array is 2-dimensional, but 3 were indexed

git bisect points to:
a6e1569
#6411
This does not occur on 0.4.18 or 0.4.19rc2

💡 Steps to Reproduce

In the GUI:

  1. Open a 3D sample, e.g. Blobs 3D
  2. Add a labels layer
  3. Paint a dot
  4. toggle 3D

This script is a simple way to test:

import napari
from skimage import data
from scipy import ndimage as ndi

blobs = data.binary_blobs(length=128, volume_fraction=0.1, n_dim=3)
labeled = ndi.label(blobs)[0]
viewer = napari.Viewer()
viewer.add_labels(labeled, name='blob ID')

Toggle to 3D, should be fine.
Go back to 2D, paint a dot anywhere.
Toggle to 3D, error.

💡 Expected Behavior

No error should occur, it should be possible to paint labels and toggle 3D.

🌎 Environment

napari: 0.5.0a2.dev430+ga59cdac8
Platform: macOS-14.1.2-arm64-arm-64bit
System: MacOS 14.1.2
Python: 3.11.4 | packaged by conda-forge | (main, Jun 10 2023, 18:08:41) [Clang 15.0.7 ]
Qt: 6.5.2
PyQt6:
NumPy: 1.25.2
SciPy: 1.11.1
Dask: 2023.8.0
VisPy: 0.13.0
magicgui: 0.7.2
superqt: unknown
in-n-out: 0.1.8
app-model: 0.2.0
npe2: 0.7.2

OpenGL:

  • GL version: 2.1 Metal - 88
  • MAX_TEXTURE_SIZE: 16384

Screens:

  • screen 1: resolution 2056x1329, scale 2.0
  • screen 2: resolution 1920x1200, scale 1.0

Settings path:

  • /Users/sobolp/Library/Application Support/napari/napari-dev_36963bf0846bfe99dcf4f566a2cc780b6a723afa/settings.yaml
    Plugins:
  • napari: 0.5.0a2.dev261+ge62c9dd3 (77 contributions)
  • napari-animation: 0.0.8.dev2+ga9ca7a7.d20230817 (2 contributions)
  • napari-console: 0.0.8 (0 contributions)
  • napari-ome-zarr: 0.5.2 (2 contributions)
  • napari-svg: 0.1.10 (2 contributions)

💡 Additional Context

First noticed by @melissawm here:
https://napari.zulipchat.com/#narrow/stream/298358-working-group-documentation/topic/multidimensional.20labels.20layer

@psobolewskiPhD psobolewskiPhD added bug Something isn't working priority-high High priority issue labels Dec 6, 2023
@psobolewskiPhD psobolewskiPhD added this to the 0.4.19 milestone Dec 6, 2023
Czaki added a commit to Czaki/napari that referenced this issue Dec 6, 2023
@Czaki
Copy link
Collaborator

Czaki commented Dec 6, 2023

Fixed bug in #6439

@psobolewskiPhD
Copy link
Member Author

@Czaki you're a wizard. Works perfect with that PR branch.

@Czaki
Copy link
Collaborator

Czaki commented Dec 7, 2023

I have moved the fix to #6520 and added a test to not mess #6439

GenevieveBuckley pushed a commit that referenced this issue Dec 11, 2023
# References and relevant issues

closes #6518

# Description
Fix for #6518 with added tests
jni added a commit that referenced this issue Dec 15, 2023
…rect color mode (#6439)

Closes #6518
Closes #6084

# Description

In this PR, similarly to #6411, instead of using `float32` to pass data
to the GPU there we introduce heuristics for choosing smaller data
types, while keeping high performance.

Instead of complex calculation of color in the shader, a precomputed
texture array is used.
To avoid repetitive texture calculation, the textures are cached in the
`Colormap` objects.

For data of type uint8/int8/uint16/int16 we do not perform any transform
of data. We send them to the GPU as it is. This allows to reduce
computational time.

Based on experiments, the rendering performance is a little worse for
uint16/int16 than for uint8/int8. But it may depend on the GPU. Also,
using uint16/int16 means usage more GPU memory than for 8 bits type.
Still less than current main.

For datatypes using at least 32 bits, we add a preprocessing step where
we identify a set of labels that are mapped to the same color and map
all of them to the same value.
This often saves enough space to fall back to uint8/uint16. It allows
using a smaller additional array, and use less GPU memory. If there are
more than `2**16` distinct colors, then float32 is used, though
performance will be reduced.

We support only up to `2**23` distinct colors for now. 

For reduced memory usage, part of the functions used for data
preprocessing are compiled using numba. We provide a version of the
function that does not require `numba` but it limits the number of
distinct colors to `2**16` and involves additional array creation (more
memory usage).

---------

Co-authored-by: Juan Nunez-Iglesias <jni@fastmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Lorenzo Gaifas <brisvag@gmail.com>
Co-authored-by: Andy Sweet <andrew.d.sweet@gmail.com>
Czaki added a commit that referenced this issue Dec 15, 2023
# References and relevant issues

closes #6518

# Description
Fix for #6518 with added tests
Czaki added a commit that referenced this issue Dec 15, 2023
…rect color mode (#6439)

Closes #6518
Closes #6084

In this PR, similarly to #6411, instead of using `float32` to pass data
to the GPU there we introduce heuristics for choosing smaller data
types, while keeping high performance.

Instead of complex calculation of color in the shader, a precomputed
texture array is used.
To avoid repetitive texture calculation, the textures are cached in the
`Colormap` objects.

For data of type uint8/int8/uint16/int16 we do not perform any transform
of data. We send them to the GPU as it is. This allows to reduce
computational time.

Based on experiments, the rendering performance is a little worse for
uint16/int16 than for uint8/int8. But it may depend on the GPU. Also,
using uint16/int16 means usage more GPU memory than for 8 bits type.
Still less than current main.

For datatypes using at least 32 bits, we add a preprocessing step where
we identify a set of labels that are mapped to the same color and map
all of them to the same value.
This often saves enough space to fall back to uint8/uint16. It allows
using a smaller additional array, and use less GPU memory. If there are
more than `2**16` distinct colors, then float32 is used, though
performance will be reduced.

We support only up to `2**23` distinct colors for now.

For reduced memory usage, part of the functions used for data
preprocessing are compiled using numba. We provide a version of the
function that does not require `numba` but it limits the number of
distinct colors to `2**16` and involves additional array creation (more
memory usage).

---------

Co-authored-by: Juan Nunez-Iglesias <jni@fastmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Lorenzo Gaifas <brisvag@gmail.com>
Co-authored-by: Andy Sweet <andrew.d.sweet@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working priority-high High priority issue
Projects
None yet
3 participants