Controlling dimensionality of napari point/shape layers?

When instantiating a new shapes or points layer, the dimensionality of the data seems to depend on the current state of the viewer.

For example, if I make a 2D image layer and then a shapes layer, the shape data is 2D:

import napari
import numpy as np

with napari.gui_qt() as app:
    viewer = napari.Viewer()
    image = np.random.rand(100,100)
    viewer.add_image(image, name="image")
    viewer.add_shapes(name='shape_layer')
    
    # now i manually draw a line in the viewer in the shape layer
    
    print(viewer.layers['shape_layer'].data)
    # [array([[ 17.64782677, -12.67391434],
    #   [ 66.43043749, 109.18696027]])]

However, if the initial image layer has 3D data, then the shapes layer is 3D:

with napari.gui_qt() as app:
    viewer = napari.Viewer()
    image = np.random.rand(5,100,100)
    viewer.add_image(image, name="image")
    viewer.add_shapes(name='shape_layer')
    
    # now i manually draw a line in the viewer in the shape layer
    
    print(viewer.layers['shape_layer'].data)
    # [array([[  0.        ,  20.35895121, -18.13110399],
    #   [  0.        ,  67.30289359, 109.57233247]])]

Is there a way to ensure that a layer contains only data of a given dimension? I’d love to be able to create layers with strict dimensionality.

There’s an ndim parameter I’ve seen in the base layer class (https://napari.org/docs/api/napari.layers.base.html?highlight=ndim), but I’m not clear if/how to use that.

Any help would be greatly appreciated!

1 Like

Hi @naten7k!

With points, the input data shape is npoints x ndim, so to get an empty layer of the right dimensionality, you pass in an empty “points” array of shape 0, ndim:

pts_layer = viewer.add_points(np.empty((0, 3)))  # 3D points

For shapes, the shape of the array is nshapes x npoints x ndim, so you actually need to pass in an array of shape (0, 0, ndim), and then you get the dimensionality you want:

shp_layer = viewer.add_shapes(np.empty((0, 0, 4)))  # 4D shapes layer
print(shp_layer.ndim)  # prints 4

Hopefully that gets you started. I must agree that this is not the most intuitive and certainly is not obvious in the current docs — I had to go look at the source code to figure it out! Perhaps an ndim keyword argument would be better.

1 Like

@naten7k I created an issue to discuss this option as well as other alternatives. Thanks for bringing this to our attention!

1 Like

Thanks @jni! This is perfect.

1 Like