copick-utils 0.6.1__py3-none-any.whl → 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- copick_utils/__init__.py +1 -1
- copick_utils/cli/__init__.py +33 -0
- copick_utils/cli/clipmesh.py +161 -0
- copick_utils/cli/clippicks.py +154 -0
- copick_utils/cli/clipseg.py +163 -0
- copick_utils/cli/conversion_commands.py +32 -0
- copick_utils/cli/enclosed.py +191 -0
- copick_utils/cli/filter_components.py +166 -0
- copick_utils/cli/fit_spline.py +191 -0
- copick_utils/cli/hull.py +138 -0
- copick_utils/cli/input_output_selection.py +76 -0
- copick_utils/cli/logical_commands.py +29 -0
- copick_utils/cli/mesh2picks.py +170 -0
- copick_utils/cli/mesh2seg.py +167 -0
- copick_utils/cli/meshop.py +262 -0
- copick_utils/cli/picks2ellipsoid.py +171 -0
- copick_utils/cli/picks2mesh.py +181 -0
- copick_utils/cli/picks2plane.py +156 -0
- copick_utils/cli/picks2seg.py +134 -0
- copick_utils/cli/picks2sphere.py +170 -0
- copick_utils/cli/picks2surface.py +164 -0
- copick_utils/cli/picksin.py +146 -0
- copick_utils/cli/picksout.py +148 -0
- copick_utils/cli/processing_commands.py +18 -0
- copick_utils/cli/seg2mesh.py +135 -0
- copick_utils/cli/seg2picks.py +128 -0
- copick_utils/cli/segop.py +248 -0
- copick_utils/cli/separate_components.py +155 -0
- copick_utils/cli/skeletonize.py +164 -0
- copick_utils/cli/util.py +580 -0
- copick_utils/cli/validbox.py +155 -0
- copick_utils/converters/__init__.py +35 -0
- copick_utils/converters/converter_common.py +543 -0
- copick_utils/converters/ellipsoid_from_picks.py +335 -0
- copick_utils/converters/lazy_converter.py +576 -0
- copick_utils/converters/mesh_from_picks.py +209 -0
- copick_utils/converters/mesh_from_segmentation.py +119 -0
- copick_utils/converters/picks_from_mesh.py +542 -0
- copick_utils/converters/picks_from_segmentation.py +168 -0
- copick_utils/converters/plane_from_picks.py +251 -0
- copick_utils/converters/segmentation_from_mesh.py +291 -0
- copick_utils/{segmentation → converters}/segmentation_from_picks.py +123 -13
- copick_utils/converters/sphere_from_picks.py +306 -0
- copick_utils/converters/surface_from_picks.py +337 -0
- copick_utils/logical/__init__.py +43 -0
- copick_utils/logical/distance_operations.py +604 -0
- copick_utils/logical/enclosed_operations.py +222 -0
- copick_utils/logical/mesh_operations.py +443 -0
- copick_utils/logical/point_operations.py +303 -0
- copick_utils/logical/segmentation_operations.py +399 -0
- copick_utils/process/__init__.py +47 -0
- copick_utils/process/connected_components.py +360 -0
- copick_utils/process/filter_components.py +306 -0
- copick_utils/process/hull.py +106 -0
- copick_utils/process/skeletonize.py +326 -0
- copick_utils/process/spline_fitting.py +648 -0
- copick_utils/process/validbox.py +333 -0
- copick_utils/util/__init__.py +6 -0
- copick_utils/util/config_models.py +614 -0
- {copick_utils-0.6.1.dist-info → copick_utils-1.0.0.dist-info}/METADATA +15 -2
- copick_utils-1.0.0.dist-info/RECORD +71 -0
- copick_utils-1.0.0.dist-info/entry_points.txt +29 -0
- copick_utils/segmentation/picks_from_segmentation.py +0 -81
- copick_utils-0.6.1.dist-info/RECORD +0 -14
- /copick_utils/{segmentation → io}/__init__.py +0 -0
- {copick_utils-0.6.1.dist-info → copick_utils-1.0.0.dist-info}/WHEEL +0 -0
- {copick_utils-0.6.1.dist-info → copick_utils-1.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
"""Pydantic models for lazy task discovery configuration."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Literal, Optional
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field, field_validator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SelectorConfig(BaseModel):
|
|
9
|
+
"""Pydantic model for selector configuration with validation."""
|
|
10
|
+
|
|
11
|
+
input_type: Literal["picks", "mesh", "segmentation"]
|
|
12
|
+
output_type: Literal["picks", "mesh", "segmentation"]
|
|
13
|
+
input_object_name: str
|
|
14
|
+
input_user_id: str
|
|
15
|
+
input_session_id: str
|
|
16
|
+
output_object_name: str
|
|
17
|
+
output_user_id: str = "converter"
|
|
18
|
+
output_session_id: str = "0"
|
|
19
|
+
individual_outputs: bool = False
|
|
20
|
+
segmentation_name: Optional[str] = None
|
|
21
|
+
voxel_spacing: Optional[float] = None
|
|
22
|
+
|
|
23
|
+
@field_validator("segmentation_name")
|
|
24
|
+
@classmethod
|
|
25
|
+
def validate_segmentation_name(cls, v, info):
|
|
26
|
+
"""Ensure segmentation_name is provided when needed."""
|
|
27
|
+
values = info.data
|
|
28
|
+
input_type = values.get("input_type")
|
|
29
|
+
output_type = values.get("output_type")
|
|
30
|
+
|
|
31
|
+
if (input_type == "segmentation" or output_type == "segmentation") and v is None:
|
|
32
|
+
raise ValueError("segmentation_name is required when input_type or output_type is 'segmentation'")
|
|
33
|
+
return v
|
|
34
|
+
|
|
35
|
+
@field_validator("voxel_spacing")
|
|
36
|
+
@classmethod
|
|
37
|
+
def validate_voxel_spacing(cls, v, info):
|
|
38
|
+
"""Ensure voxel_spacing is provided when working with segmentations."""
|
|
39
|
+
values = info.data
|
|
40
|
+
input_type = values.get("input_type")
|
|
41
|
+
output_type = values.get("output_type")
|
|
42
|
+
|
|
43
|
+
if (input_type == "segmentation" or output_type == "segmentation") and v is None:
|
|
44
|
+
raise ValueError("voxel_spacing is required when working with segmentations")
|
|
45
|
+
return v
|
|
46
|
+
|
|
47
|
+
@field_validator("output_session_id")
|
|
48
|
+
@classmethod
|
|
49
|
+
def validate_output_session_id(cls, v, info):
|
|
50
|
+
"""Validate session ID templates contain required placeholders."""
|
|
51
|
+
import re
|
|
52
|
+
|
|
53
|
+
values = info.data
|
|
54
|
+
input_session_id = values.get("input_session_id", "")
|
|
55
|
+
individual_outputs = values.get("individual_outputs", False)
|
|
56
|
+
|
|
57
|
+
# Check if input is a regex pattern
|
|
58
|
+
regex_chars = r"[.*+?^${}()|[\]\\"
|
|
59
|
+
has_regex_chars = any(char in input_session_id for char in regex_chars)
|
|
60
|
+
is_regex = False
|
|
61
|
+
if has_regex_chars:
|
|
62
|
+
try:
|
|
63
|
+
re.compile(input_session_id)
|
|
64
|
+
is_regex = True
|
|
65
|
+
except re.error:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
# Validate placeholders
|
|
69
|
+
if individual_outputs and "{instance_id}" not in v:
|
|
70
|
+
raise ValueError("output_session_id must contain {instance_id} placeholder when individual_outputs=True")
|
|
71
|
+
|
|
72
|
+
if is_regex and "{input_session_id}" not in v:
|
|
73
|
+
raise ValueError(
|
|
74
|
+
"output_session_id must contain {input_session_id} placeholder when using regex input pattern",
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
return v
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def from_uris(
|
|
81
|
+
cls,
|
|
82
|
+
input_uri: str,
|
|
83
|
+
input_type: Literal["picks", "mesh", "segmentation"],
|
|
84
|
+
output_uri: str,
|
|
85
|
+
output_type: Literal["picks", "mesh", "segmentation"],
|
|
86
|
+
individual_outputs: bool = False,
|
|
87
|
+
command_name: Optional[str] = None,
|
|
88
|
+
) -> "SelectorConfig":
|
|
89
|
+
"""Create SelectorConfig from input/output URIs.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
input_uri: Input copick URI string.
|
|
93
|
+
input_type: Type of input object ('picks', 'mesh', 'segmentation').
|
|
94
|
+
output_uri: Output copick URI string (supports smart defaults).
|
|
95
|
+
output_type: Type of output object ('picks', 'mesh', 'segmentation').
|
|
96
|
+
individual_outputs: Whether to create individual outputs.
|
|
97
|
+
command_name: Name of the command (used for smart defaults in output_uri).
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
SelectorConfig instance with parsed URI components.
|
|
101
|
+
|
|
102
|
+
Raises:
|
|
103
|
+
ValueError: If URI parsing fails or required fields are missing.
|
|
104
|
+
"""
|
|
105
|
+
from copick.util.uri import expand_output_uri, parse_copick_uri
|
|
106
|
+
|
|
107
|
+
# Expand output URI with smart defaults
|
|
108
|
+
output_uri = expand_output_uri(
|
|
109
|
+
output_uri=output_uri,
|
|
110
|
+
input_uri=input_uri,
|
|
111
|
+
input_type=input_type,
|
|
112
|
+
output_type=output_type,
|
|
113
|
+
command_name=command_name,
|
|
114
|
+
individual_outputs=individual_outputs,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Parse input URI
|
|
118
|
+
input_params = parse_copick_uri(input_uri, input_type)
|
|
119
|
+
|
|
120
|
+
# Parse output URI (now fully expanded)
|
|
121
|
+
output_params = parse_copick_uri(output_uri, output_type)
|
|
122
|
+
|
|
123
|
+
# Extract common fields
|
|
124
|
+
input_object_name = input_params.get("object_name") or input_params.get("name")
|
|
125
|
+
output_object_name = output_params.get("object_name") or output_params.get("name")
|
|
126
|
+
|
|
127
|
+
# Build config dict
|
|
128
|
+
config_dict = {
|
|
129
|
+
"input_type": input_type,
|
|
130
|
+
"output_type": output_type,
|
|
131
|
+
"input_object_name": input_object_name,
|
|
132
|
+
"input_user_id": input_params["user_id"],
|
|
133
|
+
"input_session_id": input_params["session_id"],
|
|
134
|
+
"output_object_name": output_object_name,
|
|
135
|
+
"output_user_id": output_params["user_id"],
|
|
136
|
+
"output_session_id": output_params["session_id"],
|
|
137
|
+
"individual_outputs": individual_outputs,
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
# Add segmentation-specific fields if needed
|
|
141
|
+
if input_type == "segmentation" or output_type == "segmentation":
|
|
142
|
+
seg_name = input_params.get("name") or output_params.get("name")
|
|
143
|
+
voxel_spacing = input_params.get("voxel_spacing") or output_params.get("voxel_spacing")
|
|
144
|
+
|
|
145
|
+
config_dict["segmentation_name"] = seg_name
|
|
146
|
+
|
|
147
|
+
# Convert voxel_spacing to float if it's a string
|
|
148
|
+
if isinstance(voxel_spacing, str) and voxel_spacing != "*":
|
|
149
|
+
voxel_spacing = float(voxel_spacing)
|
|
150
|
+
config_dict["voxel_spacing"] = voxel_spacing
|
|
151
|
+
|
|
152
|
+
return cls(**config_dict)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class ReferenceConfig(BaseModel):
|
|
156
|
+
"""Pydantic model for reference discovery configuration."""
|
|
157
|
+
|
|
158
|
+
reference_type: Literal["mesh", "segmentation"]
|
|
159
|
+
object_name: Optional[str] = None
|
|
160
|
+
user_id: Optional[str] = None
|
|
161
|
+
session_id: Optional[str] = None
|
|
162
|
+
voxel_spacing: Optional[float] = None
|
|
163
|
+
additional_params: Dict[str, Any] = Field(default_factory=dict)
|
|
164
|
+
|
|
165
|
+
@field_validator("voxel_spacing")
|
|
166
|
+
@classmethod
|
|
167
|
+
def validate_segmentation_voxel_spacing(cls, v, info):
|
|
168
|
+
"""Ensure voxel_spacing is provided for segmentation references."""
|
|
169
|
+
values = info.data
|
|
170
|
+
if values.get("reference_type") == "segmentation" and v is None:
|
|
171
|
+
raise ValueError("voxel_spacing is required for segmentation references")
|
|
172
|
+
return v
|
|
173
|
+
|
|
174
|
+
@field_validator("object_name")
|
|
175
|
+
@classmethod
|
|
176
|
+
def validate_required_fields(cls, v, info):
|
|
177
|
+
"""Ensure required fields are provided."""
|
|
178
|
+
if v is None:
|
|
179
|
+
raise ValueError("object_name is required for reference configuration")
|
|
180
|
+
return v
|
|
181
|
+
|
|
182
|
+
@classmethod
|
|
183
|
+
def from_uri(
|
|
184
|
+
cls,
|
|
185
|
+
uri: str,
|
|
186
|
+
reference_type: Literal["mesh", "segmentation"],
|
|
187
|
+
additional_params: Optional[Dict[str, Any]] = None,
|
|
188
|
+
) -> "ReferenceConfig":
|
|
189
|
+
"""Create ReferenceConfig from a URI.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
uri: Copick URI string for the reference object.
|
|
193
|
+
reference_type: Type of reference ('mesh' or 'segmentation').
|
|
194
|
+
additional_params: Additional parameters to include in the config.
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
ReferenceConfig instance with parsed URI components.
|
|
198
|
+
|
|
199
|
+
Raises:
|
|
200
|
+
ValueError: If URI parsing fails or required fields are missing.
|
|
201
|
+
"""
|
|
202
|
+
from copick.util.uri import parse_copick_uri
|
|
203
|
+
|
|
204
|
+
# Parse URI
|
|
205
|
+
params = parse_copick_uri(uri, reference_type)
|
|
206
|
+
|
|
207
|
+
# Extract fields based on type
|
|
208
|
+
object_name = params.get("object_name") or params.get("name")
|
|
209
|
+
user_id = params["user_id"]
|
|
210
|
+
session_id = params["session_id"]
|
|
211
|
+
voxel_spacing = params.get("voxel_spacing")
|
|
212
|
+
|
|
213
|
+
# Convert voxel_spacing to float if it's a string
|
|
214
|
+
if voxel_spacing and isinstance(voxel_spacing, str) and voxel_spacing != "*":
|
|
215
|
+
voxel_spacing = float(voxel_spacing)
|
|
216
|
+
|
|
217
|
+
return cls(
|
|
218
|
+
reference_type=reference_type,
|
|
219
|
+
object_name=object_name,
|
|
220
|
+
user_id=user_id,
|
|
221
|
+
session_id=session_id,
|
|
222
|
+
voxel_spacing=voxel_spacing,
|
|
223
|
+
additional_params=additional_params or {},
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class TaskConfig(BaseModel):
|
|
228
|
+
"""Pydantic model for complete task configuration."""
|
|
229
|
+
|
|
230
|
+
type: Literal[
|
|
231
|
+
"single_selector",
|
|
232
|
+
"dual_selector",
|
|
233
|
+
"multi_selector",
|
|
234
|
+
"single_selector_with_reference",
|
|
235
|
+
"single_selector_multi_union",
|
|
236
|
+
]
|
|
237
|
+
selector: Optional[SelectorConfig] = None
|
|
238
|
+
selectors: Optional[List[SelectorConfig]] = None
|
|
239
|
+
reference: Optional[ReferenceConfig] = None
|
|
240
|
+
additional_params: Dict[str, Any] = Field(default_factory=dict)
|
|
241
|
+
pairing_method: Optional[str] = "index_order"
|
|
242
|
+
|
|
243
|
+
@field_validator("selector")
|
|
244
|
+
@classmethod
|
|
245
|
+
def validate_single_selector(cls, v, info):
|
|
246
|
+
"""Validate single selector configuration."""
|
|
247
|
+
values = info.data
|
|
248
|
+
config_type = values.get("type")
|
|
249
|
+
if config_type == "single_selector" and v is None:
|
|
250
|
+
raise ValueError("selector is required for single_selector type")
|
|
251
|
+
elif config_type == "single_selector_with_reference" and v is None:
|
|
252
|
+
raise ValueError("selector is required for single_selector_with_reference type")
|
|
253
|
+
elif config_type == "single_selector_multi_union" and v is None:
|
|
254
|
+
raise ValueError("selector is required for single_selector_multi_union type")
|
|
255
|
+
return v
|
|
256
|
+
|
|
257
|
+
@field_validator("selectors")
|
|
258
|
+
@classmethod
|
|
259
|
+
def validate_dual_selectors(cls, v, info):
|
|
260
|
+
"""Validate selector configuration."""
|
|
261
|
+
values = info.data
|
|
262
|
+
config_type = values.get("type")
|
|
263
|
+
if config_type == "dual_selector" and (v is None or len(v) != 2):
|
|
264
|
+
raise ValueError("exactly 2 selectors required for dual_selector type")
|
|
265
|
+
if config_type == "multi_selector" and (v is None or len(v) < 2):
|
|
266
|
+
raise ValueError("at least 2 selectors required for multi_selector type")
|
|
267
|
+
return v
|
|
268
|
+
|
|
269
|
+
@field_validator("reference")
|
|
270
|
+
@classmethod
|
|
271
|
+
def validate_reference(cls, v, info):
|
|
272
|
+
"""Validate reference configuration."""
|
|
273
|
+
values = info.data
|
|
274
|
+
config_type = values.get("type")
|
|
275
|
+
if config_type == "single_selector_with_reference" and v is None:
|
|
276
|
+
raise ValueError("reference is required for single_selector_with_reference type")
|
|
277
|
+
return v
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
# URI-based convenience functions (simplified interface)
|
|
281
|
+
def create_simple_config(
|
|
282
|
+
input_uri: str,
|
|
283
|
+
input_type: Literal["picks", "mesh", "segmentation"],
|
|
284
|
+
output_uri: str,
|
|
285
|
+
output_type: Literal["picks", "mesh", "segmentation"],
|
|
286
|
+
individual_outputs: bool = False,
|
|
287
|
+
command_name: Optional[str] = None,
|
|
288
|
+
) -> TaskConfig:
|
|
289
|
+
"""Create a simple single-selector task configuration from URIs.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
input_uri: Input copick URI string.
|
|
293
|
+
input_type: Type of input object.
|
|
294
|
+
output_uri: Output copick URI string (supports smart defaults).
|
|
295
|
+
output_type: Type of output object.
|
|
296
|
+
individual_outputs: Whether to create individual outputs.
|
|
297
|
+
command_name: Name of the command (used for smart defaults in output_uri).
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
TaskConfig instance ready for use.
|
|
301
|
+
|
|
302
|
+
Example:
|
|
303
|
+
config = create_simple_config(
|
|
304
|
+
input_uri="ribosome:user1/manual-001",
|
|
305
|
+
input_type="picks",
|
|
306
|
+
output_uri="ribosome",
|
|
307
|
+
output_type="mesh",
|
|
308
|
+
command_name="picks2mesh",
|
|
309
|
+
)
|
|
310
|
+
"""
|
|
311
|
+
selector_config = SelectorConfig.from_uris(
|
|
312
|
+
input_uri=input_uri,
|
|
313
|
+
input_type=input_type,
|
|
314
|
+
output_uri=output_uri,
|
|
315
|
+
output_type=output_type,
|
|
316
|
+
individual_outputs=individual_outputs,
|
|
317
|
+
command_name=command_name,
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
return TaskConfig(type="single_selector", selector=selector_config)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def create_single_selector_config(
|
|
324
|
+
input_uri: str,
|
|
325
|
+
input_type: Literal["mesh", "segmentation"],
|
|
326
|
+
output_uri: str,
|
|
327
|
+
output_type: Literal["mesh", "segmentation"],
|
|
328
|
+
command_name: Optional[str] = None,
|
|
329
|
+
operation: str = "union",
|
|
330
|
+
) -> TaskConfig:
|
|
331
|
+
"""
|
|
332
|
+
Create a single-selector config for pattern expansion to N-way operations.
|
|
333
|
+
|
|
334
|
+
Used when a single input URI with pattern should expand to multiple objects
|
|
335
|
+
within each run, then perform an N-way operation on all matched objects.
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
input_uri: Input copick URI string (may contain patterns)
|
|
339
|
+
input_type: Type of input objects
|
|
340
|
+
output_uri: Output copick URI string
|
|
341
|
+
output_type: Type of output object
|
|
342
|
+
command_name: Name of command for smart defaults
|
|
343
|
+
operation: Operation type (currently only "union" supported)
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
TaskConfig with single_selector_multi_union type
|
|
347
|
+
|
|
348
|
+
Raises:
|
|
349
|
+
ValueError: If operation is not "union"
|
|
350
|
+
|
|
351
|
+
Example:
|
|
352
|
+
config = create_single_selector_config(
|
|
353
|
+
input_uri="membrane:user*/manual-*@10.0",
|
|
354
|
+
input_type="segmentation",
|
|
355
|
+
output_uri="merged",
|
|
356
|
+
output_type="segmentation",
|
|
357
|
+
command_name="segop",
|
|
358
|
+
operation="union",
|
|
359
|
+
)
|
|
360
|
+
"""
|
|
361
|
+
if operation not in ["union", "concatenate"]:
|
|
362
|
+
raise ValueError(
|
|
363
|
+
f"Single-input pattern expansion only supports 'union' and 'concatenate' operations, got '{operation}'",
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
selector = SelectorConfig.from_uris(
|
|
367
|
+
input_uri=input_uri,
|
|
368
|
+
input_type=input_type,
|
|
369
|
+
output_uri=output_uri,
|
|
370
|
+
output_type=output_type,
|
|
371
|
+
individual_outputs=False,
|
|
372
|
+
command_name=command_name,
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
return TaskConfig(
|
|
376
|
+
type="single_selector_multi_union",
|
|
377
|
+
selector=selector,
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def create_dual_selector_config(
|
|
382
|
+
input1_uri: str,
|
|
383
|
+
input2_uri: str,
|
|
384
|
+
input_type: Literal["mesh", "segmentation"],
|
|
385
|
+
output_uri: str,
|
|
386
|
+
output_type: Literal["mesh", "segmentation"],
|
|
387
|
+
pairing_method: str = "index_order",
|
|
388
|
+
individual_outputs: bool = False,
|
|
389
|
+
command_name: Optional[str] = None,
|
|
390
|
+
) -> TaskConfig:
|
|
391
|
+
"""Create a dual-selector task configuration from URIs.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
input1_uri: First input copick URI string.
|
|
395
|
+
input2_uri: Second input copick URI string.
|
|
396
|
+
input_type: Type of input objects (both inputs must be same type).
|
|
397
|
+
output_uri: Output copick URI string (supports smart defaults).
|
|
398
|
+
output_type: Type of output object.
|
|
399
|
+
pairing_method: How to pair inputs ("index_order", etc.).
|
|
400
|
+
individual_outputs: Whether to create individual outputs.
|
|
401
|
+
command_name: Name of the command (used for smart defaults in output_uri).
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
TaskConfig instance ready for use.
|
|
405
|
+
|
|
406
|
+
Example:
|
|
407
|
+
config = create_dual_selector_config(
|
|
408
|
+
input1_uri="membrane:user1/manual-001",
|
|
409
|
+
input2_uri="vesicle:user1/auto-001",
|
|
410
|
+
input_type="mesh",
|
|
411
|
+
output_uri="combined",
|
|
412
|
+
output_type="mesh",
|
|
413
|
+
command_name="meshop",
|
|
414
|
+
)
|
|
415
|
+
"""
|
|
416
|
+
from copick.util.uri import parse_copick_uri
|
|
417
|
+
|
|
418
|
+
# Parse both inputs
|
|
419
|
+
parse_copick_uri(input1_uri, input_type)
|
|
420
|
+
input2_params = parse_copick_uri(input2_uri, input_type)
|
|
421
|
+
parse_copick_uri(output_uri, output_type)
|
|
422
|
+
|
|
423
|
+
# Create first selector from URIs
|
|
424
|
+
selector1_config = SelectorConfig.from_uris(
|
|
425
|
+
input_uri=input1_uri,
|
|
426
|
+
input_type=input_type,
|
|
427
|
+
output_uri=output_uri,
|
|
428
|
+
output_type=output_type,
|
|
429
|
+
individual_outputs=individual_outputs,
|
|
430
|
+
command_name=command_name,
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
# Create second selector manually (output fields not used)
|
|
434
|
+
input2_object_name = input2_params.get("object_name") or input2_params.get("name")
|
|
435
|
+
|
|
436
|
+
selector2_dict = {
|
|
437
|
+
"input_type": input_type,
|
|
438
|
+
"output_type": output_type,
|
|
439
|
+
"input_object_name": input2_object_name,
|
|
440
|
+
"input_user_id": input2_params["user_id"],
|
|
441
|
+
"input_session_id": input2_params["session_id"],
|
|
442
|
+
"output_object_name": input2_object_name, # Not used
|
|
443
|
+
"output_user_id": input2_params["user_id"], # Not used
|
|
444
|
+
"output_session_id": input2_params["session_id"], # Not used
|
|
445
|
+
"individual_outputs": False,
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
# Add segmentation-specific fields if needed
|
|
449
|
+
if input_type == "segmentation":
|
|
450
|
+
seg_name = input2_params.get("name")
|
|
451
|
+
voxel_spacing = input2_params.get("voxel_spacing")
|
|
452
|
+
|
|
453
|
+
if isinstance(voxel_spacing, str) and voxel_spacing != "*":
|
|
454
|
+
voxel_spacing = float(voxel_spacing)
|
|
455
|
+
|
|
456
|
+
selector2_dict["segmentation_name"] = seg_name
|
|
457
|
+
selector2_dict["voxel_spacing"] = voxel_spacing
|
|
458
|
+
|
|
459
|
+
selector2_config = SelectorConfig(**selector2_dict)
|
|
460
|
+
|
|
461
|
+
return TaskConfig(
|
|
462
|
+
type="dual_selector",
|
|
463
|
+
selectors=[selector1_config, selector2_config],
|
|
464
|
+
pairing_method=pairing_method,
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def create_multi_selector_config(
|
|
469
|
+
input_uris: List[str],
|
|
470
|
+
input_type: Literal["mesh", "segmentation"],
|
|
471
|
+
output_uri: str,
|
|
472
|
+
output_type: Literal["mesh", "segmentation"],
|
|
473
|
+
pairing_method: str = "n_way",
|
|
474
|
+
individual_outputs: bool = False,
|
|
475
|
+
command_name: Optional[str] = None,
|
|
476
|
+
) -> TaskConfig:
|
|
477
|
+
"""
|
|
478
|
+
Create a multi-selector task configuration from N input URIs.
|
|
479
|
+
|
|
480
|
+
Args:
|
|
481
|
+
input_uris: List of input copick URI strings (N≥2)
|
|
482
|
+
input_type: Type of input objects (all must be same type)
|
|
483
|
+
output_uri: Output copick URI string
|
|
484
|
+
output_type: Type of output object
|
|
485
|
+
pairing_method: How to pair inputs ("n_way")
|
|
486
|
+
individual_outputs: Whether to create individual outputs
|
|
487
|
+
command_name: Name of command for smart defaults
|
|
488
|
+
|
|
489
|
+
Returns:
|
|
490
|
+
TaskConfig with multi_selector type
|
|
491
|
+
|
|
492
|
+
Raises:
|
|
493
|
+
ValueError: If fewer than 2 input URIs provided
|
|
494
|
+
|
|
495
|
+
Example:
|
|
496
|
+
config = create_multi_selector_config(
|
|
497
|
+
input_uris=[
|
|
498
|
+
"membrane:user1/manual-*",
|
|
499
|
+
"vesicle:user2/auto-*",
|
|
500
|
+
"ribosome:user3/pred-*"
|
|
501
|
+
],
|
|
502
|
+
input_type="segmentation",
|
|
503
|
+
output_uri="merged",
|
|
504
|
+
output_type="segmentation",
|
|
505
|
+
command_name="segop",
|
|
506
|
+
)
|
|
507
|
+
"""
|
|
508
|
+
from copick.util.uri import parse_copick_uri
|
|
509
|
+
|
|
510
|
+
if len(input_uris) < 2:
|
|
511
|
+
raise ValueError(f"At least 2 input URIs required, got {len(input_uris)}")
|
|
512
|
+
|
|
513
|
+
# Create selector for first input (determines output config)
|
|
514
|
+
first_selector = SelectorConfig.from_uris(
|
|
515
|
+
input_uri=input_uris[0],
|
|
516
|
+
input_type=input_type,
|
|
517
|
+
output_uri=output_uri,
|
|
518
|
+
output_type=output_type,
|
|
519
|
+
individual_outputs=individual_outputs,
|
|
520
|
+
command_name=command_name,
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
selectors = [first_selector]
|
|
524
|
+
|
|
525
|
+
# Create selectors for remaining inputs (output fields unused)
|
|
526
|
+
for input_uri in input_uris[1:]:
|
|
527
|
+
params = parse_copick_uri(input_uri, input_type)
|
|
528
|
+
object_name = params.get("object_name") or params.get("name")
|
|
529
|
+
|
|
530
|
+
selector_dict = {
|
|
531
|
+
"input_type": input_type,
|
|
532
|
+
"output_type": output_type,
|
|
533
|
+
"input_object_name": object_name,
|
|
534
|
+
"input_user_id": params["user_id"],
|
|
535
|
+
"input_session_id": params["session_id"],
|
|
536
|
+
"output_object_name": object_name, # Not used
|
|
537
|
+
"output_user_id": params["user_id"], # Not used
|
|
538
|
+
"output_session_id": params["session_id"], # Not used
|
|
539
|
+
"individual_outputs": False,
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
# Add segmentation-specific fields
|
|
543
|
+
if input_type == "segmentation":
|
|
544
|
+
voxel_spacing = params.get("voxel_spacing")
|
|
545
|
+
if isinstance(voxel_spacing, str) and voxel_spacing != "*":
|
|
546
|
+
voxel_spacing = float(voxel_spacing)
|
|
547
|
+
|
|
548
|
+
selector_dict["segmentation_name"] = object_name
|
|
549
|
+
selector_dict["voxel_spacing"] = voxel_spacing
|
|
550
|
+
|
|
551
|
+
selectors.append(SelectorConfig(**selector_dict))
|
|
552
|
+
|
|
553
|
+
return TaskConfig(
|
|
554
|
+
type="multi_selector",
|
|
555
|
+
selectors=selectors,
|
|
556
|
+
pairing_method=pairing_method,
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def create_reference_config(
|
|
561
|
+
input_uri: str,
|
|
562
|
+
input_type: Literal["picks", "mesh", "segmentation"],
|
|
563
|
+
output_uri: str,
|
|
564
|
+
output_type: Literal["picks", "mesh", "segmentation"],
|
|
565
|
+
reference_uri: str,
|
|
566
|
+
reference_type: Literal["mesh", "segmentation"],
|
|
567
|
+
additional_params: Optional[Dict[str, Any]] = None,
|
|
568
|
+
command_name: Optional[str] = None,
|
|
569
|
+
) -> TaskConfig:
|
|
570
|
+
"""Create a single-selector-with-reference task configuration from URIs.
|
|
571
|
+
|
|
572
|
+
Args:
|
|
573
|
+
input_uri: Input copick URI string.
|
|
574
|
+
input_type: Type of input object.
|
|
575
|
+
output_uri: Output copick URI string (supports smart defaults).
|
|
576
|
+
output_type: Type of output object.
|
|
577
|
+
reference_uri: Reference copick URI string.
|
|
578
|
+
reference_type: Type of reference object.
|
|
579
|
+
additional_params: Additional parameters for reference config.
|
|
580
|
+
command_name: Name of the command (used for smart defaults in output_uri).
|
|
581
|
+
|
|
582
|
+
Returns:
|
|
583
|
+
TaskConfig instance ready for use.
|
|
584
|
+
|
|
585
|
+
Example:
|
|
586
|
+
config = create_reference_config(
|
|
587
|
+
input_uri="ribosome:user1/all-001",
|
|
588
|
+
input_type="picks",
|
|
589
|
+
output_uri="ribosome",
|
|
590
|
+
output_type="picks",
|
|
591
|
+
reference_uri="boundary:user1/boundary-001",
|
|
592
|
+
reference_type="mesh",
|
|
593
|
+
command_name="picksin",
|
|
594
|
+
)
|
|
595
|
+
"""
|
|
596
|
+
selector_config = SelectorConfig.from_uris(
|
|
597
|
+
input_uri=input_uri,
|
|
598
|
+
input_type=input_type,
|
|
599
|
+
output_uri=output_uri,
|
|
600
|
+
output_type=output_type,
|
|
601
|
+
command_name=command_name,
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
reference_config = ReferenceConfig.from_uri(
|
|
605
|
+
uri=reference_uri,
|
|
606
|
+
reference_type=reference_type,
|
|
607
|
+
additional_params=additional_params,
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
return TaskConfig(
|
|
611
|
+
type="single_selector_with_reference",
|
|
612
|
+
selector=selector_config,
|
|
613
|
+
reference=reference_config,
|
|
614
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: copick-utils
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: Utilities for copick
|
|
5
5
|
Project-URL: Repository, https://github.com/KyleHarrington/copick-utils.git
|
|
6
6
|
Project-URL: Issues, https://github.com/KyleHarrington/copick-utils/issues
|
|
@@ -28,7 +28,20 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
28
28
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
29
29
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
30
30
|
Requires-Python: >=3.9
|
|
31
|
-
Requires-Dist:
|
|
31
|
+
Requires-Dist: click
|
|
32
|
+
Requires-Dist: click-option-group
|
|
33
|
+
Requires-Dist: copick>=1.16.0
|
|
34
|
+
Requires-Dist: manifold3d
|
|
35
|
+
Requires-Dist: mapbox-earcut
|
|
36
|
+
Requires-Dist: numpy
|
|
37
|
+
Requires-Dist: rtree
|
|
38
|
+
Requires-Dist: scikit-image
|
|
39
|
+
Requires-Dist: scikit-learn
|
|
40
|
+
Requires-Dist: scipy
|
|
41
|
+
Requires-Dist: shapely
|
|
42
|
+
Requires-Dist: tqdm
|
|
43
|
+
Requires-Dist: trimesh
|
|
44
|
+
Requires-Dist: zarr
|
|
32
45
|
Provides-Extra: dev
|
|
33
46
|
Requires-Dist: black>=25.1.0; extra == 'dev'
|
|
34
47
|
Requires-Dist: hatch-vcs>=0.4.0; extra == 'dev'
|