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.
- 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.1.dist-info}/METADATA +15 -2
- copick_utils-1.0.1.dist-info/RECORD +71 -0
- {copick_utils-0.6.1.dist-info → copick_utils-1.0.1.dist-info}/WHEEL +1 -1
- copick_utils-1.0.1.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.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""CLI commands for segmentation logical operations (boolean operations)."""
|
|
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_boolean_operation_option,
|
|
12
|
+
add_multi_input_options,
|
|
13
|
+
add_output_option,
|
|
14
|
+
add_workers_option,
|
|
15
|
+
)
|
|
16
|
+
from copick_utils.util.config_models import (
|
|
17
|
+
create_dual_selector_config,
|
|
18
|
+
create_multi_selector_config,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@click.command(
|
|
23
|
+
context_settings={"show_default": True},
|
|
24
|
+
short_help="Perform boolean operations between segmentations.",
|
|
25
|
+
no_args_is_help=True,
|
|
26
|
+
)
|
|
27
|
+
@add_config_option
|
|
28
|
+
@optgroup.group("\nInput Options", help="Options related to input segmentations.")
|
|
29
|
+
@optgroup.option(
|
|
30
|
+
"--run-names",
|
|
31
|
+
"-r",
|
|
32
|
+
multiple=True,
|
|
33
|
+
help="Specific run names to process (default: all runs).",
|
|
34
|
+
)
|
|
35
|
+
@add_multi_input_options("segmentation")
|
|
36
|
+
@optgroup.group("\nTool Options", help="Options related to this tool.")
|
|
37
|
+
@add_boolean_operation_option
|
|
38
|
+
@optgroup.option(
|
|
39
|
+
"--voxel-spacing",
|
|
40
|
+
"-vs",
|
|
41
|
+
type=float,
|
|
42
|
+
required=True,
|
|
43
|
+
help="Voxel spacing for input and output segmentations.",
|
|
44
|
+
)
|
|
45
|
+
@add_workers_option
|
|
46
|
+
@optgroup.group("\nOutput Options", help="Options related to output segmentations.")
|
|
47
|
+
@add_output_option("segmentation", default_tool="segop")
|
|
48
|
+
@add_debug_option
|
|
49
|
+
def segop(
|
|
50
|
+
config,
|
|
51
|
+
run_names,
|
|
52
|
+
input_uris,
|
|
53
|
+
operation,
|
|
54
|
+
voxel_spacing,
|
|
55
|
+
workers,
|
|
56
|
+
output_uri,
|
|
57
|
+
debug,
|
|
58
|
+
):
|
|
59
|
+
"""
|
|
60
|
+
Perform boolean operations between segmentations.
|
|
61
|
+
|
|
62
|
+
\b
|
|
63
|
+
URI Format:
|
|
64
|
+
Segmentations: name:user_id/session_id (voxel spacing via --voxel-spacing)
|
|
65
|
+
|
|
66
|
+
\b
|
|
67
|
+
Pattern Support:
|
|
68
|
+
- Glob (default): Use * and ? wildcards (e.g., "membrane:user*/session-*")
|
|
69
|
+
- Regex: Prefix with 're:' (e.g., "re:membrane:user\\d+/session-\\d+")
|
|
70
|
+
|
|
71
|
+
\b
|
|
72
|
+
Operations:
|
|
73
|
+
- union: Combine segmentations (logical OR) - accepts N≥1 inputs
|
|
74
|
+
- difference: First minus second - requires exactly 2 inputs
|
|
75
|
+
- intersection: Common voxels (logical AND) - requires exactly 2 inputs
|
|
76
|
+
- exclusion: Exclusive or (XOR) - requires exactly 2 inputs
|
|
77
|
+
|
|
78
|
+
\b
|
|
79
|
+
Note: All segmentations are converted to binary for boolean operations.
|
|
80
|
+
Voxel spacing applies globally to all inputs and output.
|
|
81
|
+
|
|
82
|
+
\b
|
|
83
|
+
Single-Input Pattern Expansion (union only):
|
|
84
|
+
When providing a single -i flag with a pattern, the union operation will
|
|
85
|
+
expand the pattern within each run and merge all matching segmentations.
|
|
86
|
+
This is useful for combining multiple versions/annotations within each run.
|
|
87
|
+
|
|
88
|
+
\b
|
|
89
|
+
Examples:
|
|
90
|
+
# Single-input union: merge all matching segmentations within each run
|
|
91
|
+
copick logical segop --operation union -vs 10.0 \\
|
|
92
|
+
-i "membrane:user*/manual-*" \\
|
|
93
|
+
-o "merged"
|
|
94
|
+
|
|
95
|
+
# N-way union with multiple -i flags (merge across different objects)
|
|
96
|
+
copick logical segop --operation union -vs 10.0 \\
|
|
97
|
+
-i "membrane:user1/manual-*" \\
|
|
98
|
+
-i "vesicle:user2/auto-*" \\
|
|
99
|
+
-i "ribosome:user3/pred-*" \\
|
|
100
|
+
-o "merged"
|
|
101
|
+
|
|
102
|
+
# N-way union with regex patterns
|
|
103
|
+
copick logical segop --operation union -vs 10.0 \\
|
|
104
|
+
-i "re:membrane:user1/manual-\\d+" \\
|
|
105
|
+
-i "re:vesicle:user2/auto-\\d+" \\
|
|
106
|
+
-o "merged"
|
|
107
|
+
|
|
108
|
+
# 2-way difference (exactly 2 inputs required)
|
|
109
|
+
copick logical segop --operation difference -vs 10.0 \\
|
|
110
|
+
-i "membrane:user1/manual-001" \\
|
|
111
|
+
-i "mask:user1/mask-001" \\
|
|
112
|
+
-o "membrane:segop/masked"
|
|
113
|
+
"""
|
|
114
|
+
logger = get_logger(__name__, debug=debug)
|
|
115
|
+
|
|
116
|
+
# VALIDATION: Check input count vs operation
|
|
117
|
+
num_inputs = len(input_uris)
|
|
118
|
+
|
|
119
|
+
if operation in ["difference", "intersection", "exclusion"]:
|
|
120
|
+
if num_inputs != 2:
|
|
121
|
+
raise click.BadParameter(
|
|
122
|
+
f"'{operation}' operation requires exactly 2 inputs, got {num_inputs}. Provide exactly 2 -i flags.",
|
|
123
|
+
)
|
|
124
|
+
elif operation == "union" and num_inputs < 1:
|
|
125
|
+
raise click.BadParameter(
|
|
126
|
+
f"'{operation}' operation requires at least 1 input, got {num_inputs}. Provide 1 or more -i flags.",
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
root = copick.from_file(config)
|
|
130
|
+
run_names_list = list(run_names) if run_names else None
|
|
131
|
+
|
|
132
|
+
# Append voxel spacing to all URIs
|
|
133
|
+
input_uris_full = [f"{uri}@{voxel_spacing}" if "@" not in uri else uri for uri in input_uris]
|
|
134
|
+
output_uri_full = f"{output_uri}@{voxel_spacing}" if "@" not in output_uri else output_uri
|
|
135
|
+
|
|
136
|
+
# Create appropriate config based on input count
|
|
137
|
+
try:
|
|
138
|
+
if num_inputs == 1:
|
|
139
|
+
# Single input with pattern expansion (only for union)
|
|
140
|
+
from copick_utils.util.config_models import create_single_selector_config
|
|
141
|
+
|
|
142
|
+
task_config = create_single_selector_config(
|
|
143
|
+
input_uri=input_uris_full[0],
|
|
144
|
+
input_type="segmentation",
|
|
145
|
+
output_uri=output_uri_full,
|
|
146
|
+
output_type="segmentation",
|
|
147
|
+
command_name="segop",
|
|
148
|
+
operation=operation,
|
|
149
|
+
)
|
|
150
|
+
elif num_inputs == 2:
|
|
151
|
+
# Use existing dual selector for 2-input operations
|
|
152
|
+
task_config = create_dual_selector_config(
|
|
153
|
+
input1_uri=input_uris_full[0],
|
|
154
|
+
input2_uri=input_uris_full[1],
|
|
155
|
+
input_type="segmentation",
|
|
156
|
+
output_uri=output_uri_full,
|
|
157
|
+
output_type="segmentation",
|
|
158
|
+
command_name="segop",
|
|
159
|
+
)
|
|
160
|
+
else:
|
|
161
|
+
# Use new multi selector for N-way operations (N≥3)
|
|
162
|
+
task_config = create_multi_selector_config(
|
|
163
|
+
input_uris=input_uris_full,
|
|
164
|
+
input_type="segmentation",
|
|
165
|
+
output_uri=output_uri_full,
|
|
166
|
+
output_type="segmentation",
|
|
167
|
+
command_name="segop",
|
|
168
|
+
)
|
|
169
|
+
except ValueError as e:
|
|
170
|
+
raise click.BadParameter(str(e)) from e
|
|
171
|
+
|
|
172
|
+
# Logging
|
|
173
|
+
if num_inputs == 1:
|
|
174
|
+
logger.info(f"Performing {operation} operation with pattern-based input expansion")
|
|
175
|
+
params = parse_copick_uri(input_uris[0], "segmentation")
|
|
176
|
+
logger.info(f" Pattern: {params['name']} ({params['user_id']}/{params['session_id']})")
|
|
177
|
+
logger.info(" Note: Pattern will be expanded to multiple segmentations per run")
|
|
178
|
+
else:
|
|
179
|
+
logger.info(f"Performing {operation} operation on {num_inputs} segmentations")
|
|
180
|
+
for i, uri in enumerate(input_uris, start=1):
|
|
181
|
+
params = parse_copick_uri(uri, "segmentation")
|
|
182
|
+
logger.info(f" Input {i}: {params['name']} ({params['user_id']}/{params['session_id']})")
|
|
183
|
+
|
|
184
|
+
output_params = parse_copick_uri(output_uri_full, "segmentation")
|
|
185
|
+
logger.info(f"Target: {output_params['name']} ({output_params['user_id']}/{output_params['session_id']})")
|
|
186
|
+
|
|
187
|
+
# Select appropriate lazy batch converter
|
|
188
|
+
if num_inputs == 1:
|
|
189
|
+
# Single input with pattern expansion (only union supports this)
|
|
190
|
+
from copick_utils.logical.segmentation_operations import segmentation_multi_union_lazy_batch
|
|
191
|
+
|
|
192
|
+
lazy_batch_functions = {
|
|
193
|
+
"union": segmentation_multi_union_lazy_batch,
|
|
194
|
+
}
|
|
195
|
+
elif num_inputs == 2:
|
|
196
|
+
# Use existing dual converters
|
|
197
|
+
from copick_utils.logical.segmentation_operations import (
|
|
198
|
+
segmentation_difference_lazy_batch,
|
|
199
|
+
segmentation_exclusion_lazy_batch,
|
|
200
|
+
segmentation_intersection_lazy_batch,
|
|
201
|
+
segmentation_union_lazy_batch,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
lazy_batch_functions = {
|
|
205
|
+
"union": segmentation_union_lazy_batch,
|
|
206
|
+
"difference": segmentation_difference_lazy_batch,
|
|
207
|
+
"intersection": segmentation_intersection_lazy_batch,
|
|
208
|
+
"exclusion": segmentation_exclusion_lazy_batch,
|
|
209
|
+
}
|
|
210
|
+
else:
|
|
211
|
+
# Use new N-way converters (only union supports N>2)
|
|
212
|
+
from copick_utils.logical.segmentation_operations import segmentation_multi_union_lazy_batch
|
|
213
|
+
|
|
214
|
+
lazy_batch_functions = {
|
|
215
|
+
"union": segmentation_multi_union_lazy_batch,
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
lazy_batch_function = lazy_batch_functions[operation]
|
|
219
|
+
|
|
220
|
+
# Execute parallel discovery and processing
|
|
221
|
+
results = lazy_batch_function(
|
|
222
|
+
root=root,
|
|
223
|
+
config=task_config,
|
|
224
|
+
run_names=run_names_list,
|
|
225
|
+
workers=workers,
|
|
226
|
+
voxel_spacing=voxel_spacing,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# Aggregate results
|
|
230
|
+
successful = sum(1 for r in results.values() if r and r.get("processed", 0) > 0)
|
|
231
|
+
total_voxels = sum(r.get("voxels_created", 0) for r in results.values() if r)
|
|
232
|
+
total_processed = sum(r.get("processed", 0) for r in results.values() if r)
|
|
233
|
+
|
|
234
|
+
all_errors = []
|
|
235
|
+
for result in results.values():
|
|
236
|
+
if result and result.get("errors"):
|
|
237
|
+
all_errors.extend(result["errors"])
|
|
238
|
+
|
|
239
|
+
logger.info(f"Completed: {successful}/{len(results)} runs processed successfully")
|
|
240
|
+
logger.info(f"Total {operation} operations completed: {total_processed}")
|
|
241
|
+
logger.info(f"Total voxels created: {total_voxels}")
|
|
242
|
+
|
|
243
|
+
if all_errors:
|
|
244
|
+
logger.warning(f"Encountered {len(all_errors)} errors during processing")
|
|
245
|
+
for error in all_errors[:5]:
|
|
246
|
+
logger.warning(f" - {error}")
|
|
247
|
+
if len(all_errors) > 5:
|
|
248
|
+
logger.warning(f" ... and {len(all_errors) - 5} more errors")
|
|
@@ -0,0 +1,155 @@
|
|
|
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 expand_output_uri, parse_copick_uri
|
|
7
|
+
|
|
8
|
+
from copick_utils.cli.util import add_input_option, add_output_option
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.command(
|
|
12
|
+
context_settings={"show_default": True},
|
|
13
|
+
short_help="Separate connected components in segmentations.",
|
|
14
|
+
no_args_is_help=True,
|
|
15
|
+
)
|
|
16
|
+
@add_config_option
|
|
17
|
+
@optgroup.group("\nInput Options", help="Options related to the input segmentation.")
|
|
18
|
+
@optgroup.option(
|
|
19
|
+
"--run-names",
|
|
20
|
+
multiple=True,
|
|
21
|
+
help="Specific run names to process (default: all runs).",
|
|
22
|
+
)
|
|
23
|
+
@add_input_option("segmentation")
|
|
24
|
+
@optgroup.group("\nTool Options", help="Options related to this tool.")
|
|
25
|
+
@optgroup.option(
|
|
26
|
+
"--connectivity",
|
|
27
|
+
"-cn",
|
|
28
|
+
type=click.Choice(["face", "face-edge", "all"]),
|
|
29
|
+
default="all",
|
|
30
|
+
help="Connectivity for connected components (face=6-connected, face-edge=18-connected, all=26-connected).",
|
|
31
|
+
)
|
|
32
|
+
@optgroup.option(
|
|
33
|
+
"--min-size",
|
|
34
|
+
type=float,
|
|
35
|
+
default=None,
|
|
36
|
+
help="Minimum component volume in cubic angstroms (ų) to keep (optional).",
|
|
37
|
+
)
|
|
38
|
+
@optgroup.option(
|
|
39
|
+
"--multilabel/--binary",
|
|
40
|
+
is_flag=True,
|
|
41
|
+
default=True,
|
|
42
|
+
help="Process as multilabel segmentation (analyze each label separately).",
|
|
43
|
+
)
|
|
44
|
+
@optgroup.option(
|
|
45
|
+
"--workers",
|
|
46
|
+
type=int,
|
|
47
|
+
default=8,
|
|
48
|
+
help="Number of worker processes.",
|
|
49
|
+
)
|
|
50
|
+
@optgroup.group("\nOutput Options", help="Options related to output segmentations.")
|
|
51
|
+
@add_output_option("segmentation", default_tool="components")
|
|
52
|
+
@add_debug_option
|
|
53
|
+
def separate_components(
|
|
54
|
+
config,
|
|
55
|
+
run_names,
|
|
56
|
+
input_uri,
|
|
57
|
+
connectivity,
|
|
58
|
+
min_size,
|
|
59
|
+
multilabel,
|
|
60
|
+
workers,
|
|
61
|
+
output_uri,
|
|
62
|
+
debug,
|
|
63
|
+
):
|
|
64
|
+
"""Separate connected components in segmentations into individual segmentations.
|
|
65
|
+
|
|
66
|
+
\b
|
|
67
|
+
URI Format:
|
|
68
|
+
Segmentations: name:user_id/session_id@voxel_spacing
|
|
69
|
+
|
|
70
|
+
\b
|
|
71
|
+
For multilabel segmentations, connected components analysis is performed on each
|
|
72
|
+
label separately. Output segmentations use {instance_id} placeholder for auto-numbering
|
|
73
|
+
(e.g., "inst-0", "inst-1", etc.).
|
|
74
|
+
|
|
75
|
+
\b
|
|
76
|
+
Examples:
|
|
77
|
+
# Separate components with smart defaults (auto user_id and session template)
|
|
78
|
+
copick process separate_components -i "membrane:user1/manual-001@10.0" -o "{instance_id}"
|
|
79
|
+
|
|
80
|
+
# Custom session prefix
|
|
81
|
+
copick process separate_components -i "membrane:user1/manual-001@10.0" -o "membrane:components/inst-{instance_id}"
|
|
82
|
+
|
|
83
|
+
# Full URI specification
|
|
84
|
+
copick process separate_components -i "membrane:user1/manual-001@10.0" -o "membrane:components/comp-{instance_id}@10.0"
|
|
85
|
+
"""
|
|
86
|
+
from copick_utils.process.connected_components import separate_components_batch
|
|
87
|
+
|
|
88
|
+
logger = get_logger(__name__, debug=debug)
|
|
89
|
+
|
|
90
|
+
root = copick.from_file(config)
|
|
91
|
+
run_names_list = list(run_names) if run_names else None
|
|
92
|
+
|
|
93
|
+
# Expand output URI with smart defaults (individual_outputs=True for {instance_id})
|
|
94
|
+
try:
|
|
95
|
+
output_uri = expand_output_uri(
|
|
96
|
+
output_uri=output_uri,
|
|
97
|
+
input_uri=input_uri,
|
|
98
|
+
input_type="segmentation",
|
|
99
|
+
output_type="segmentation",
|
|
100
|
+
command_name="components",
|
|
101
|
+
individual_outputs=True,
|
|
102
|
+
)
|
|
103
|
+
except ValueError as e:
|
|
104
|
+
raise click.BadParameter(f"Error expanding output URI: {e}") from e
|
|
105
|
+
|
|
106
|
+
# Parse input URI
|
|
107
|
+
try:
|
|
108
|
+
input_params = parse_copick_uri(input_uri, "segmentation")
|
|
109
|
+
except ValueError as e:
|
|
110
|
+
raise click.BadParameter(f"Invalid input URI: {e}") from e
|
|
111
|
+
|
|
112
|
+
segmentation_name = input_params["name"]
|
|
113
|
+
segmentation_user_id = input_params["user_id"]
|
|
114
|
+
segmentation_session_id = input_params["session_id"]
|
|
115
|
+
|
|
116
|
+
# Parse output URI (now fully expanded)
|
|
117
|
+
try:
|
|
118
|
+
output_params = parse_copick_uri(output_uri, "segmentation")
|
|
119
|
+
except ValueError as e:
|
|
120
|
+
raise click.BadParameter(f"Invalid output URI: {e}") from e
|
|
121
|
+
|
|
122
|
+
output_user_id = output_params["user_id"]
|
|
123
|
+
output_session_id_template = output_params["session_id"]
|
|
124
|
+
|
|
125
|
+
# Validate that output_session_id_template contains {instance_id}
|
|
126
|
+
if "{instance_id}" not in output_session_id_template:
|
|
127
|
+
raise click.BadParameter("Output URI must contain {instance_id} placeholder for separate_components command")
|
|
128
|
+
|
|
129
|
+
logger.info(f"Separating connected components for segmentation '{segmentation_name}'")
|
|
130
|
+
logger.info(f"Source segmentation: {segmentation_user_id}/{segmentation_session_id}")
|
|
131
|
+
logger.info(f"Output template: {output_params['name']} ({output_user_id}/{output_session_id_template})")
|
|
132
|
+
logger.info(f"Connectivity: {connectivity}")
|
|
133
|
+
if min_size is not None:
|
|
134
|
+
logger.info(f"Minimum size: {min_size} ų")
|
|
135
|
+
logger.info(f"Processing as {'multilabel' if multilabel else 'binary'} segmentation")
|
|
136
|
+
|
|
137
|
+
results = separate_components_batch(
|
|
138
|
+
root=root,
|
|
139
|
+
segmentation_name=segmentation_name,
|
|
140
|
+
segmentation_user_id=segmentation_user_id,
|
|
141
|
+
segmentation_session_id=segmentation_session_id,
|
|
142
|
+
connectivity=connectivity,
|
|
143
|
+
min_size=min_size,
|
|
144
|
+
session_id_template=output_session_id_template,
|
|
145
|
+
output_user_id=output_user_id,
|
|
146
|
+
multilabel=multilabel,
|
|
147
|
+
run_names=run_names_list,
|
|
148
|
+
workers=workers,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
successful = sum(1 for result in results.values() if result and result.get("processed", 0) > 0)
|
|
152
|
+
total_components = sum(result.get("components_created", 0) for result in results.values() if result)
|
|
153
|
+
|
|
154
|
+
logger.info(f"Completed: {successful}/{len(results)} runs processed successfully")
|
|
155
|
+
logger.info(f"Total components created: {total_components}")
|
|
@@ -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 expand_output_uri, parse_copick_uri
|
|
7
|
+
|
|
8
|
+
from copick_utils.cli.util import add_input_option, add_output_option
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.command(
|
|
12
|
+
context_settings={"show_default": True},
|
|
13
|
+
short_help="3D skeletonization of segmentations.",
|
|
14
|
+
no_args_is_help=True,
|
|
15
|
+
)
|
|
16
|
+
@add_config_option
|
|
17
|
+
@optgroup.group("\nInput Options", help="Options related to the input segmentation.")
|
|
18
|
+
@optgroup.option(
|
|
19
|
+
"--run-names",
|
|
20
|
+
multiple=True,
|
|
21
|
+
help="Specific run names to process (default: all runs).",
|
|
22
|
+
)
|
|
23
|
+
@add_input_option("segmentation")
|
|
24
|
+
@optgroup.group("\nTool Options", help="Options related to this tool.")
|
|
25
|
+
@optgroup.option(
|
|
26
|
+
"--method",
|
|
27
|
+
type=click.Choice(["skimage", "distance_transform"]),
|
|
28
|
+
default="skimage",
|
|
29
|
+
help="Skeletonization method.",
|
|
30
|
+
)
|
|
31
|
+
@optgroup.option(
|
|
32
|
+
"--remove-noise/--keep-noise",
|
|
33
|
+
is_flag=True,
|
|
34
|
+
default=True,
|
|
35
|
+
help="Remove small objects before skeletonization.",
|
|
36
|
+
)
|
|
37
|
+
@optgroup.option(
|
|
38
|
+
"--min-object-size",
|
|
39
|
+
type=int,
|
|
40
|
+
default=50,
|
|
41
|
+
help="Minimum size of objects to keep during preprocessing.",
|
|
42
|
+
)
|
|
43
|
+
@optgroup.option(
|
|
44
|
+
"--remove-short-branches/--keep-short-branches",
|
|
45
|
+
is_flag=True,
|
|
46
|
+
default=True,
|
|
47
|
+
help="Remove short branches from skeleton.",
|
|
48
|
+
)
|
|
49
|
+
@optgroup.option(
|
|
50
|
+
"--min-branch-length",
|
|
51
|
+
type=int,
|
|
52
|
+
default=5,
|
|
53
|
+
help="Minimum length of branches to keep.",
|
|
54
|
+
)
|
|
55
|
+
@optgroup.option(
|
|
56
|
+
"--workers",
|
|
57
|
+
type=int,
|
|
58
|
+
default=8,
|
|
59
|
+
help="Number of worker processes.",
|
|
60
|
+
)
|
|
61
|
+
@optgroup.group("\nOutput Options", help="Options related to output segmentations.")
|
|
62
|
+
@add_output_option("segmentation", default_tool="skel")
|
|
63
|
+
@add_debug_option
|
|
64
|
+
def skeletonize(
|
|
65
|
+
config,
|
|
66
|
+
run_names,
|
|
67
|
+
input_uri,
|
|
68
|
+
method,
|
|
69
|
+
remove_noise,
|
|
70
|
+
min_object_size,
|
|
71
|
+
remove_short_branches,
|
|
72
|
+
min_branch_length,
|
|
73
|
+
workers,
|
|
74
|
+
output_uri,
|
|
75
|
+
debug,
|
|
76
|
+
):
|
|
77
|
+
"""3D skeletonization of segmentations using pattern matching.
|
|
78
|
+
|
|
79
|
+
\b
|
|
80
|
+
URI Format:
|
|
81
|
+
Segmentations: name:user_id/session_id@voxel_spacing
|
|
82
|
+
|
|
83
|
+
\b
|
|
84
|
+
This command can process multiple segmentations by matching session IDs against
|
|
85
|
+
a pattern. This is useful for processing the output of connected components
|
|
86
|
+
separation (e.g., pattern "inst-.*" to match "inst-0", "inst-1", etc.).
|
|
87
|
+
|
|
88
|
+
\b
|
|
89
|
+
Examples:
|
|
90
|
+
# Skeletonize exact match
|
|
91
|
+
copick process skeletonize -i "membrane:user1/inst-0@10.0" -o "membrane:skel/skel-0@10.0"
|
|
92
|
+
|
|
93
|
+
# Skeletonize all instances using pattern
|
|
94
|
+
copick process skeletonize -i "membrane:user1/inst-.*@10.0" -o "membrane:skel/skel-{input_session_id}@10.0"
|
|
95
|
+
"""
|
|
96
|
+
from copick_utils.process.skeletonize import skeletonize_batch
|
|
97
|
+
|
|
98
|
+
logger = get_logger(__name__, debug=debug)
|
|
99
|
+
|
|
100
|
+
root = copick.from_file(config)
|
|
101
|
+
run_names_list = list(run_names) if run_names else None
|
|
102
|
+
|
|
103
|
+
# Expand output URI with smart defaults
|
|
104
|
+
try:
|
|
105
|
+
output_uri = expand_output_uri(
|
|
106
|
+
output_uri=output_uri,
|
|
107
|
+
input_uri=input_uri,
|
|
108
|
+
input_type="segmentation",
|
|
109
|
+
output_type="segmentation",
|
|
110
|
+
command_name="skeletonize",
|
|
111
|
+
individual_outputs=False,
|
|
112
|
+
)
|
|
113
|
+
except ValueError as e:
|
|
114
|
+
raise click.BadParameter(f"Error expanding output URI: {e}") from e
|
|
115
|
+
|
|
116
|
+
# Parse input URI
|
|
117
|
+
try:
|
|
118
|
+
input_params = parse_copick_uri(input_uri, "segmentation")
|
|
119
|
+
except ValueError as e:
|
|
120
|
+
raise click.BadParameter(f"Invalid input URI: {e}") from e
|
|
121
|
+
|
|
122
|
+
segmentation_name = input_params["name"]
|
|
123
|
+
segmentation_user_id = input_params["user_id"]
|
|
124
|
+
session_id_pattern = input_params["session_id"]
|
|
125
|
+
|
|
126
|
+
# Parse output URI (now fully expanded)
|
|
127
|
+
try:
|
|
128
|
+
output_params = parse_copick_uri(output_uri, "segmentation")
|
|
129
|
+
except ValueError as e:
|
|
130
|
+
raise click.BadParameter(f"Invalid output URI: {e}") from e
|
|
131
|
+
|
|
132
|
+
output_user_id = output_params["user_id"]
|
|
133
|
+
output_session_id_template = output_params["session_id"]
|
|
134
|
+
|
|
135
|
+
logger.info(f"Skeletonizing segmentations '{segmentation_name}'")
|
|
136
|
+
logger.info(f"Source segmentations: {segmentation_user_id} matching pattern '{session_id_pattern}'")
|
|
137
|
+
logger.info(f"Method: {method}, output user ID: {output_user_id}")
|
|
138
|
+
logger.info(f"Preprocessing: remove_noise={remove_noise} (min_size={min_object_size})")
|
|
139
|
+
logger.info(f"Post-processing: remove_short_branches={remove_short_branches} (min_length={min_branch_length})")
|
|
140
|
+
logger.info(f"Output session ID template: '{output_session_id_template}'")
|
|
141
|
+
|
|
142
|
+
results = skeletonize_batch(
|
|
143
|
+
root=root,
|
|
144
|
+
segmentation_name=segmentation_name,
|
|
145
|
+
segmentation_user_id=segmentation_user_id,
|
|
146
|
+
session_id_pattern=session_id_pattern,
|
|
147
|
+
method=method,
|
|
148
|
+
remove_noise=remove_noise,
|
|
149
|
+
min_object_size=min_object_size,
|
|
150
|
+
remove_short_branches=remove_short_branches,
|
|
151
|
+
min_branch_length=min_branch_length,
|
|
152
|
+
output_session_id_template=output_session_id_template,
|
|
153
|
+
output_user_id=output_user_id,
|
|
154
|
+
run_names=run_names_list,
|
|
155
|
+
workers=workers,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
successful = sum(1 for result in results.values() if result and result.get("processed", 0) > 0)
|
|
159
|
+
total_skeletons = sum(result.get("skeletons_created", 0) for result in results.values() if result)
|
|
160
|
+
total_processed = sum(result.get("segmentations_processed", 0) for result in results.values() if result)
|
|
161
|
+
|
|
162
|
+
logger.info(f"Completed: {successful}/{len(results)} runs processed successfully")
|
|
163
|
+
logger.info(f"Total segmentations processed: {total_processed}")
|
|
164
|
+
logger.info(f"Total skeletons created: {total_skeletons}")
|