11import warnings
22
33import numpy as np
4- from magicgui import magic_factory
54from napari_tools_menu import register_function
65import napari
76from napari .types import LayerDataTuple
87from typing import List
98from ._utils import compute_combined_slices
9+ from magicgui import magic_factory
1010
1111# This is the actual plugin function, where we export our function
1212# (The functions themselves are defined below)
1616def crop_region (
1717 layer : napari .layers .Layer ,
1818 shapes_layer : napari .layers .Shapes ,
19+ as_numpy : bool = False ,
20+ translate : bool = True ,
1921 viewer : 'napari.viewer.Viewer' = None ,
2022) -> List [LayerDataTuple ]:
21- """Crop regions in napari defined by shapes."""
23+ """Crop regions in napari defined by shapes.
24+
25+ Parameters
26+ ----------
27+ layer : napari.layers.Layer
28+ Layer to crop. Can be an image or labels layer.
29+ shapes_layer : napari.layers.Shapes
30+ Shapes layer defining the regions to crop.
31+ as_numpy : bool, optional
32+ If True, return the cropped data as numpy arrays. Default is False.
33+ translate : bool, optional
34+ If True, apply translation to the cropped data. Default is True.
35+ viewer : napari.viewer.Viewer, optional
36+ Viewer instance to use for the dimensions order.
37+
38+ Returns
39+ -------
40+
41+ """
2242 if shapes_layer is None :
2343 shapes_layer .mode = "add_rectangle"
2444 warnings .warn ("Please annotate a region to crop." )
@@ -126,7 +146,8 @@ def crop_region(
126146 # Pixels belonging to the bounding box are in the half-open interval [min_row; max_row) and [min_col; max_col).
127147 new_layer_props ['metadata' ] = {'bbox' : tuple (start + stop )}
128148 # apply layer translation scaled by layer scaling factor
129- new_layer_props ['translate' ] = tuple (np .asarray (tuple (start )) * np .asarray (layer_props ['scale' ]))
149+ if translate :
150+ new_layer_props ['translate' ] = tuple (np .asarray (tuple (start )) * np .asarray (layer_props ['scale' ]))
130151
131152 # If layer name is in viewer or is about to be added,
132153 # increment layer name until it has a different name
@@ -139,6 +160,8 @@ def crop_region(
139160 new_layer_index += 1
140161 new_layer_props ["name" ] = new_name
141162 names_list .append (new_name )
163+ if as_numpy :
164+ cropped_data = np .asarray (cropped_data )
142165 cropped_list .append ((cropped_data , new_layer_props , layer_type ))
143166 return cropped_list
144167
@@ -203,3 +226,67 @@ def cut_with_plane(image_to_be_cut, plane_normal, plane_position, positive_cut=T
203226 if crop :
204227 image_cut = trim_zeros (image_cut )
205228 return image_cut
229+
230+ @magic_factory (
231+ call_button = "Draw" ,
232+ shape_type = {"choices" : ["rectangle" , "ellipse" ]}, # Dropdown for shape type
233+ shape_size_x = {"widget_type" : "SpinBox" , "min" : 1 , "max" : 5000 , "step" : 1 },
234+ shape_size_y = {"widget_type" : "SpinBox" , "min" : 1 , "max" : 5000 , "step" : 1 },
235+ )
236+ def draw_fixed_shapes (
237+ points : napari .types .PointsData ,
238+ shape_type : str = "rectangle" ,
239+ shape_size_x : int = 256 ,
240+ shape_size_y : int = 256 ,
241+ viewer : napari .Viewer = None ,
242+ ) -> napari .layers .Shapes :
243+ """Create shapes of fixed size at points layer coordinates.
244+
245+ Parameters
246+ ----------
247+ points : napari.types.PointsData
248+ Coordinates of the points layer.
249+ shape_type : str
250+ Type of shape to create. Can be 'rectangle' or 'ellipse'.
251+ shape_size_x : int
252+ Width of the shape.
253+ shape_size_y : int
254+ Height of the shape.
255+ viewer : napari.Viewer, optional
256+ Viewer instance to use for the dimensions order.
257+
258+ Returns
259+ -------
260+ Shapes
261+ Shapes layer with the created shapes."""
262+ if points is None :
263+ raise ValueError ("No points provided. Please select a points layer." )
264+ dims_order = tuple (range (points .ndim ))
265+ if viewer is not None :
266+ dims_order = viewer .dims .order
267+ shape_size = (shape_size_y , shape_size_x )
268+ odd_shape = [size % 2 for size in shape_size ]
269+
270+ shapes_data = []
271+ for coord in points :
272+ shape_data = np .array ([
273+ [coord [dims_order [- 2 ]] - (shape_size [- 2 ] // 2 ), coord [dims_order [- 1 ]] - (shape_size [- 1 ] // 2 )], # Top-left
274+ [coord [dims_order [- 2 ]] - (shape_size [- 2 ] // 2 ), coord [dims_order [- 1 ]] + (shape_size [- 1 ] // 2 ) + odd_shape [- 1 ]], # Bottom-left
275+ [coord [dims_order [- 2 ]] + (shape_size [- 2 ] // 2 ) + odd_shape [- 2 ], coord [dims_order [- 1 ]] + (shape_size [- 1 ] // 2 ) + odd_shape [- 1 ]], # Bottom-right
276+ [coord [dims_order [- 2 ]] + (shape_size [- 2 ] // 2 ) + odd_shape [- 2 ], coord [dims_order [- 1 ]] - (shape_size [- 1 ] // 2 )], # Top-right
277+ ])
278+ # Insert extra coordinates for higher dimensions
279+ # For example, if the shape is 3D, we need to add the z-coordinates
280+ extra_coords = np .take (coord , indices = dims_order [:- 2 ], axis = 0 )
281+ for i , ec in enumerate (extra_coords ):
282+ shape_data = np .insert (shape_data , 0 , round (ec ), axis = - 1 )
283+ shape_data = shape_data [:, np .argsort (dims_order )]
284+ shapes_data .append (shape_data )
285+
286+ return napari .layers .Shapes (
287+ data = shapes_data ,
288+ shape_type = [shape_type for _ in points ],
289+ edge_color = 'magenta' ,
290+ face_color = '#ffff0080' , # semi-transparent yellow
291+ edge_width = 2 ,
292+ )
0 commit comments