copick-utils 0.6.1__py3-none-any.whl → 1.0.0__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.
- copick_utils/__init__.py +1 -1
- copick_utils/cli/__init__.py +33 -0
- copick_utils/cli/clipmesh.py +161 -0
- copick_utils/cli/clippicks.py +154 -0
- copick_utils/cli/clipseg.py +163 -0
- copick_utils/cli/conversion_commands.py +32 -0
- copick_utils/cli/enclosed.py +191 -0
- copick_utils/cli/filter_components.py +166 -0
- copick_utils/cli/fit_spline.py +191 -0
- copick_utils/cli/hull.py +138 -0
- copick_utils/cli/input_output_selection.py +76 -0
- copick_utils/cli/logical_commands.py +29 -0
- copick_utils/cli/mesh2picks.py +170 -0
- copick_utils/cli/mesh2seg.py +167 -0
- copick_utils/cli/meshop.py +262 -0
- copick_utils/cli/picks2ellipsoid.py +171 -0
- copick_utils/cli/picks2mesh.py +181 -0
- copick_utils/cli/picks2plane.py +156 -0
- copick_utils/cli/picks2seg.py +134 -0
- copick_utils/cli/picks2sphere.py +170 -0
- copick_utils/cli/picks2surface.py +164 -0
- copick_utils/cli/picksin.py +146 -0
- copick_utils/cli/picksout.py +148 -0
- copick_utils/cli/processing_commands.py +18 -0
- copick_utils/cli/seg2mesh.py +135 -0
- copick_utils/cli/seg2picks.py +128 -0
- copick_utils/cli/segop.py +248 -0
- copick_utils/cli/separate_components.py +155 -0
- copick_utils/cli/skeletonize.py +164 -0
- copick_utils/cli/util.py +580 -0
- copick_utils/cli/validbox.py +155 -0
- copick_utils/converters/__init__.py +35 -0
- copick_utils/converters/converter_common.py +543 -0
- copick_utils/converters/ellipsoid_from_picks.py +335 -0
- copick_utils/converters/lazy_converter.py +576 -0
- copick_utils/converters/mesh_from_picks.py +209 -0
- copick_utils/converters/mesh_from_segmentation.py +119 -0
- copick_utils/converters/picks_from_mesh.py +542 -0
- copick_utils/converters/picks_from_segmentation.py +168 -0
- copick_utils/converters/plane_from_picks.py +251 -0
- copick_utils/converters/segmentation_from_mesh.py +291 -0
- copick_utils/{segmentation → converters}/segmentation_from_picks.py +123 -13
- copick_utils/converters/sphere_from_picks.py +306 -0
- copick_utils/converters/surface_from_picks.py +337 -0
- copick_utils/logical/__init__.py +43 -0
- copick_utils/logical/distance_operations.py +604 -0
- copick_utils/logical/enclosed_operations.py +222 -0
- copick_utils/logical/mesh_operations.py +443 -0
- copick_utils/logical/point_operations.py +303 -0
- copick_utils/logical/segmentation_operations.py +399 -0
- copick_utils/process/__init__.py +47 -0
- copick_utils/process/connected_components.py +360 -0
- copick_utils/process/filter_components.py +306 -0
- copick_utils/process/hull.py +106 -0
- copick_utils/process/skeletonize.py +326 -0
- copick_utils/process/spline_fitting.py +648 -0
- copick_utils/process/validbox.py +333 -0
- copick_utils/util/__init__.py +6 -0
- copick_utils/util/config_models.py +614 -0
- {copick_utils-0.6.1.dist-info → copick_utils-1.0.0.dist-info}/METADATA +15 -2
- copick_utils-1.0.0.dist-info/RECORD +71 -0
- copick_utils-1.0.0.dist-info/entry_points.txt +29 -0
- copick_utils/segmentation/picks_from_segmentation.py +0 -81
- copick_utils-0.6.1.dist-info/RECORD +0 -14
- /copick_utils/{segmentation → io}/__init__.py +0 -0
- {copick_utils-0.6.1.dist-info → copick_utils-1.0.0.dist-info}/WHEEL +0 -0
- {copick_utils-0.6.1.dist-info → copick_utils-1.0.0.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
|
+
)
|