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.
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.0.dist-info}/METADATA +15 -2
  61. copick_utils-1.0.0.dist-info/RECORD +71 -0
  62. copick_utils-1.0.0.dist-info/entry_points.txt +29 -0
  63. copick_utils/segmentation/picks_from_segmentation.py +0 -81
  64. copick_utils-0.6.1.dist-info/RECORD +0 -14
  65. /copick_utils/{segmentation → io}/__init__.py +0 -0
  66. {copick_utils-0.6.1.dist-info → copick_utils-1.0.0.dist-info}/WHEEL +0 -0
  67. {copick_utils-0.6.1.dist-info → copick_utils-1.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,156 @@
1
+ import click
2
+ import copick
3
+ from click_option_group import optgroup
4
+ from copick.cli.util import add_config_option, add_debug_option
5
+ from copick.util.log import get_logger
6
+ from copick.util.uri import parse_copick_uri
7
+
8
+ from copick_utils.cli.util import (
9
+ add_clustering_options,
10
+ add_input_option,
11
+ add_output_option,
12
+ add_workers_option,
13
+ )
14
+ from copick_utils.util.config_models import create_simple_config
15
+
16
+
17
+ @click.command(
18
+ context_settings={"show_default": True},
19
+ short_help="Convert picks to plane meshes.",
20
+ no_args_is_help=True,
21
+ )
22
+ @add_config_option
23
+ @optgroup.group("\nInput Options", help="Options related to the input picks.")
24
+ @optgroup.option(
25
+ "--run-names",
26
+ "-r",
27
+ multiple=True,
28
+ help="Specific run names to process (default: all runs).",
29
+ )
30
+ @add_input_option("picks")
31
+ @optgroup.group("\nTool Options", help="Options related to this tool.")
32
+ @optgroup.option(
33
+ "--padding",
34
+ type=float,
35
+ default=1.2,
36
+ help="Padding factor for plane size (1.0=exact fit, >1.0=larger plane).",
37
+ )
38
+ @add_clustering_options
39
+ @add_workers_option
40
+ @optgroup.group("\nOutput Options", help="Options related to output meshes.")
41
+ @add_output_option("mesh", default_tool="picks2plane")
42
+ @optgroup.option(
43
+ "--individual-meshes/--no-individual-meshes",
44
+ "-im",
45
+ is_flag=True,
46
+ default=False,
47
+ help="Create individual meshes for each instance (enables {instance_id} placeholder).",
48
+ )
49
+ @add_debug_option
50
+ def picks2plane(
51
+ config,
52
+ run_names,
53
+ input_uri,
54
+ use_clustering,
55
+ clustering_method,
56
+ clustering_eps,
57
+ clustering_min_samples,
58
+ clustering_n_clusters,
59
+ padding,
60
+ all_clusters,
61
+ workers,
62
+ output_uri,
63
+ individual_meshes,
64
+ debug,
65
+ ):
66
+ """
67
+ Convert picks to plane meshes.
68
+
69
+ \b
70
+ URI Format:
71
+ Picks: object_name:user_id/session_id
72
+ Meshes: object_name:user_id/session_id
73
+
74
+ \b
75
+ Examples:
76
+ # Convert single pick set to single plane mesh
77
+ copick convert picks2plane -i "membrane:user1/manual-001" -o "membrane:picks2plane/plane-001"
78
+
79
+ # Create individual plane meshes from clusters
80
+ copick convert picks2plane -i "membrane:user1/manual-001" -o "membrane:picks2plane/plane-{instance_id}" --individual-meshes
81
+
82
+ # Convert all manual picks using pattern matching
83
+ copick convert picks2plane -i "membrane:user1/manual-.*" -o "membrane:picks2plane/plane-{input_session_id}"
84
+ """
85
+ from copick_utils.converters.plane_from_picks import plane_from_picks_lazy_batch
86
+
87
+ logger = get_logger(__name__, debug=debug)
88
+
89
+ root = copick.from_file(config)
90
+ run_names_list = list(run_names) if run_names else None
91
+
92
+ # Create config directly from URIs with smart defaults
93
+ try:
94
+ task_config = create_simple_config(
95
+ input_uri=input_uri,
96
+ input_type="picks",
97
+ output_uri=output_uri,
98
+ output_type="mesh",
99
+ individual_outputs=individual_meshes,
100
+ command_name="picks2plane",
101
+ )
102
+ except ValueError as e:
103
+ raise click.BadParameter(str(e)) from e
104
+
105
+ # Extract parameters for logging
106
+ input_params = parse_copick_uri(input_uri, "picks")
107
+ output_params = parse_copick_uri(output_uri, "mesh")
108
+
109
+ # Prepare clustering parameters
110
+ clustering_params = {}
111
+ if clustering_method == "dbscan":
112
+ clustering_params = {"eps": clustering_eps, "min_samples": clustering_min_samples}
113
+ elif clustering_method == "kmeans":
114
+ clustering_params = {"n_clusters": clustering_n_clusters}
115
+
116
+ logger.info(f"Converting picks to plane mesh for object '{input_params['object_name']}'")
117
+ logger.info(f"Source picks pattern: {input_params['user_id']}/{input_params['session_id']}")
118
+ logger.info(
119
+ f"Target mesh template: {output_params['object_name']} ({output_params['user_id']}/{output_params['session_id']})",
120
+ )
121
+
122
+ # Parallel discovery and processing - no sequential bottleneck!
123
+ results = plane_from_picks_lazy_batch(
124
+ root=root,
125
+ config=task_config,
126
+ run_names=run_names_list,
127
+ workers=workers,
128
+ use_clustering=use_clustering,
129
+ clustering_method=clustering_method,
130
+ clustering_params=clustering_params,
131
+ padding=padding,
132
+ all_clusters=all_clusters,
133
+ )
134
+
135
+ successful = sum(1 for result in results.values() if result and result.get("processed", 0) > 0)
136
+ total_vertices = sum(result.get("vertices_created", 0) for result in results.values() if result)
137
+ total_faces = sum(result.get("faces_created", 0) for result in results.values() if result)
138
+ total_processed = sum(result.get("processed", 0) for result in results.values() if result)
139
+
140
+ # Collect all errors
141
+ all_errors = []
142
+ for result in results.values():
143
+ if result and result.get("errors"):
144
+ all_errors.extend(result["errors"])
145
+
146
+ logger.info(f"Completed: {successful}/{len(results)} runs processed successfully")
147
+ logger.info(f"Total conversion tasks completed: {total_processed}")
148
+ logger.info(f"Total vertices created: {total_vertices}")
149
+ logger.info(f"Total faces created: {total_faces}")
150
+
151
+ if all_errors:
152
+ logger.warning(f"Encountered {len(all_errors)} errors during processing")
153
+ for error in all_errors[:5]: # Show first 5 errors
154
+ logger.warning(f" - {error}")
155
+ if len(all_errors) > 5:
156
+ logger.warning(f" ... and {len(all_errors) - 5} more errors")
@@ -0,0 +1,134 @@
1
+ import click
2
+ import copick
3
+ from click_option_group import optgroup
4
+ from copick.cli.util import add_config_option, add_debug_option
5
+ from copick.util.log import get_logger
6
+ from copick.util.uri import parse_copick_uri
7
+
8
+ from copick_utils.cli.util import (
9
+ add_input_option,
10
+ add_output_option,
11
+ add_picks_painting_options,
12
+ add_workers_option,
13
+ )
14
+ from copick_utils.util.config_models import create_simple_config
15
+
16
+
17
+ @click.command(
18
+ context_settings={"show_default": True},
19
+ short_help="Convert picks to segmentation.",
20
+ no_args_is_help=True,
21
+ )
22
+ @add_config_option
23
+ @optgroup.group("\nInput Options", help="Options related to the input picks.")
24
+ @optgroup.option(
25
+ "--run-names",
26
+ "-r",
27
+ multiple=True,
28
+ help="Specific run names to process (default: all runs).",
29
+ )
30
+ @add_input_option("picks")
31
+ @optgroup.group("\nTool Options", help="Options related to this tool.")
32
+ @add_picks_painting_options
33
+ @optgroup.option(
34
+ "--tomo-type",
35
+ "-tt",
36
+ default="wbp",
37
+ help="Type of tomogram to use as reference.",
38
+ )
39
+ @add_workers_option
40
+ @optgroup.group("\nOutput Options", help="Options related to output segmentations.")
41
+ @add_output_option("segmentation", default_tool="picks2seg")
42
+ @add_debug_option
43
+ def picks2seg(
44
+ config,
45
+ run_names,
46
+ input_uri,
47
+ radius,
48
+ tomo_type,
49
+ workers,
50
+ output_uri,
51
+ debug,
52
+ ):
53
+ """
54
+ Convert picks to segmentation volumes by painting spheres.
55
+
56
+ \b
57
+ URI Format:
58
+ Picks: object_name:user_id/session_id
59
+ Segmentations: name:user_id/session_id@voxel_spacing
60
+
61
+ \b
62
+ Examples:
63
+ # Convert single pick set to segmentation
64
+ copick convert picks2seg -i "ribosome:user1/manual-001" -o "ribosome:picks2seg/painted-001@10.0"
65
+
66
+ # Convert all manual picks using pattern matching
67
+ copick convert picks2seg -i "ribosome:user1/manual-.*" -o "ribosome:picks2seg/painted-{input_session_id}@10.0"
68
+ """
69
+ from copick_utils.converters.segmentation_from_picks import segmentation_from_picks_lazy_batch
70
+
71
+ logger = get_logger(__name__, debug=debug)
72
+
73
+ root = copick.from_file(config)
74
+ run_names_list = list(run_names) if run_names else None
75
+
76
+ # Create config directly from URIs with smart defaults
77
+ try:
78
+ task_config = create_simple_config(
79
+ input_uri=input_uri,
80
+ input_type="picks",
81
+ output_uri=output_uri,
82
+ output_type="segmentation",
83
+ command_name="picks2seg",
84
+ )
85
+ except ValueError as e:
86
+ raise click.BadParameter(str(e)) from e
87
+
88
+ # Extract parameters for logging
89
+ input_params = parse_copick_uri(input_uri, "picks")
90
+ output_params = parse_copick_uri(output_uri, "segmentation")
91
+
92
+ voxel_spacing = output_params["voxel_spacing"]
93
+ if isinstance(voxel_spacing, str):
94
+ voxel_spacing = float(voxel_spacing)
95
+
96
+ logger.info(f"Converting picks to segmentation for object '{input_params['object_name']}'")
97
+ logger.info(f"Source picks pattern: {input_params['user_id']}/{input_params['session_id']}")
98
+ logger.info(
99
+ f"Target segmentation template: {output_params['name']} ({output_params['user_id']}/{output_params['session_id']})",
100
+ )
101
+ logger.info(f"Sphere radius: {radius}, voxel spacing: {voxel_spacing}")
102
+
103
+ # Parallel discovery and processing - no sequential bottleneck!
104
+ results = segmentation_from_picks_lazy_batch(
105
+ root=root,
106
+ config=task_config,
107
+ run_names=run_names_list,
108
+ workers=workers,
109
+ radius=radius,
110
+ tomo_type=tomo_type,
111
+ )
112
+
113
+ successful = sum(1 for result in results.values() if result and result.get("processed", 0) > 0)
114
+ total_points = sum(result.get("points_converted", 0) for result in results.values() if result)
115
+ total_voxels = sum(result.get("voxels_created", 0) for result in results.values() if result)
116
+ total_processed = sum(result.get("processed", 0) for result in results.values() if result)
117
+
118
+ # Collect all errors
119
+ all_errors = []
120
+ for result in results.values():
121
+ if result and result.get("errors"):
122
+ all_errors.extend(result["errors"])
123
+
124
+ logger.info(f"Completed: {successful}/{len(results)} runs processed successfully")
125
+ logger.info(f"Total conversion tasks completed: {total_processed}")
126
+ logger.info(f"Total points converted: {total_points}")
127
+ logger.info(f"Total voxels created: {total_voxels}")
128
+
129
+ if all_errors:
130
+ logger.warning(f"Encountered {len(all_errors)} errors during processing")
131
+ for error in all_errors[:5]: # Show first 5 errors
132
+ logger.warning(f" - {error}")
133
+ if len(all_errors) > 5:
134
+ logger.warning(f" ... and {len(all_errors) - 5} more errors")
@@ -0,0 +1,170 @@
1
+ import click
2
+ import copick
3
+ from click_option_group import optgroup
4
+ from copick.cli.util import add_config_option, add_debug_option
5
+ from copick.util.log import get_logger
6
+ from copick.util.uri import parse_copick_uri
7
+
8
+ from copick_utils.cli.util import (
9
+ add_clustering_options,
10
+ add_input_option,
11
+ add_output_option,
12
+ add_workers_option,
13
+ )
14
+ from copick_utils.util.config_models import create_simple_config
15
+
16
+
17
+ @click.command(
18
+ context_settings={"show_default": True},
19
+ short_help="Convert picks to sphere meshes.",
20
+ no_args_is_help=True,
21
+ )
22
+ @add_config_option
23
+ @optgroup.group("\nInput Options", help="Options related to the input picks.")
24
+ @optgroup.option(
25
+ "--run-names",
26
+ "-r",
27
+ multiple=True,
28
+ help="Specific run names to process (default: all runs).",
29
+ )
30
+ @add_input_option("picks")
31
+ @optgroup.group("\nTool Options", help="Options related to this tool.")
32
+ @optgroup.option(
33
+ "--subdivisions",
34
+ type=int,
35
+ default=2,
36
+ help="Number of sphere subdivisions for mesh resolution.",
37
+ )
38
+ @optgroup.option(
39
+ "--deduplicate-spheres/--no-deduplicate-spheres",
40
+ is_flag=True,
41
+ default=True,
42
+ help="Merge overlapping spheres to avoid duplicates.",
43
+ )
44
+ @optgroup.option(
45
+ "--min-sphere-distance",
46
+ type=float,
47
+ help="Minimum distance between sphere centers for deduplication (default: 0.5 * average radius).",
48
+ )
49
+ @add_clustering_options
50
+ @add_workers_option
51
+ @optgroup.group("\nOutput Options", help="Options related to output meshes.")
52
+ @add_output_option("mesh", default_tool="picks2sphere")
53
+ @optgroup.option(
54
+ "--individual-meshes/--no-individual-meshes",
55
+ "-im",
56
+ is_flag=True,
57
+ default=False,
58
+ help="Create individual meshes for each instance (enables {instance_id} placeholder).",
59
+ )
60
+ @add_debug_option
61
+ def picks2sphere(
62
+ config,
63
+ run_names,
64
+ input_uri,
65
+ use_clustering,
66
+ clustering_method,
67
+ clustering_eps,
68
+ clustering_min_samples,
69
+ clustering_n_clusters,
70
+ subdivisions,
71
+ deduplicate_spheres,
72
+ min_sphere_distance,
73
+ all_clusters,
74
+ workers,
75
+ output_uri,
76
+ individual_meshes,
77
+ debug,
78
+ ):
79
+ """
80
+ Convert picks to sphere meshes.
81
+
82
+ \b
83
+ URI Format:
84
+ Picks: object_name:user_id/session_id
85
+ Meshes: object_name:user_id/session_id
86
+
87
+ \b
88
+ Examples:
89
+ # Convert single pick set to single sphere mesh
90
+ copick convert picks2sphere -i "ribosome:user1/manual-001" -o "ribosome:picks2sphere/sphere-001"
91
+
92
+ # Create individual sphere meshes
93
+ copick convert picks2sphere -i "ribosome:user1/manual-001" -o "ribosome:picks2sphere/sphere-{instance_id}" --individual-meshes
94
+
95
+ # Convert all manual picks using pattern matching
96
+ copick convert picks2sphere -i "ribosome:user1/manual-.*" -o "ribosome:picks2sphere/sphere-{input_session_id}"
97
+ """
98
+ from copick_utils.converters.sphere_from_picks import sphere_from_picks_lazy_batch
99
+
100
+ logger = get_logger(__name__, debug=debug)
101
+
102
+ root = copick.from_file(config)
103
+ run_names_list = list(run_names) if run_names else None
104
+
105
+ # Create config directly from URIs with smart defaults
106
+ try:
107
+ task_config = create_simple_config(
108
+ input_uri=input_uri,
109
+ input_type="picks",
110
+ output_uri=output_uri,
111
+ output_type="mesh",
112
+ individual_outputs=individual_meshes,
113
+ command_name="picks2sphere",
114
+ )
115
+ except ValueError as e:
116
+ raise click.BadParameter(str(e)) from e
117
+
118
+ # Extract parameters for logging
119
+ input_params = parse_copick_uri(input_uri, "picks")
120
+ output_params = parse_copick_uri(output_uri, "mesh")
121
+
122
+ # Prepare clustering parameters
123
+ clustering_params = {}
124
+ if clustering_method == "dbscan":
125
+ clustering_params = {"eps": clustering_eps, "min_samples": clustering_min_samples}
126
+ elif clustering_method == "kmeans":
127
+ clustering_params = {"n_clusters": clustering_n_clusters}
128
+
129
+ logger.info(f"Converting picks to sphere mesh for object '{input_params['object_name']}'")
130
+ logger.info(f"Source picks pattern: {input_params['user_id']}/{input_params['session_id']}")
131
+ logger.info(
132
+ f"Target mesh template: {output_params['object_name']} ({output_params['user_id']}/{output_params['session_id']})",
133
+ )
134
+
135
+ # Parallel discovery and processing - no sequential bottleneck!
136
+ results = sphere_from_picks_lazy_batch(
137
+ root=root,
138
+ config=task_config,
139
+ run_names=run_names_list,
140
+ workers=workers,
141
+ use_clustering=use_clustering,
142
+ clustering_method=clustering_method,
143
+ clustering_params=clustering_params,
144
+ subdivisions=subdivisions,
145
+ all_clusters=all_clusters,
146
+ deduplicate_spheres_flag=deduplicate_spheres,
147
+ min_sphere_distance=min_sphere_distance,
148
+ )
149
+ successful = sum(1 for result in results.values() if result and result.get("processed", 0) > 0)
150
+ total_vertices = sum(result.get("vertices_created", 0) for result in results.values() if result)
151
+ total_faces = sum(result.get("faces_created", 0) for result in results.values() if result)
152
+ total_processed = sum(result.get("processed", 0) for result in results.values() if result)
153
+
154
+ # Collect all errors
155
+ all_errors = []
156
+ for result in results.values():
157
+ if result and result.get("errors"):
158
+ all_errors.extend(result["errors"])
159
+
160
+ logger.info(f"Completed: {successful}/{len(results)} runs processed successfully")
161
+ logger.info(f"Total conversion tasks completed: {total_processed}")
162
+ logger.info(f"Total vertices created: {total_vertices}")
163
+ logger.info(f"Total faces created: {total_faces}")
164
+
165
+ if all_errors:
166
+ logger.warning(f"Encountered {len(all_errors)} errors during processing")
167
+ for error in all_errors[:5]: # Show first 5 errors
168
+ logger.warning(f" - {error}")
169
+ if len(all_errors) > 5:
170
+ logger.warning(f" ... and {len(all_errors) - 5} more errors")
@@ -0,0 +1,164 @@
1
+ import click
2
+ import copick
3
+ from click_option_group import optgroup
4
+ from copick.cli.util import add_config_option, add_debug_option
5
+ from copick.util.log import get_logger
6
+ from copick.util.uri import parse_copick_uri
7
+
8
+ from copick_utils.cli.util import (
9
+ add_clustering_options,
10
+ add_input_option,
11
+ add_output_option,
12
+ add_workers_option,
13
+ )
14
+ from copick_utils.util.config_models import create_simple_config
15
+
16
+
17
+ @click.command(
18
+ context_settings={"show_default": True},
19
+ short_help="Convert picks to 2D surface meshes.",
20
+ no_args_is_help=True,
21
+ )
22
+ @add_config_option
23
+ @optgroup.group("\nInput Options", help="Options related to the input picks.")
24
+ @optgroup.option(
25
+ "--run-names",
26
+ "-r",
27
+ multiple=True,
28
+ help="Specific run names to process (default: all runs).",
29
+ )
30
+ @add_input_option("picks")
31
+ @optgroup.group("\nTool Options", help="Options related to this tool.")
32
+ @optgroup.option(
33
+ "--surface-method",
34
+ type=click.Choice(["delaunay", "rbf", "grid"]),
35
+ default="delaunay",
36
+ help="Surface fitting method.",
37
+ )
38
+ @optgroup.option(
39
+ "--grid-resolution",
40
+ type=int,
41
+ default=50,
42
+ help="Resolution for grid-based surface methods.",
43
+ )
44
+ @add_clustering_options
45
+ @add_workers_option
46
+ @optgroup.group("\nOutput Options", help="Options related to output meshes.")
47
+ @add_output_option("mesh", default_tool="picks2surface")
48
+ @optgroup.option(
49
+ "--individual-meshes/--no-individual-meshes",
50
+ "-im",
51
+ is_flag=True,
52
+ default=False,
53
+ help="Create individual meshes for each instance (enables {instance_id} placeholder).",
54
+ )
55
+ @add_debug_option
56
+ def picks2surface(
57
+ config,
58
+ run_names,
59
+ input_uri,
60
+ surface_method,
61
+ grid_resolution,
62
+ use_clustering,
63
+ clustering_method,
64
+ clustering_eps,
65
+ clustering_min_samples,
66
+ clustering_n_clusters,
67
+ all_clusters,
68
+ workers,
69
+ output_uri,
70
+ individual_meshes,
71
+ debug,
72
+ ):
73
+ """
74
+ Convert picks to 2D surface meshes.
75
+
76
+ \b
77
+ URI Format:
78
+ Picks: object_name:user_id/session_id
79
+ Meshes: object_name:user_id/session_id
80
+
81
+ \b
82
+ Examples:
83
+ # Convert single pick set to single surface mesh
84
+ copick convert picks2surface -i "membrane:user1/manual-001" -o "membrane:picks2surface/surface-001"
85
+
86
+ # Create individual surface meshes from clusters
87
+ copick convert picks2surface -i "membrane:user1/manual-001" -o "membrane:picks2surface/surface-{instance_id}" --individual-meshes
88
+
89
+ # Convert all manual picks using pattern matching
90
+ copick convert picks2surface -i "membrane:user1/manual-.*" -o "membrane:picks2surface/surface-{input_session_id}"
91
+ """
92
+ from copick_utils.converters.surface_from_picks import surface_from_picks_lazy_batch
93
+
94
+ logger = get_logger(__name__, debug=debug)
95
+
96
+ root = copick.from_file(config)
97
+ run_names_list = list(run_names) if run_names else None
98
+
99
+ # Create config directly from URIs with smart defaults
100
+ try:
101
+ task_config = create_simple_config(
102
+ input_uri=input_uri,
103
+ input_type="picks",
104
+ output_uri=output_uri,
105
+ output_type="mesh",
106
+ individual_outputs=individual_meshes,
107
+ command_name="picks2surface",
108
+ )
109
+ except ValueError as e:
110
+ raise click.BadParameter(str(e)) from e
111
+
112
+ # Extract parameters for logging
113
+ input_params = parse_copick_uri(input_uri, "picks")
114
+ output_params = parse_copick_uri(output_uri, "mesh")
115
+
116
+ # Prepare clustering parameters
117
+ clustering_params = {}
118
+ if clustering_method == "dbscan":
119
+ clustering_params = {"eps": clustering_eps, "min_samples": clustering_min_samples}
120
+ elif clustering_method == "kmeans":
121
+ clustering_params = {"n_clusters": clustering_n_clusters}
122
+
123
+ logger.info(f"Converting picks to {surface_method} surface mesh for object '{input_params['object_name']}'")
124
+ logger.info(f"Source picks pattern: {input_params['user_id']}/{input_params['session_id']}")
125
+ logger.info(
126
+ f"Target mesh template: {output_params['object_name']} ({output_params['user_id']}/{output_params['session_id']})",
127
+ )
128
+
129
+ # Parallel discovery and processing - no sequential bottleneck!
130
+ results = surface_from_picks_lazy_batch(
131
+ root=root,
132
+ config=task_config,
133
+ run_names=run_names_list,
134
+ workers=workers,
135
+ surface_method=surface_method,
136
+ grid_resolution=grid_resolution,
137
+ use_clustering=use_clustering,
138
+ clustering_method=clustering_method,
139
+ clustering_params=clustering_params,
140
+ all_clusters=all_clusters,
141
+ )
142
+
143
+ successful = sum(1 for result in results.values() if result and result.get("processed", 0) > 0)
144
+ total_vertices = sum(result.get("vertices_created", 0) for result in results.values() if result)
145
+ total_faces = sum(result.get("faces_created", 0) for result in results.values() if result)
146
+ total_processed = sum(result.get("processed", 0) for result in results.values() if result)
147
+
148
+ # Collect all errors
149
+ all_errors = []
150
+ for result in results.values():
151
+ if result and result.get("errors"):
152
+ all_errors.extend(result["errors"])
153
+
154
+ logger.info(f"Completed: {successful}/{len(results)} runs processed successfully")
155
+ logger.info(f"Total conversion tasks completed: {total_processed}")
156
+ logger.info(f"Total vertices created: {total_vertices}")
157
+ logger.info(f"Total faces created: {total_faces}")
158
+
159
+ if all_errors:
160
+ logger.warning(f"Encountered {len(all_errors)} errors during processing")
161
+ for error in all_errors[:5]: # Show first 5 errors
162
+ logger.warning(f" - {error}")
163
+ if len(all_errors) > 5:
164
+ logger.warning(f" ... and {len(all_errors) - 5} more errors")