copick-utils 0.6.1__py3-none-any.whl → 1.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. copick_utils/__init__.py +1 -1
  2. copick_utils/cli/__init__.py +33 -0
  3. copick_utils/cli/clipmesh.py +161 -0
  4. copick_utils/cli/clippicks.py +154 -0
  5. copick_utils/cli/clipseg.py +163 -0
  6. copick_utils/cli/conversion_commands.py +32 -0
  7. copick_utils/cli/enclosed.py +191 -0
  8. copick_utils/cli/filter_components.py +166 -0
  9. copick_utils/cli/fit_spline.py +191 -0
  10. copick_utils/cli/hull.py +138 -0
  11. copick_utils/cli/input_output_selection.py +76 -0
  12. copick_utils/cli/logical_commands.py +29 -0
  13. copick_utils/cli/mesh2picks.py +170 -0
  14. copick_utils/cli/mesh2seg.py +167 -0
  15. copick_utils/cli/meshop.py +262 -0
  16. copick_utils/cli/picks2ellipsoid.py +171 -0
  17. copick_utils/cli/picks2mesh.py +181 -0
  18. copick_utils/cli/picks2plane.py +156 -0
  19. copick_utils/cli/picks2seg.py +134 -0
  20. copick_utils/cli/picks2sphere.py +170 -0
  21. copick_utils/cli/picks2surface.py +164 -0
  22. copick_utils/cli/picksin.py +146 -0
  23. copick_utils/cli/picksout.py +148 -0
  24. copick_utils/cli/processing_commands.py +18 -0
  25. copick_utils/cli/seg2mesh.py +135 -0
  26. copick_utils/cli/seg2picks.py +128 -0
  27. copick_utils/cli/segop.py +248 -0
  28. copick_utils/cli/separate_components.py +155 -0
  29. copick_utils/cli/skeletonize.py +164 -0
  30. copick_utils/cli/util.py +580 -0
  31. copick_utils/cli/validbox.py +155 -0
  32. copick_utils/converters/__init__.py +35 -0
  33. copick_utils/converters/converter_common.py +543 -0
  34. copick_utils/converters/ellipsoid_from_picks.py +335 -0
  35. copick_utils/converters/lazy_converter.py +576 -0
  36. copick_utils/converters/mesh_from_picks.py +209 -0
  37. copick_utils/converters/mesh_from_segmentation.py +119 -0
  38. copick_utils/converters/picks_from_mesh.py +542 -0
  39. copick_utils/converters/picks_from_segmentation.py +168 -0
  40. copick_utils/converters/plane_from_picks.py +251 -0
  41. copick_utils/converters/segmentation_from_mesh.py +291 -0
  42. copick_utils/{segmentation → converters}/segmentation_from_picks.py +123 -13
  43. copick_utils/converters/sphere_from_picks.py +306 -0
  44. copick_utils/converters/surface_from_picks.py +337 -0
  45. copick_utils/logical/__init__.py +43 -0
  46. copick_utils/logical/distance_operations.py +604 -0
  47. copick_utils/logical/enclosed_operations.py +222 -0
  48. copick_utils/logical/mesh_operations.py +443 -0
  49. copick_utils/logical/point_operations.py +303 -0
  50. copick_utils/logical/segmentation_operations.py +399 -0
  51. copick_utils/process/__init__.py +47 -0
  52. copick_utils/process/connected_components.py +360 -0
  53. copick_utils/process/filter_components.py +306 -0
  54. copick_utils/process/hull.py +106 -0
  55. copick_utils/process/skeletonize.py +326 -0
  56. copick_utils/process/spline_fitting.py +648 -0
  57. copick_utils/process/validbox.py +333 -0
  58. copick_utils/util/__init__.py +6 -0
  59. copick_utils/util/config_models.py +614 -0
  60. {copick_utils-0.6.1.dist-info → copick_utils-1.0.1.dist-info}/METADATA +15 -2
  61. copick_utils-1.0.1.dist-info/RECORD +71 -0
  62. {copick_utils-0.6.1.dist-info → copick_utils-1.0.1.dist-info}/WHEEL +1 -1
  63. copick_utils-1.0.1.dist-info/entry_points.txt +29 -0
  64. copick_utils/segmentation/picks_from_segmentation.py +0 -81
  65. copick_utils-0.6.1.dist-info/RECORD +0 -14
  66. /copick_utils/{segmentation → io}/__init__.py +0 -0
  67. {copick_utils-0.6.1.dist-info → copick_utils-1.0.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,209 @@
1
+ from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple
2
+
3
+ import numpy as np
4
+ import trimesh as tm
5
+ from copick.util.log import get_logger
6
+
7
+ from copick_utils.converters.converter_common import (
8
+ cluster,
9
+ create_batch_converter,
10
+ create_batch_worker,
11
+ store_mesh_with_stats,
12
+ validate_points,
13
+ )
14
+ from copick_utils.converters.lazy_converter import create_lazy_batch_converter
15
+
16
+ if TYPE_CHECKING:
17
+ from copick.models import CopickMesh, CopickRun
18
+
19
+ logger = get_logger(__name__)
20
+
21
+
22
+ def convex_hull_mesh(points: np.ndarray) -> tm.Trimesh:
23
+ """Create a convex hull mesh from points.
24
+
25
+ Args:
26
+ points: Nx3 array of points.
27
+
28
+ Returns:
29
+ Trimesh object representing the convex hull.
30
+ """
31
+ if len(points) < 4:
32
+ raise ValueError("Need at least 4 points to create a convex hull")
33
+
34
+ # Use trimesh's convex hull function instead of scipy
35
+ hull_mesh = tm.convex.convex_hull(points)
36
+ return hull_mesh
37
+
38
+
39
+ def alpha_shape_mesh(points: np.ndarray, alpha: float) -> tm.Trimesh:
40
+ """Create an alpha shape mesh from points.
41
+
42
+ Args:
43
+ points: Nx3 array of points.
44
+ alpha: Alpha parameter controlling the shape detail.
45
+
46
+ Returns:
47
+ Trimesh object representing the alpha shape.
48
+ """
49
+ logger.warning("Alpha shape mode is currently disabled, falling back to convex hull")
50
+ return convex_hull_mesh(points)
51
+
52
+
53
+ def mesh_from_picks(
54
+ points: np.ndarray,
55
+ run: "CopickRun",
56
+ object_name: str,
57
+ session_id: str,
58
+ user_id: str,
59
+ mesh_type: str = "convex_hull",
60
+ alpha: Optional[float] = None,
61
+ use_clustering: bool = False,
62
+ clustering_method: str = "dbscan",
63
+ clustering_params: Optional[Dict[str, Any]] = None,
64
+ all_clusters: bool = False,
65
+ individual_meshes: bool = False,
66
+ session_id_template: Optional[str] = None,
67
+ ) -> Optional[Tuple["CopickMesh", Dict[str, int]]]:
68
+ """Create mesh(es) from pick points.
69
+
70
+ Args:
71
+ points: Nx3 array of pick positions.
72
+ run: Copick run object.
73
+ object_name: Name of the mesh object.
74
+ session_id: Session ID for the mesh.
75
+ user_id: User ID for the mesh.
76
+ mesh_type: Type of mesh to create ('convex_hull', 'alpha_shape').
77
+ alpha: Alpha parameter for alpha shapes (required if mesh_type='alpha_shape').
78
+ use_clustering: Whether to cluster points first.
79
+ clustering_method: Clustering method ('dbscan', 'kmeans').
80
+ clustering_params: Parameters for clustering.
81
+ e.g.
82
+ - {'eps': 5.0, 'min_samples': 3} for DBSCAN
83
+ - {'n_clusters': 3} for KMeans
84
+ all_clusters: If True, use all clusters; if False, use only the largest cluster.
85
+ individual_meshes: If True, create separate mesh objects for each mesh.
86
+ session_id_template: Template for individual mesh session IDs.
87
+
88
+ Returns:
89
+ Tuple of (CopickMesh object, stats dict) or None if creation failed.
90
+ Stats dict contains 'vertices_created' and 'faces_created' totals.
91
+ """
92
+ if not validate_points(points, 4, "mesh"):
93
+ return None
94
+
95
+ if clustering_params is None:
96
+ clustering_params = {}
97
+
98
+ # Define mesh creation function
99
+ def create_mesh_from_points(cluster_points):
100
+ if mesh_type == "convex_hull":
101
+ return convex_hull_mesh(cluster_points)
102
+ elif mesh_type == "alpha_shape":
103
+ if alpha is None:
104
+ raise ValueError("Alpha parameter is required for alpha shapes")
105
+ return alpha_shape_mesh(cluster_points, alpha)
106
+ else:
107
+ raise ValueError(f"Unknown mesh type: {mesh_type}")
108
+
109
+ # Handle clustering workflow with special mesh logic
110
+ if use_clustering:
111
+ point_clusters = cluster(
112
+ points,
113
+ clustering_method,
114
+ min_points_per_cluster=4, # Meshes need at least 4 points
115
+ **clustering_params,
116
+ )
117
+
118
+ if not point_clusters:
119
+ logger.warning("No valid clusters found")
120
+ return None
121
+
122
+ logger.info(f"Found {len(point_clusters)} clusters")
123
+
124
+ if all_clusters and len(point_clusters) > 1:
125
+ if individual_meshes:
126
+ # Create separate mesh objects for each mesh
127
+ created_meshes = []
128
+ total_vertices = 0
129
+ total_faces = 0
130
+
131
+ for i, cluster_points in enumerate(point_clusters):
132
+ try:
133
+ cluster_mesh = create_mesh_from_points(cluster_points)
134
+
135
+ # Generate session ID using template if provided
136
+ if session_id_template:
137
+ mesh_session_id = session_id_template.format(
138
+ base_session_id=session_id,
139
+ instance_id=i,
140
+ )
141
+ else:
142
+ mesh_session_id = f"{session_id}-{i:03d}"
143
+
144
+ copick_mesh = run.new_mesh(object_name, mesh_session_id, user_id, exist_ok=True)
145
+ copick_mesh.mesh = cluster_mesh
146
+ copick_mesh.store()
147
+ created_meshes.append(copick_mesh)
148
+ total_vertices += len(cluster_mesh.vertices)
149
+ total_faces += len(cluster_mesh.faces)
150
+ logger.info(
151
+ f"Created individual mesh {i} with {len(cluster_mesh.vertices)} vertices",
152
+ )
153
+ except Exception as e:
154
+ logger.error(f"Failed to create mesh {i}: {e}")
155
+ continue
156
+
157
+ # Return the first mesh and total stats
158
+ if created_meshes:
159
+ stats = {"vertices_created": total_vertices, "faces_created": total_faces}
160
+ return created_meshes[0], stats
161
+ else:
162
+ return None
163
+ else:
164
+ # Create meshes from all clusters and combine them
165
+ all_meshes = []
166
+ for cluster_points in point_clusters:
167
+ cluster_mesh = create_mesh_from_points(cluster_points)
168
+ all_meshes.append(cluster_mesh)
169
+
170
+ # Combine all meshes
171
+ combined_mesh = tm.util.concatenate(all_meshes)
172
+ else:
173
+ # Use largest cluster
174
+ cluster_sizes = [len(cluster) for cluster in point_clusters]
175
+ largest_cluster_idx = np.argmax(cluster_sizes)
176
+ points_to_use = point_clusters[largest_cluster_idx]
177
+ logger.info(f"Using largest cluster with {len(points_to_use)} points")
178
+
179
+ combined_mesh = create_mesh_from_points(points_to_use)
180
+ else:
181
+ # Use all points without clustering
182
+ combined_mesh = create_mesh_from_points(points)
183
+
184
+ # Store mesh and return stats
185
+ try:
186
+ return store_mesh_with_stats(run, combined_mesh, object_name, session_id, user_id, "mesh")
187
+ except Exception as e:
188
+ logger.critical(f"Error creating mesh: {e}")
189
+ return None
190
+
191
+
192
+ # Create worker function using common infrastructure
193
+ _mesh_from_picks_worker = create_batch_worker(mesh_from_picks, "mesh", "picks", min_points=4)
194
+
195
+
196
+ # Create batch converter using common infrastructure
197
+ mesh_from_picks_batch = create_batch_converter(
198
+ mesh_from_picks,
199
+ "Converting picks to meshes",
200
+ "mesh",
201
+ "picks",
202
+ min_points=4,
203
+ )
204
+
205
+ # Lazy batch converter for new architecture
206
+ mesh_from_picks_lazy_batch = create_lazy_batch_converter(
207
+ converter_func=mesh_from_picks,
208
+ task_description="Converting picks to meshes",
209
+ )
@@ -0,0 +1,119 @@
1
+ """Convert segmentation volumes to meshes."""
2
+
3
+ from typing import TYPE_CHECKING, Dict, Optional, Tuple
4
+
5
+ import numpy as np
6
+ import trimesh as tm
7
+ from copick.util.log import get_logger
8
+
9
+ from copick_utils.converters.converter_common import (
10
+ create_batch_converter,
11
+ create_batch_worker,
12
+ store_mesh_with_stats,
13
+ )
14
+ from copick_utils.converters.lazy_converter import create_lazy_batch_converter
15
+
16
+ if TYPE_CHECKING:
17
+ from copick.models import CopickMesh, CopickRun, CopickSegmentation
18
+
19
+ logger = get_logger(__name__)
20
+
21
+
22
+ def volume_to_mesh(volume: np.ndarray, voxel_spacing: float, level: float = 0.5, step_size: int = 1) -> tm.Trimesh:
23
+ """
24
+ Convert a binary volume to a mesh using marching cubes.
25
+
26
+ Args:
27
+ volume: Binary volume array with shape (z, y, x)
28
+ voxel_spacing: Spacing between voxels in physical units
29
+ level: Isosurface level for marching cubes
30
+ step_size: Step size for marching cubes (higher = coarser mesh)
31
+
32
+ Returns:
33
+ Trimesh object representing the mesh
34
+ """
35
+ from skimage import measure
36
+
37
+ # Generate mesh using marching cubes
38
+ verts, faces, normals, values = measure.marching_cubes(
39
+ volume.astype(float),
40
+ level=level,
41
+ step_size=step_size,
42
+ spacing=(voxel_spacing, voxel_spacing, voxel_spacing),
43
+ )
44
+
45
+ # Create trimesh object
46
+ mesh = tm.Trimesh(vertices=verts, faces=faces, vertex_normals=normals)
47
+
48
+ return mesh
49
+
50
+
51
+ def mesh_from_segmentation(
52
+ segmentation: "CopickSegmentation",
53
+ run: "CopickRun",
54
+ object_name: str,
55
+ session_id: str,
56
+ user_id: str,
57
+ level: float = 0.5,
58
+ step_size: int = 1,
59
+ ) -> Optional[Tuple["CopickMesh", Dict[str, int]]]:
60
+ """
61
+ Convert a CopickSegmentation to a mesh.
62
+
63
+ Args:
64
+ segmentation: CopickSegmentation object to convert
65
+ run: CopickRun object
66
+ object_name: Name for the output mesh object
67
+ session_id: Session ID for the output mesh
68
+ user_id: User ID for the output mesh
69
+ level: Isosurface level for marching cubes
70
+ step_size: Step size for marching cubes
71
+
72
+ Returns:
73
+ Tuple of (CopickMesh object, stats dict) or None if creation failed.
74
+ Stats dict contains 'vertices_created' and 'faces_created'.
75
+ """
76
+ try:
77
+ # Load the volume from the segmentation
78
+ volume = segmentation.numpy()
79
+
80
+ if volume is None or volume.size == 0:
81
+ logger.error("Empty or invalid volume")
82
+ return None
83
+
84
+ # Get voxel spacing from segmentation
85
+ voxel_spacing = segmentation.voxel_size
86
+
87
+ # Convert volume to mesh
88
+ mesh = volume_to_mesh(volume, voxel_spacing, level, step_size)
89
+
90
+ if mesh.vertices.size == 0:
91
+ logger.error("Empty mesh generated")
92
+ return None
93
+
94
+ # Store mesh and return stats
95
+ return store_mesh_with_stats(run, mesh, object_name, session_id, user_id, "mesh")
96
+
97
+ except Exception as e:
98
+ logger.error(f"Error creating mesh: {e}")
99
+ return None
100
+
101
+
102
+ # Create worker function using common infrastructure
103
+ _mesh_from_segmentation_worker = create_batch_worker(mesh_from_segmentation, "mesh", "segmentation", min_points=0)
104
+
105
+
106
+ # Create batch converter using common infrastructure
107
+ mesh_from_segmentation_batch = create_batch_converter(
108
+ mesh_from_segmentation,
109
+ "Converting segmentations to meshes",
110
+ "mesh",
111
+ "segmentation",
112
+ min_points=0,
113
+ )
114
+
115
+ # Lazy batch converter for new architecture
116
+ mesh_from_segmentation_lazy_batch = create_lazy_batch_converter(
117
+ converter_func=mesh_from_segmentation,
118
+ task_description="Converting segmentations to meshes",
119
+ )