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,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.6.1
3
+ Version: 1.0.1
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: copick>=0.8.0
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'