copick-utils 0.6.0__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 -0
- 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 +151 -15
- copick_utils/converters/sphere_from_picks.py +306 -0
- copick_utils/converters/surface_from_picks.py +337 -0
- copick_utils/features/skimage.py +33 -13
- copick_utils/io/readers.py +62 -59
- copick_utils/io/writers.py +9 -14
- 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/pickers/grid_picker.py +5 -4
- 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.0.dist-info → copick_utils-1.0.0.dist-info}/METADATA +38 -12
- copick_utils-1.0.0.dist-info/RECORD +71 -0
- {copick_utils-0.6.0.dist-info → copick_utils-1.0.0.dist-info}/WHEEL +1 -1
- copick_utils-1.0.0.dist-info/entry_points.txt +29 -0
- copick_utils/__about__.py +0 -4
- copick_utils/segmentation/picks_from_segmentation.py +0 -67
- copick_utils-0.6.0.dist-info/RECORD +0 -15
- /copick_utils/{segmentation → io}/__init__.py +0 -0
- /copick_utils-0.6.0.dist-info/LICENSE.txt → /copick_utils-1.0.0.dist-info/licenses/LICENSE +0 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Input/output selection logic for conversion CLI commands."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _is_regex_pattern(pattern: str) -> bool:
|
|
7
|
+
"""Check if string is a regex pattern by trying to compile it and seeing if it has special chars."""
|
|
8
|
+
# Check for common regex special characters
|
|
9
|
+
regex_chars = r"[.*+?^${}()|[\]\\"
|
|
10
|
+
has_regex_chars = any(char in pattern for char in regex_chars)
|
|
11
|
+
|
|
12
|
+
if not has_regex_chars:
|
|
13
|
+
return False
|
|
14
|
+
|
|
15
|
+
# Try to compile as regex
|
|
16
|
+
try:
|
|
17
|
+
re.compile(pattern)
|
|
18
|
+
return True
|
|
19
|
+
except re.error:
|
|
20
|
+
return False
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def validate_placeholders(
|
|
24
|
+
pick_session_id: str,
|
|
25
|
+
mesh_session_id: str,
|
|
26
|
+
individual_meshes: bool,
|
|
27
|
+
) -> None:
|
|
28
|
+
"""
|
|
29
|
+
Validate that session ID templates contain required placeholders.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
pick_session_id: Input session ID or pattern
|
|
33
|
+
mesh_session_id: Output session ID template
|
|
34
|
+
individual_meshes: Whether individual meshes are being created
|
|
35
|
+
|
|
36
|
+
Raises:
|
|
37
|
+
ValueError: If template validation fails
|
|
38
|
+
"""
|
|
39
|
+
if individual_meshes and "{instance_id}" not in mesh_session_id:
|
|
40
|
+
raise ValueError(
|
|
41
|
+
"Session ID template must contain {instance_id} placeholder when individual-meshes is enabled",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Check if this is many-to-many mode (input has regex pattern)
|
|
45
|
+
if _is_regex_pattern(pick_session_id) and "{input_session_id}" not in mesh_session_id:
|
|
46
|
+
raise ValueError(
|
|
47
|
+
"Session ID template must contain {input_session_id} placeholder when using regex input pattern",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def validate_conversion_placeholders(
|
|
52
|
+
input_session_id: str,
|
|
53
|
+
output_session_id: str,
|
|
54
|
+
individual_outputs: bool,
|
|
55
|
+
) -> None:
|
|
56
|
+
"""
|
|
57
|
+
Validate that session ID templates contain required placeholders.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
input_session_id: Input session ID or pattern
|
|
61
|
+
output_session_id: Output session ID template
|
|
62
|
+
individual_outputs: Whether individual outputs are being created
|
|
63
|
+
|
|
64
|
+
Raises:
|
|
65
|
+
ValueError: If template validation fails
|
|
66
|
+
"""
|
|
67
|
+
if individual_outputs and "{instance_id}" not in output_session_id:
|
|
68
|
+
raise ValueError(
|
|
69
|
+
"Session ID template must contain {instance_id} placeholder when individual outputs are enabled",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Check if this is many-to-many mode (input has regex pattern)
|
|
73
|
+
if _is_regex_pattern(input_session_id) and "{input_session_id}" not in output_session_id:
|
|
74
|
+
raise ValueError(
|
|
75
|
+
"Session ID template must contain {input_session_id} placeholder when using regex input pattern",
|
|
76
|
+
)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""CLI commands for logical operations (boolean operations, distance limiting, point filtering).
|
|
2
|
+
|
|
3
|
+
This module imports all logical operation commands from specialized files for better organization.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
from copick_utils.cli.clipmesh import clipmesh
|
|
8
|
+
from copick_utils.cli.clippicks import clippicks
|
|
9
|
+
from copick_utils.cli.clipseg import clipseg
|
|
10
|
+
from copick_utils.cli.enclosed import enclosed
|
|
11
|
+
from copick_utils.cli.meshop import meshop
|
|
12
|
+
from copick_utils.cli.picksin import picksin
|
|
13
|
+
from copick_utils.cli.picksout import picksout
|
|
14
|
+
from copick_utils.cli.segop import segop
|
|
15
|
+
|
|
16
|
+
# All commands are now available for import by the main CLI
|
|
17
|
+
__all__ = [
|
|
18
|
+
# Boolean operation commands
|
|
19
|
+
"meshop",
|
|
20
|
+
"segop",
|
|
21
|
+
"enclosed",
|
|
22
|
+
# Distance limiting commands
|
|
23
|
+
"clipmesh",
|
|
24
|
+
"clipseg",
|
|
25
|
+
"clippicks",
|
|
26
|
+
# Point filtering commands
|
|
27
|
+
"picksin",
|
|
28
|
+
"picksout",
|
|
29
|
+
]
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""CLI commands for converting meshes to picks."""
|
|
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 add_input_option, add_output_option, add_tomogram_option, add_workers_option
|
|
11
|
+
from copick_utils.util.config_models import create_simple_config
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.command(
|
|
15
|
+
context_settings={"show_default": True},
|
|
16
|
+
short_help="Convert mesh to picks.",
|
|
17
|
+
no_args_is_help=True,
|
|
18
|
+
)
|
|
19
|
+
@add_config_option
|
|
20
|
+
@optgroup.group("\nInput Options", help="Options related to the input meshes.")
|
|
21
|
+
@optgroup.option(
|
|
22
|
+
"--run-names",
|
|
23
|
+
"-r",
|
|
24
|
+
multiple=True,
|
|
25
|
+
help="Specific run names to process (default: all runs).",
|
|
26
|
+
)
|
|
27
|
+
@add_input_option("mesh")
|
|
28
|
+
@add_tomogram_option(required=True)
|
|
29
|
+
@optgroup.group("\nTool Options", help="Options related to this tool.")
|
|
30
|
+
@optgroup.option(
|
|
31
|
+
"--sampling-type",
|
|
32
|
+
type=click.Choice(["inside", "surface", "outside", "vertices"]),
|
|
33
|
+
required=True,
|
|
34
|
+
help="Type of sampling: inside (points inside mesh), surface (points on mesh surface), outside (points outside mesh), vertices (return mesh vertices).",
|
|
35
|
+
)
|
|
36
|
+
@optgroup.option(
|
|
37
|
+
"--n-points",
|
|
38
|
+
type=int,
|
|
39
|
+
default=1000,
|
|
40
|
+
help="Number of points to sample (ignored for 'vertices' type).",
|
|
41
|
+
)
|
|
42
|
+
@optgroup.option(
|
|
43
|
+
"--min-dist",
|
|
44
|
+
type=float,
|
|
45
|
+
help="Minimum distance between points (default: 2 * voxel_spacing).",
|
|
46
|
+
)
|
|
47
|
+
@optgroup.option(
|
|
48
|
+
"--edge-dist",
|
|
49
|
+
type=float,
|
|
50
|
+
default=32.0,
|
|
51
|
+
help="Distance from volume edges in voxels.",
|
|
52
|
+
)
|
|
53
|
+
@optgroup.option(
|
|
54
|
+
"--include-normals/--no-include-normals",
|
|
55
|
+
is_flag=True,
|
|
56
|
+
default=False,
|
|
57
|
+
help="Include surface normals as orientations (surface sampling only).",
|
|
58
|
+
)
|
|
59
|
+
@optgroup.option(
|
|
60
|
+
"--random-orientations/--no-random-orientations",
|
|
61
|
+
is_flag=True,
|
|
62
|
+
default=False,
|
|
63
|
+
help="Generate random orientations for points.",
|
|
64
|
+
)
|
|
65
|
+
@optgroup.option(
|
|
66
|
+
"--seed",
|
|
67
|
+
type=int,
|
|
68
|
+
help="Random seed for reproducible results.",
|
|
69
|
+
)
|
|
70
|
+
@add_workers_option
|
|
71
|
+
@optgroup.group("\nOutput Options", help="Options related to output picks.")
|
|
72
|
+
@add_output_option("picks", default_tool="mesh2picks")
|
|
73
|
+
@add_debug_option
|
|
74
|
+
def mesh2picks(
|
|
75
|
+
config,
|
|
76
|
+
run_names,
|
|
77
|
+
input_uri,
|
|
78
|
+
tomogram_uri,
|
|
79
|
+
sampling_type,
|
|
80
|
+
n_points,
|
|
81
|
+
min_dist,
|
|
82
|
+
edge_dist,
|
|
83
|
+
include_normals,
|
|
84
|
+
random_orientations,
|
|
85
|
+
seed,
|
|
86
|
+
workers,
|
|
87
|
+
output_uri,
|
|
88
|
+
debug,
|
|
89
|
+
):
|
|
90
|
+
"""
|
|
91
|
+
Convert meshes to picks using different sampling strategies.
|
|
92
|
+
|
|
93
|
+
\b
|
|
94
|
+
URI Format:
|
|
95
|
+
Meshes: object_name:user_id/session_id
|
|
96
|
+
Picks: object_name:user_id/session_id
|
|
97
|
+
Tomograms: tomo_type@voxel_spacing
|
|
98
|
+
|
|
99
|
+
\b
|
|
100
|
+
Examples:
|
|
101
|
+
# Convert single mesh to picks with surface sampling
|
|
102
|
+
copick convert mesh2picks -i "boundary:user1/boundary-001" --tomogram wbp@10.0 --sampling-type surface -o "boundary"
|
|
103
|
+
|
|
104
|
+
# Convert all boundary meshes using pattern matching
|
|
105
|
+
copick convert mesh2picks -i "boundary:user1/boundary-.*" -t wbp@10.0 --sampling-type inside -o "{input_session_id}"
|
|
106
|
+
"""
|
|
107
|
+
from copick_utils.converters.picks_from_mesh import picks_from_mesh_lazy_batch
|
|
108
|
+
|
|
109
|
+
logger = get_logger(__name__, debug=debug)
|
|
110
|
+
|
|
111
|
+
root = copick.from_file(config)
|
|
112
|
+
run_names_list = list(run_names) if run_names else None
|
|
113
|
+
|
|
114
|
+
# Parse tomogram URI to extract tomo_type and voxel_spacing
|
|
115
|
+
try:
|
|
116
|
+
tomogram_params = parse_copick_uri(tomogram_uri, "tomogram")
|
|
117
|
+
except ValueError as e:
|
|
118
|
+
raise click.BadParameter(f"Invalid tomogram URI: {e}") from e
|
|
119
|
+
|
|
120
|
+
tomo_type = tomogram_params["tomo_type"]
|
|
121
|
+
voxel_spacing = tomogram_params["voxel_spacing"]
|
|
122
|
+
if isinstance(voxel_spacing, str):
|
|
123
|
+
voxel_spacing = float(voxel_spacing)
|
|
124
|
+
|
|
125
|
+
# Create config directly from URIs with smart defaults
|
|
126
|
+
try:
|
|
127
|
+
task_config = create_simple_config(
|
|
128
|
+
input_uri=input_uri,
|
|
129
|
+
input_type="mesh",
|
|
130
|
+
output_uri=output_uri,
|
|
131
|
+
output_type="picks",
|
|
132
|
+
command_name="mesh2picks",
|
|
133
|
+
)
|
|
134
|
+
except ValueError as e:
|
|
135
|
+
raise click.BadParameter(str(e)) from e
|
|
136
|
+
|
|
137
|
+
# Extract parameters for logging
|
|
138
|
+
input_params = parse_copick_uri(input_uri, "mesh")
|
|
139
|
+
output_params = parse_copick_uri(output_uri, "picks")
|
|
140
|
+
|
|
141
|
+
logger.info(f"Converting mesh to picks for object '{input_params['object_name']}'")
|
|
142
|
+
logger.info(f"Source mesh pattern: {input_params['user_id']}/{input_params['session_id']}")
|
|
143
|
+
logger.info(
|
|
144
|
+
f"Target picks template: {output_params['object_name']} ({output_params['user_id']}/{output_params['session_id']})",
|
|
145
|
+
)
|
|
146
|
+
logger.info(f"Tomogram: {tomo_type}@{voxel_spacing}")
|
|
147
|
+
logger.info(f"Sampling type: {sampling_type}, n_points: {n_points}")
|
|
148
|
+
|
|
149
|
+
# Parallel discovery and processing with consistent architecture!
|
|
150
|
+
results = picks_from_mesh_lazy_batch(
|
|
151
|
+
root=root,
|
|
152
|
+
config=task_config,
|
|
153
|
+
run_names=run_names_list,
|
|
154
|
+
workers=workers,
|
|
155
|
+
sampling_type=sampling_type,
|
|
156
|
+
n_points=n_points,
|
|
157
|
+
voxel_spacing=voxel_spacing,
|
|
158
|
+
tomo_type=tomo_type,
|
|
159
|
+
min_dist=min_dist,
|
|
160
|
+
edge_dist=edge_dist,
|
|
161
|
+
include_normals=include_normals,
|
|
162
|
+
random_orientations=random_orientations,
|
|
163
|
+
seed=seed,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
successful = sum(1 for result in results.values() if result and result.get("processed", 0) > 0)
|
|
167
|
+
total_points = sum(result.get("points_created", 0) for result in results.values() if result)
|
|
168
|
+
|
|
169
|
+
logger.info(f"Completed: {successful}/{len(results)} runs processed successfully")
|
|
170
|
+
logger.info(f"Total points created: {total_points}")
|
|
@@ -0,0 +1,167 @@
|
|
|
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_mesh_voxelization_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 mesh to segmentation.",
|
|
20
|
+
no_args_is_help=True,
|
|
21
|
+
)
|
|
22
|
+
@add_config_option
|
|
23
|
+
@optgroup.group("\nInput Options", help="Options related to the input meshes.")
|
|
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("mesh")
|
|
31
|
+
@optgroup.group("\nTool Options", help="Options related to this tool.")
|
|
32
|
+
@add_mesh_voxelization_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="mesh2seg")
|
|
42
|
+
@add_debug_option
|
|
43
|
+
def mesh2seg(
|
|
44
|
+
config,
|
|
45
|
+
run_names,
|
|
46
|
+
input_uri,
|
|
47
|
+
mode,
|
|
48
|
+
boundary_sampling_density,
|
|
49
|
+
invert,
|
|
50
|
+
tomo_type,
|
|
51
|
+
workers,
|
|
52
|
+
output_uri,
|
|
53
|
+
debug,
|
|
54
|
+
):
|
|
55
|
+
"""
|
|
56
|
+
Convert meshes to segmentation volumes with multiple voxelization modes.
|
|
57
|
+
|
|
58
|
+
\b
|
|
59
|
+
URI Format:
|
|
60
|
+
Meshes: object_name:user_id/session_id
|
|
61
|
+
Segmentations: name:user_id/session_id@voxel_spacing?multilabel=true
|
|
62
|
+
|
|
63
|
+
\b
|
|
64
|
+
Pattern Matching:
|
|
65
|
+
- Exact: "membrane:user1/session1"
|
|
66
|
+
- Glob: "membrane:user1/session*" or "membrane:*/manual-*"
|
|
67
|
+
- Regex: "re:membrane:user\\d+/session\\d+"
|
|
68
|
+
- Wildcard: "membrane" (expands to "membrane:*/*")
|
|
69
|
+
|
|
70
|
+
\b
|
|
71
|
+
Voxelization modes:
|
|
72
|
+
- watertight: Fill entire interior volume using ray casting
|
|
73
|
+
- boundary: Voxelize only the surface with controllable sampling density
|
|
74
|
+
|
|
75
|
+
\b
|
|
76
|
+
Additional options:
|
|
77
|
+
- --invert: Fill outside instead of inside (watertight mode)
|
|
78
|
+
- --boundary-sampling-density: Surface sampling density (boundary mode)
|
|
79
|
+
|
|
80
|
+
\b
|
|
81
|
+
Examples:
|
|
82
|
+
# Convert mesh interior to segmentation (default)
|
|
83
|
+
copick convert mesh2seg -i "membrane:user1/manual-001" -o "membrane:mesh2seg/from-mesh-001@10.0"
|
|
84
|
+
|
|
85
|
+
# Convert mesh boundary only with high sampling density
|
|
86
|
+
copick convert mesh2seg --mode boundary --boundary-sampling-density 2.0 \\
|
|
87
|
+
-i "membrane:user1/manual-001" -o "membrane:mesh2seg/boundary-001@10.0"
|
|
88
|
+
|
|
89
|
+
# Invert watertight mesh (fill outside)
|
|
90
|
+
copick convert mesh2seg --invert -i "membrane:user1/manual-001" -o "membrane:mesh2seg/inverted-001@10.0"
|
|
91
|
+
|
|
92
|
+
# Convert all manual meshes using pattern matching with multilabel output
|
|
93
|
+
copick convert mesh2seg -i "membrane:user1/manual-.*" -o "membrane:mesh2seg/from-mesh-{input_session_id}@10.0?multilabel=true"
|
|
94
|
+
"""
|
|
95
|
+
from copick_utils.converters.segmentation_from_mesh import segmentation_from_mesh_lazy_batch
|
|
96
|
+
|
|
97
|
+
logger = get_logger(__name__, debug=debug)
|
|
98
|
+
|
|
99
|
+
root = copick.from_file(config)
|
|
100
|
+
run_names_list = list(run_names) if run_names else None
|
|
101
|
+
|
|
102
|
+
# Create config directly from URIs with smart defaults
|
|
103
|
+
try:
|
|
104
|
+
task_config = create_simple_config(
|
|
105
|
+
input_uri=input_uri,
|
|
106
|
+
input_type="mesh",
|
|
107
|
+
output_uri=output_uri,
|
|
108
|
+
output_type="segmentation",
|
|
109
|
+
command_name="mesh2seg",
|
|
110
|
+
)
|
|
111
|
+
except ValueError as e:
|
|
112
|
+
raise click.BadParameter(str(e)) from e
|
|
113
|
+
|
|
114
|
+
# Extract parameters for logging and processing
|
|
115
|
+
input_params = parse_copick_uri(input_uri, "mesh")
|
|
116
|
+
output_params = parse_copick_uri(output_uri, "segmentation")
|
|
117
|
+
|
|
118
|
+
voxel_spacing_output = output_params["voxel_spacing"]
|
|
119
|
+
if isinstance(voxel_spacing_output, str):
|
|
120
|
+
voxel_spacing_output = float(voxel_spacing_output)
|
|
121
|
+
multilabel_output = output_params.get("multilabel") or False
|
|
122
|
+
|
|
123
|
+
logger.info(f"Converting mesh to segmentation for object '{input_params['object_name']}'")
|
|
124
|
+
logger.info(f"Source mesh pattern: {input_params['user_id']}/{input_params['session_id']}")
|
|
125
|
+
logger.info(
|
|
126
|
+
f"Target segmentation template: {output_params['name']} ({output_params['user_id']}/{output_params['session_id']})",
|
|
127
|
+
)
|
|
128
|
+
logger.info(f"Mode: {mode}, voxel spacing: {voxel_spacing_output}, multilabel: {multilabel_output}")
|
|
129
|
+
if mode == "boundary":
|
|
130
|
+
logger.info(f"Boundary sampling density: {boundary_sampling_density}")
|
|
131
|
+
if invert:
|
|
132
|
+
logger.info("Volume inversion: enabled")
|
|
133
|
+
|
|
134
|
+
# Parallel discovery and processing - no sequential bottleneck!
|
|
135
|
+
results = segmentation_from_mesh_lazy_batch(
|
|
136
|
+
root=root,
|
|
137
|
+
config=task_config,
|
|
138
|
+
run_names=run_names_list,
|
|
139
|
+
workers=workers,
|
|
140
|
+
voxel_spacing=voxel_spacing_output,
|
|
141
|
+
tomo_type=tomo_type,
|
|
142
|
+
is_multilabel=multilabel_output,
|
|
143
|
+
mode=mode,
|
|
144
|
+
boundary_sampling_density=boundary_sampling_density,
|
|
145
|
+
invert=invert,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
successful = sum(1 for result in results.values() if result and result.get("processed", 0) > 0)
|
|
149
|
+
total_voxels = sum(result.get("voxels_created", 0) for result in results.values() if result)
|
|
150
|
+
total_processed = sum(result.get("processed", 0) for result in results.values() if result)
|
|
151
|
+
|
|
152
|
+
# Collect all errors
|
|
153
|
+
all_errors = []
|
|
154
|
+
for result in results.values():
|
|
155
|
+
if result and result.get("errors"):
|
|
156
|
+
all_errors.extend(result["errors"])
|
|
157
|
+
|
|
158
|
+
logger.info(f"Completed: {successful}/{len(results)} runs processed successfully")
|
|
159
|
+
logger.info(f"Total conversion tasks completed: {total_processed}")
|
|
160
|
+
logger.info(f"Total voxels created: {total_voxels}")
|
|
161
|
+
|
|
162
|
+
if all_errors:
|
|
163
|
+
logger.warning(f"Encountered {len(all_errors)} errors during processing")
|
|
164
|
+
for error in all_errors[:5]: # Show first 5 errors
|
|
165
|
+
logger.warning(f" - {error}")
|
|
166
|
+
if len(all_errors) > 5:
|
|
167
|
+
logger.warning(f" ... and {len(all_errors) - 5} more errors")
|