Coordinate systems in napari¶
In napari, there are three main coordinate systems: (1) canvas, (2) world, and (3) layer. The canvas coordinates system is the 2D coordinate system of the canvas on which the scene is rendered. World coordinates are the nD coordinates of the entire scene. As the name suggests, layer coordinates are the nD coordinate system of the data in a given layer. Layer coordinates are specific to each layer’s data and are related to the world coordinate system via the layer transforms.
In 3D mode, clicks are lines¶
Since the 3D scene is rendered on a 2D surface (your screen), your mouse click does not map to a specific point in space. As the view is a parallel projection, napari can determine a line through 3D space that intersects the canvas where the user clicked.
When a user clicks or moves the mouse in the canvas, napari emits a mouse event with the following properties:
pos: the position of the click in canvas coordinates.
position: the position of the click in world coordinates. The point is located at the intersection of the click line (
view_direction) and a plane parallel to the camera plane (i.e,. a plane normal to
view_direction: a unit vector giving the direction of the camera in world coordinates.
dims_displayed: a list of the dimensions currently being displayed in the viewer. This comes from
dims_point: the indices for the data in view in world coordinates. This comes from
Determining where the click intersects the data¶
Each napari layer has a method called
get_ray_intersections() that will return the points on the data bounding box that a given line will intersect (
end_point ). When the click line (
view_direction) and position (
position) are used as inputs,
end_point are the end points of the segment click line that intersects the layer’s axis-alinged data bounding box.
start_point is the end point that is closest to the camera (i.e, the “first” intersection) and
end_point is the end point that is farthest from the camera (i.e., the “last” intersection). You can use the line segment between
end_point to interrogate the layer data that is “under” your cursor.
get_ray_intersection() docstrings below for details. Note that if the line does not intersect the data bounding box (i.e., the click was outside of the data),
view_direction should be provided as world coordinates if
world is set to True and in layer coordinates if
world is set to
def get_ray_intersections( self, position: List[float], view_direction: np.ndarray, dims_displayed: List[int], world: bool = True, ) -> Union[Tuple[np.ndarray, np.ndarray], Tuple[None, None]]: """Get the start and end point for the ray extending from a point through the data bounding box. Parameters ---------- position : the position of the point in nD coordinates. World vs. data is set by the world keyword argument. view_direction : np.ndarray a unit vector giving the direction of the ray in nD coordinates. World vs. data is set by the world keyword argument. dims_displayed : a list of the dimensions currently being displayed in the viewer. world : bool True if the provided coordinates are in world coordinates. Default value is True. Returns ------- start_point : Optional[np.ndarray] The point on the axis-aligned data bounding box that the cursor click intersects with. This is the point closest to the camera. The point is the full nD coordinates of the layer data. If the click does not intersect the axis-aligned data bounding box, None is returned. end_point : Optional[np.ndarray] The point on the axis-aligned data bounding box that the cursor click intersects with. This is the point farthest from the camera. The point is the full nD coordinates of the layer data. If the click does not intersect the axis-aligned data bounding box, None is returned. """
Adding 3D interactivity via mouse events¶
Custom 3D interactivity can be added via mouse callbacks. The
layer.get_ray_intersections() function has been designed to work seamlessly with the napari mouse callback event. You can pass the mouse callback event properties to
layer.get_ray_intersections() get the
end_point of where the click line intersects the layer data.
@layer.mouse_drag_callbacks.append def on_click(layer, event): # get the points where the click intersects the start_point, end_point = layer.get_ray_intersections( position=event.position, view_direction=event.view_direction, dims_displayed=event.dims_displayed, world=True ) if (start_point is not None) and (end_point is not None): # use start_point and end_point to interrogate layer data
For an example implementation, see
Getting the layer data under the cursor¶
There are convenience methods in the layer objects (
layer.get_value()) to get the layer data value underneath the cursor that is “on top” (i.e., closest to
layer.get_value() takes the click position, view direction, dims_displayed in either world or layer coordinates (see
world argument) as input. Thus, it can be easily integrated into a mouse event callback. Note that
None if the layer is not currently visible. See the docstring below for details.
def get_value( self, position, *, view_direction: Optional[np.ndarray] = None, dims_displayed: Optional[List[int]] = None, world=False, ): """Value of the data at a position. If the layer is not visible, return None. Parameters ---------- position : tuple Position in either data or world coordinates. view_direction : Optional[np.ndarray] A unit vector giving the direction of the ray in nD world coordinates. The default value is None. dims_displayed : Optional[List[int]] A list of the dimensions currently being displayed in the viewer. The default value is None. world : bool If True the position is taken to be in world coordinates and converted into data coordinates. False by default. Returns ------- value : tuple, None Value of the data. If the layer is not visible return None.
Progress on the implementation of
layer.get_value() can be found at #3187. Layers for which this is not yet implemented will return
layer.get_value() is called whilst the viewer is in a 3D rendering mode.