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,146 @@
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_reference_mesh_option,
12
+ add_reference_seg_option,
13
+ add_workers_option,
14
+ )
15
+ from copick_utils.util.config_models import create_reference_config
16
+
17
+
18
+ @click.command(
19
+ context_settings={"show_default": True},
20
+ short_help="Filter picks to include only those inside a reference volume.",
21
+ no_args_is_help=True,
22
+ )
23
+ @add_config_option
24
+ @optgroup.group("\nInput Options", help="Options related to the input picks.")
25
+ @optgroup.option(
26
+ "--run-names",
27
+ "-r",
28
+ multiple=True,
29
+ help="Specific run names to process (default: all runs).",
30
+ )
31
+ @add_input_option("picks")
32
+ @optgroup.group("\nReference Options", help="Options for reference volume (provide either mesh or segmentation).")
33
+ @add_reference_mesh_option(required=False)
34
+ @add_reference_seg_option(required=False)
35
+ @optgroup.group("\nTool Options", help="Options related to this tool.")
36
+ @add_workers_option
37
+ @optgroup.group("\nOutput Options", help="Options related to output picks.")
38
+ @add_output_option("picks", default_tool="picksin")
39
+ @add_debug_option
40
+ def picksin(
41
+ config,
42
+ run_names,
43
+ input_uri,
44
+ ref_mesh_uri,
45
+ ref_seg_uri,
46
+ workers,
47
+ output_uri,
48
+ debug,
49
+ ):
50
+ """
51
+ Filter picks to include only those inside a reference volume.
52
+
53
+ \b
54
+ URI Format:
55
+ Picks: object_name:user_id/session_id
56
+ Meshes: object_name:user_id/session_id
57
+ Segmentations: name:user_id/session_id@voxel_spacing
58
+
59
+ \b
60
+ The reference volume can be either a watertight mesh or a segmentation.
61
+ Only picks that fall inside the reference volume will be kept.
62
+
63
+ \b
64
+ Examples:
65
+ # Include only picks inside reference mesh
66
+ copick logical picksin -i "ribosome:user1/all-001" -rm "boundary:user1/boundary-001" -o "ribosome:picksin/inside-001"
67
+
68
+ # Include only picks inside segmentation
69
+ copick logical picksin -i "ribosome:user1/all-001" -rs "mask:user1/mask-001@10.0" -o "ribosome:picksin/inside-001"
70
+ """
71
+ from copick_utils.logical.point_operations import picks_inclusion_by_mesh_lazy_batch
72
+
73
+ logger = get_logger(__name__, debug=debug)
74
+
75
+ # Validate that exactly one reference type is provided
76
+ if not ref_mesh_uri and not ref_seg_uri:
77
+ raise click.BadParameter("Must provide either --ref-mesh or --ref-seg")
78
+ if ref_mesh_uri and ref_seg_uri:
79
+ raise click.BadParameter("Cannot provide both --ref-mesh and --ref-seg")
80
+
81
+ root = copick.from_file(config)
82
+ run_names_list = list(run_names) if run_names else None
83
+
84
+ # Determine reference type and URI
85
+ reference_uri = ref_mesh_uri or ref_seg_uri
86
+ reference_type = "mesh" if ref_mesh_uri else "segmentation"
87
+
88
+ # Create config directly from URIs with smart defaults
89
+ try:
90
+ task_config = create_reference_config(
91
+ input_uri=input_uri,
92
+ input_type="picks",
93
+ output_uri=output_uri,
94
+ output_type="picks",
95
+ reference_uri=reference_uri,
96
+ reference_type=reference_type,
97
+ command_name="picksin",
98
+ )
99
+ except ValueError as e:
100
+ raise click.BadParameter(str(e)) from e
101
+
102
+ # Extract parameters for logging
103
+ input_params = parse_copick_uri(input_uri, "picks")
104
+ output_params = parse_copick_uri(output_uri, "picks")
105
+ ref_params = parse_copick_uri(reference_uri, reference_type)
106
+
107
+ logger.info(f"Including picks inside reference volume for object '{input_params['object_name']}'")
108
+ logger.info(f"Source picks pattern: {input_params['user_id']}/{input_params['session_id']}")
109
+ if reference_type == "mesh":
110
+ logger.info(f"Reference mesh: {ref_params['object_name']} ({ref_params['user_id']}/{ref_params['session_id']})")
111
+ else:
112
+ logger.info(
113
+ f"Reference segmentation: {ref_params['name']} ({ref_params['user_id']}/{ref_params['session_id']})",
114
+ )
115
+ logger.info(
116
+ f"Target picks template: {output_params['object_name']} ({output_params['user_id']}/{output_params['session_id']})",
117
+ )
118
+
119
+ # Parallel discovery and processing - no sequential bottleneck!
120
+ results = picks_inclusion_by_mesh_lazy_batch(
121
+ root=root,
122
+ config=task_config,
123
+ run_names=run_names_list,
124
+ workers=workers,
125
+ )
126
+
127
+ successful = sum(1 for result in results.values() if result and result.get("processed", 0) > 0)
128
+ total_points = sum(result.get("points_created", 0) for result in results.values() if result)
129
+ total_processed = sum(result.get("processed", 0) for result in results.values() if result)
130
+
131
+ # Collect all errors
132
+ all_errors = []
133
+ for result in results.values():
134
+ if result and result.get("errors"):
135
+ all_errors.extend(result["errors"])
136
+
137
+ logger.info(f"Completed: {successful}/{len(results)} runs processed successfully")
138
+ logger.info(f"Total inclusion operations completed: {total_processed}")
139
+ logger.info(f"Total points included: {total_points}")
140
+
141
+ if all_errors:
142
+ logger.warning(f"Encountered {len(all_errors)} errors during processing")
143
+ for error in all_errors[:5]: # Show first 5 errors
144
+ logger.warning(f" - {error}")
145
+ if len(all_errors) > 5:
146
+ logger.warning(f" ... and {len(all_errors) - 5} more errors")
@@ -0,0 +1,148 @@
1
+ """CLI commands for point filtering operations (inclusion/exclusion by volume)."""
2
+
3
+ import click
4
+ import copick
5
+ from click_option_group import optgroup
6
+ from copick.cli.util import add_config_option, add_debug_option
7
+ from copick.util.log import get_logger
8
+ from copick.util.uri import parse_copick_uri
9
+
10
+ from copick_utils.cli.util import (
11
+ add_input_option,
12
+ add_output_option,
13
+ add_reference_mesh_option,
14
+ add_reference_seg_option,
15
+ add_workers_option,
16
+ )
17
+ from copick_utils.util.config_models import create_reference_config
18
+
19
+
20
+ @click.command(
21
+ context_settings={"show_default": True},
22
+ short_help="Filter picks to exclude those inside a reference volume.",
23
+ no_args_is_help=True,
24
+ )
25
+ @add_config_option
26
+ @optgroup.group("\nInput Options", help="Options related to the input picks.")
27
+ @optgroup.option(
28
+ "--run-names",
29
+ "-r",
30
+ multiple=True,
31
+ help="Specific run names to process (default: all runs).",
32
+ )
33
+ @add_input_option("picks")
34
+ @optgroup.group("\nReference Options", help="Options for reference volume (provide either mesh or segmentation).")
35
+ @add_reference_mesh_option(required=False)
36
+ @add_reference_seg_option(required=False)
37
+ @optgroup.group("\nTool Options", help="Options related to this tool.")
38
+ @add_workers_option
39
+ @optgroup.group("\nOutput Options", help="Options related to output picks.")
40
+ @add_output_option("picks", default_tool="picksout")
41
+ @add_debug_option
42
+ def picksout(
43
+ config,
44
+ run_names,
45
+ input_uri,
46
+ ref_mesh_uri,
47
+ ref_seg_uri,
48
+ workers,
49
+ output_uri,
50
+ debug,
51
+ ):
52
+ """
53
+ Filter picks to exclude those inside a reference volume.
54
+
55
+ \b
56
+ URI Format:
57
+ Picks: object_name:user_id/session_id
58
+ Meshes: object_name:user_id/session_id
59
+ Segmentations: name:user_id/session_id@voxel_spacing
60
+
61
+ \b
62
+ The reference volume can be either a watertight mesh or a segmentation.
63
+ Picks that fall inside the reference volume will be removed.
64
+
65
+ \b
66
+ Examples:
67
+ # Exclude picks inside reference mesh
68
+ copick logical picksout -i "ribosome:user1/all-001" -rm "boundary:user1/boundary-001" -o "ribosome:picksout/outside-001"
69
+
70
+ # Exclude picks inside segmentation
71
+ copick logical picksout -i "ribosome:user1/all-001" -rs "mask:user1/mask-001@10.0" -o "ribosome:picksout/outside-001"
72
+ """
73
+ from copick_utils.logical.point_operations import picks_exclusion_by_mesh_lazy_batch
74
+
75
+ logger = get_logger(__name__, debug=debug)
76
+
77
+ # Validate that exactly one reference type is provided
78
+ if not ref_mesh_uri and not ref_seg_uri:
79
+ raise click.BadParameter("Must provide either --ref-mesh or --ref-seg")
80
+ if ref_mesh_uri and ref_seg_uri:
81
+ raise click.BadParameter("Cannot provide both --ref-mesh and --ref-seg")
82
+
83
+ root = copick.from_file(config)
84
+ run_names_list = list(run_names) if run_names else None
85
+
86
+ # Determine reference type and URI
87
+ reference_uri = ref_mesh_uri or ref_seg_uri
88
+ reference_type = "mesh" if ref_mesh_uri else "segmentation"
89
+
90
+ # Create config directly from URIs with smart defaults
91
+ try:
92
+ task_config = create_reference_config(
93
+ input_uri=input_uri,
94
+ input_type="picks",
95
+ output_uri=output_uri,
96
+ output_type="picks",
97
+ reference_uri=reference_uri,
98
+ reference_type=reference_type,
99
+ command_name="picksout",
100
+ )
101
+ except ValueError as e:
102
+ raise click.BadParameter(str(e)) from e
103
+
104
+ # Extract parameters for logging
105
+ input_params = parse_copick_uri(input_uri, "picks")
106
+ output_params = parse_copick_uri(output_uri, "picks")
107
+ ref_params = parse_copick_uri(reference_uri, reference_type)
108
+
109
+ logger.info(f"Excluding picks inside reference volume for object '{input_params['object_name']}'")
110
+ logger.info(f"Source picks pattern: {input_params['user_id']}/{input_params['session_id']}")
111
+ if reference_type == "mesh":
112
+ logger.info(f"Reference mesh: {ref_params['object_name']} ({ref_params['user_id']}/{ref_params['session_id']})")
113
+ else:
114
+ logger.info(
115
+ f"Reference segmentation: {ref_params['name']} ({ref_params['user_id']}/{ref_params['session_id']})",
116
+ )
117
+ logger.info(
118
+ f"Target picks template: {output_params['object_name']} ({output_params['user_id']}/{output_params['session_id']})",
119
+ )
120
+
121
+ # Parallel discovery and processing - no sequential bottleneck!
122
+ results = picks_exclusion_by_mesh_lazy_batch(
123
+ root=root,
124
+ config=task_config,
125
+ run_names=run_names_list,
126
+ workers=workers,
127
+ )
128
+
129
+ successful = sum(1 for result in results.values() if result and result.get("processed", 0) > 0)
130
+ total_points = sum(result.get("points_created", 0) for result in results.values() if result)
131
+ total_processed = sum(result.get("processed", 0) for result in results.values() if result)
132
+
133
+ # Collect all errors
134
+ all_errors = []
135
+ for result in results.values():
136
+ if result and result.get("errors"):
137
+ all_errors.extend(result["errors"])
138
+
139
+ logger.info(f"Completed: {successful}/{len(results)} runs processed successfully")
140
+ logger.info(f"Total exclusion operations completed: {total_processed}")
141
+ logger.info(f"Total points excluded (remaining): {total_points}")
142
+
143
+ if all_errors:
144
+ logger.warning(f"Encountered {len(all_errors)} errors during processing")
145
+ for error in all_errors[:5]: # Show first 5 errors
146
+ logger.warning(f" - {error}")
147
+ if len(all_errors) > 5:
148
+ logger.warning(f" ... and {len(all_errors) - 5} more errors")
@@ -0,0 +1,18 @@
1
+ """CLI commands for segmentation processing operations."""
2
+
3
+ from copick_utils.cli.filter_components import filter_components
4
+ from copick_utils.cli.fit_spline import fit_spline
5
+ from copick_utils.cli.hull import hull
6
+ from copick_utils.cli.separate_components import separate_components
7
+ from copick_utils.cli.skeletonize import skeletonize
8
+ from copick_utils.cli.validbox import validbox
9
+
10
+ # All commands are now available for import by the main CLI
11
+ __all__ = [
12
+ "validbox",
13
+ "hull",
14
+ "skeletonize",
15
+ "separate_components",
16
+ "filter_components",
17
+ "fit_spline",
18
+ ]
@@ -0,0 +1,135 @@
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_marching_cubes_options,
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 segmentation to mesh.",
20
+ no_args_is_help=True,
21
+ )
22
+ @add_config_option
23
+ @optgroup.group("\nInput Options", help="Options related to the input segmentations.")
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("segmentation")
31
+ @optgroup.group("\nTool Options", help="Options related to this tool.")
32
+ @add_marching_cubes_options
33
+ @add_workers_option
34
+ @optgroup.group("\nOutput Options", help="Options related to output meshes.")
35
+ @add_output_option("mesh", default_tool="seg2mesh")
36
+ @optgroup.option(
37
+ "--individual-meshes/--no-individual-meshes",
38
+ "-im",
39
+ is_flag=True,
40
+ default=False,
41
+ help="Create individual meshes for each instance (enables {instance_id} placeholder).",
42
+ )
43
+ @add_debug_option
44
+ def seg2mesh(
45
+ config,
46
+ run_names,
47
+ input_uri,
48
+ level,
49
+ step_size,
50
+ workers,
51
+ output_uri,
52
+ individual_meshes,
53
+ debug,
54
+ ):
55
+ """
56
+ Convert segmentation volumes to meshes using marching cubes.
57
+
58
+ \b
59
+ URI Format:
60
+ Segmentations: name:user_id/session_id@voxel_spacing
61
+ Meshes: object_name:user_id/session_id
62
+
63
+ \b
64
+ Examples:
65
+ # Convert single segmentation to mesh
66
+ copick convert seg2mesh -i "membrane:user1/manual-001@10.0" -o "membrane:seg2mesh/from-seg-001"
67
+
68
+ # Convert all manual segmentations using pattern matching
69
+ copick convert seg2mesh -i "membrane:user1/manual-.*@10.0" -o "membrane:seg2mesh/from-seg-{input_session_id}"
70
+ """
71
+ from copick_utils.converters.mesh_from_segmentation import mesh_from_segmentation_lazy_batch
72
+
73
+ logger = get_logger(__name__, debug=debug)
74
+
75
+ root = copick.from_file(config)
76
+ run_names_list = list(run_names) if run_names else None
77
+
78
+ # Create config directly from URIs with smart defaults
79
+ try:
80
+ task_config = create_simple_config(
81
+ input_uri=input_uri,
82
+ input_type="segmentation",
83
+ output_uri=output_uri,
84
+ output_type="mesh",
85
+ individual_outputs=individual_meshes,
86
+ command_name="seg2mesh",
87
+ )
88
+ except ValueError as e:
89
+ raise click.BadParameter(str(e)) from e
90
+
91
+ # Extract parameters for logging
92
+ input_params = parse_copick_uri(input_uri, "segmentation")
93
+ output_params = parse_copick_uri(output_uri, "mesh")
94
+
95
+ logger.info(f"Converting segmentation to mesh for '{input_params['name']}'")
96
+ logger.info(
97
+ f"Source segmentation pattern: {input_params['name']} ({input_params['user_id']}/{input_params['session_id']})",
98
+ )
99
+ logger.info(
100
+ f"Target mesh template: {output_params['object_name']} ({output_params['user_id']}/{output_params['session_id']})",
101
+ )
102
+ logger.info(f"Marching cubes level: {level}, step size: {step_size}")
103
+
104
+ # Parallel discovery and processing - no sequential bottleneck!
105
+ results = mesh_from_segmentation_lazy_batch(
106
+ root=root,
107
+ config=task_config,
108
+ run_names=run_names_list,
109
+ workers=workers,
110
+ level=level,
111
+ step_size=step_size,
112
+ )
113
+
114
+ successful = sum(1 for result in results.values() if result and result.get("processed", 0) > 0)
115
+ total_vertices = sum(result.get("vertices_created", 0) for result in results.values() if result)
116
+ total_faces = sum(result.get("faces_created", 0) for result in results.values() if result)
117
+ total_processed = sum(result.get("processed", 0) for result in results.values() if result)
118
+
119
+ # Collect all errors
120
+ all_errors = []
121
+ for result in results.values():
122
+ if result and result.get("errors"):
123
+ all_errors.extend(result["errors"])
124
+
125
+ logger.info(f"Completed: {successful}/{len(results)} runs processed successfully")
126
+ logger.info(f"Total conversion tasks completed: {total_processed}")
127
+ logger.info(f"Total vertices created: {total_vertices}")
128
+ logger.info(f"Total faces created: {total_faces}")
129
+
130
+ if all_errors:
131
+ logger.warning(f"Encountered {len(all_errors)} errors during processing")
132
+ for error in all_errors[:5]: # Show first 5 errors
133
+ logger.warning(f" - {error}")
134
+ if len(all_errors) > 5:
135
+ logger.warning(f" ... and {len(all_errors) - 5} more errors")
@@ -0,0 +1,128 @@
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_segmentation_processing_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 segmentation to picks.",
20
+ no_args_is_help=True,
21
+ )
22
+ @add_config_option
23
+ @optgroup.group("\nInput Options", help="Options related to the input segmentations.")
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("segmentation")
31
+ @optgroup.group("\nTool Options", help="Options related to this tool.")
32
+ @add_segmentation_processing_options
33
+ @add_workers_option
34
+ @optgroup.group("\nOutput Options", help="Options related to output picks.")
35
+ @add_output_option("picks", default_tool="seg2picks")
36
+ @add_debug_option
37
+ def seg2picks(
38
+ config,
39
+ run_names,
40
+ input_uri,
41
+ segmentation_idx,
42
+ maxima_filter_size,
43
+ min_particle_size,
44
+ max_particle_size,
45
+ workers,
46
+ output_uri,
47
+ debug,
48
+ ):
49
+ """
50
+ Convert segmentation volumes to picks by extracting centroids.
51
+
52
+ \b
53
+ URI Format:
54
+ Segmentations: name:user_id/session_id@voxel_spacing
55
+ Picks: object_name:user_id/session_id
56
+
57
+ \b
58
+ Examples:
59
+ # Convert single segmentation to picks
60
+ copick convert seg2picks -i "membrane:user1/manual-001@10.0" -o "membrane:seg2picks/centroid-001"
61
+
62
+ # Convert all manual segmentations using pattern matching
63
+ copick convert seg2picks -i "membrane:user1/manual-.*@10.0" -o "membrane:seg2picks/centroid-{input_session_id}"
64
+ """
65
+ from copick_utils.converters.picks_from_segmentation import picks_from_segmentation_lazy_batch
66
+
67
+ logger = get_logger(__name__, debug=debug)
68
+
69
+ root = copick.from_file(config)
70
+ run_names_list = list(run_names) if run_names else None
71
+
72
+ # Create config directly from URIs with smart defaults
73
+ try:
74
+ task_config = create_simple_config(
75
+ input_uri=input_uri,
76
+ input_type="segmentation",
77
+ output_uri=output_uri,
78
+ output_type="picks",
79
+ command_name="seg2picks",
80
+ )
81
+ except ValueError as e:
82
+ raise click.BadParameter(str(e)) from e
83
+
84
+ # Extract parameters for logging
85
+ input_params = parse_copick_uri(input_uri, "segmentation")
86
+ output_params = parse_copick_uri(output_uri, "picks")
87
+
88
+ logger.info(f"Converting segmentation to picks for '{input_params['name']}'")
89
+ logger.info(
90
+ f"Source segmentation pattern: {input_params['name']} ({input_params['user_id']}/{input_params['session_id']})",
91
+ )
92
+ logger.info(
93
+ f"Target picks template: {output_params['object_name']} ({output_params['user_id']}/{output_params['session_id']})",
94
+ )
95
+ logger.info(f"Label {segmentation_idx}, particle size: {min_particle_size}-{max_particle_size}")
96
+
97
+ # Parallel discovery and processing - no sequential bottleneck!
98
+ results = picks_from_segmentation_lazy_batch(
99
+ root=root,
100
+ config=task_config,
101
+ run_names=run_names_list,
102
+ workers=workers,
103
+ segmentation_idx=segmentation_idx,
104
+ maxima_filter_size=maxima_filter_size,
105
+ min_particle_size=min_particle_size,
106
+ max_particle_size=max_particle_size,
107
+ )
108
+
109
+ successful = sum(1 for result in results.values() if result and result.get("processed", 0) > 0)
110
+ total_points = sum(result.get("points_created", 0) for result in results.values() if result)
111
+ total_processed = sum(result.get("processed", 0) for result in results.values() if result)
112
+
113
+ # Collect all errors
114
+ all_errors = []
115
+ for result in results.values():
116
+ if result and result.get("errors"):
117
+ all_errors.extend(result["errors"])
118
+
119
+ logger.info(f"Completed: {successful}/{len(results)} runs processed successfully")
120
+ logger.info(f"Total conversion tasks completed: {total_processed}")
121
+ logger.info(f"Total points created: {total_points}")
122
+
123
+ if all_errors:
124
+ logger.warning(f"Encountered {len(all_errors)} errors during processing")
125
+ for error in all_errors[:5]: # Show first 5 errors
126
+ logger.warning(f" - {error}")
127
+ if len(all_errors) > 5:
128
+ logger.warning(f" ... and {len(all_errors) - 5} more errors")