octopi 1.4.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.
Files changed (65) hide show
  1. octopi/__init__.py +7 -0
  2. octopi/datasets/__init__.py +0 -0
  3. octopi/datasets/augment.py +83 -0
  4. octopi/datasets/cached_datset.py +113 -0
  5. octopi/datasets/dataset.py +19 -0
  6. octopi/datasets/generators.py +458 -0
  7. octopi/datasets/io.py +200 -0
  8. octopi/datasets/mixup.py +49 -0
  9. octopi/datasets/multi_config_generator.py +252 -0
  10. octopi/entry_points/__init__.py +0 -0
  11. octopi/entry_points/common.py +119 -0
  12. octopi/entry_points/create_slurm_submission.py +251 -0
  13. octopi/entry_points/groups.py +152 -0
  14. octopi/entry_points/run_create_targets.py +234 -0
  15. octopi/entry_points/run_evaluate.py +99 -0
  16. octopi/entry_points/run_extract_mb_picks.py +191 -0
  17. octopi/entry_points/run_extract_midpoint.py +143 -0
  18. octopi/entry_points/run_localize.py +176 -0
  19. octopi/entry_points/run_optuna.py +161 -0
  20. octopi/entry_points/run_segment.py +154 -0
  21. octopi/entry_points/run_train.py +189 -0
  22. octopi/extract/__init__.py +0 -0
  23. octopi/extract/localize.py +217 -0
  24. octopi/extract/membranebound_extract.py +263 -0
  25. octopi/extract/midpoint_extract.py +193 -0
  26. octopi/main.py +33 -0
  27. octopi/models/AttentionUnet.py +56 -0
  28. octopi/models/MedNeXt.py +111 -0
  29. octopi/models/ModelTemplate.py +36 -0
  30. octopi/models/SegResNet.py +92 -0
  31. octopi/models/Unet.py +59 -0
  32. octopi/models/UnetPlusPlus.py +47 -0
  33. octopi/models/__init__.py +0 -0
  34. octopi/models/common.py +72 -0
  35. octopi/processing/__init__.py +0 -0
  36. octopi/processing/create_targets_from_picks.py +224 -0
  37. octopi/processing/downloader.py +138 -0
  38. octopi/processing/downsample.py +125 -0
  39. octopi/processing/evaluate.py +302 -0
  40. octopi/processing/importers.py +116 -0
  41. octopi/processing/segmentation_from_picks.py +167 -0
  42. octopi/pytorch/__init__.py +0 -0
  43. octopi/pytorch/hyper_search.py +244 -0
  44. octopi/pytorch/model_search_submitter.py +291 -0
  45. octopi/pytorch/segmentation.py +363 -0
  46. octopi/pytorch/segmentation_multigpu.py +162 -0
  47. octopi/pytorch/trainer.py +465 -0
  48. octopi/pytorch_lightning/__init__.py +0 -0
  49. octopi/pytorch_lightning/optuna_pl_ddp.py +273 -0
  50. octopi/pytorch_lightning/train_pl.py +244 -0
  51. octopi/utils/__init__.py +0 -0
  52. octopi/utils/config.py +57 -0
  53. octopi/utils/io.py +215 -0
  54. octopi/utils/losses.py +86 -0
  55. octopi/utils/parsers.py +162 -0
  56. octopi/utils/progress.py +78 -0
  57. octopi/utils/stopping_criteria.py +143 -0
  58. octopi/utils/submit_slurm.py +95 -0
  59. octopi/utils/visualization_tools.py +290 -0
  60. octopi/workflows.py +262 -0
  61. octopi-1.4.0.dist-info/METADATA +119 -0
  62. octopi-1.4.0.dist-info/RECORD +65 -0
  63. octopi-1.4.0.dist-info/WHEEL +4 -0
  64. octopi-1.4.0.dist-info/entry_points.txt +3 -0
  65. octopi-1.4.0.dist-info/licenses/LICENSE +41 -0
@@ -0,0 +1,99 @@
1
+ from octopi.utils import parsers
2
+ from octopi import cli_context
3
+ import rich_click as click
4
+ from typing import List
5
+
6
+ def my_evaluator(
7
+ copick_config_path: str,
8
+ ground_truth_user_id: str,
9
+ ground_truth_session_id: str,
10
+ predict_user_id: str,
11
+ predict_session_id: str,
12
+ save_path: str,
13
+ distance_threshold_scale: float,
14
+ object_names: List[str] = None,
15
+ runIDs: List[str] = None
16
+ ):
17
+ import octopi.processing.evaluate as evaluate
18
+
19
+ eval = evaluate.evaluator(
20
+ copick_config_path,
21
+ ground_truth_user_id,
22
+ ground_truth_session_id,
23
+ predict_user_id,
24
+ predict_session_id,
25
+ object_names=object_names
26
+ )
27
+
28
+ eval.run(save_path=save_path, distance_threshold_scale=distance_threshold_scale, runIDs=runIDs)
29
+
30
+
31
+ @click.command('evaluate', context_settings=cli_context)
32
+ # Output Arguments
33
+ @click.option('-o','--output', type=click.Path(), default='scores',
34
+ help="Path to save evaluation results")
35
+ # Evaluation Parameters
36
+ @click.option('-names','--object-names', type=str, default=None,
37
+ callback=lambda ctx, param, value: parsers.parse_list(value) if value else None,
38
+ help="Optional list of object names to evaluate, e.g., ribosome,apoferritin")
39
+ @click.option('-dts','--distance-threshold-scale', type=float, default=0.8,
40
+ help="Compute Distance Threshold Based on Particle Radius")
41
+ # Input Arguments
42
+ @click.option('--run-ids', type=str, default=None,
43
+ callback=lambda ctx, param, value: parsers.parse_list(value) if value else None,
44
+ help="Optional list of run IDs to evaluate, e.g., run1,run2,run3 or [run1,run2,run3]")
45
+ @click.option('-psid', '--predict-session-id', type=str,
46
+ default='1', help="Session ID for prediction data")
47
+ @click.option('-puid','--predict-user-id', type=str, required=True,
48
+ default='octopi', help="User ID for prediction data")
49
+ @click.option('-gtsid','--ground-truth-session-id', type=str, default=None,
50
+ help="Session ID for ground truth data")
51
+ @click.option('-gtuid','--ground-truth-user-id', type=str, required=True,
52
+ help="User ID for ground truth data")
53
+ @click.option('-c', '--config', type=click.Path(exists=True), required=True,
54
+ help="Path to the copick configuration file")
55
+ def cli(config, ground_truth_user_id, ground_truth_session_id,
56
+ predict_user_id, predict_session_id, run_ids,
57
+ distance_threshold_scale, object_names,
58
+ output):
59
+ """
60
+ Evaluate particle localization performance against ground truth annotations.
61
+
62
+ This command compares predicted particle picks against expert annotations using distance-based
63
+ matching. A prediction is considered correct (true positive) if it falls within a specified
64
+ distance threshold of a ground truth annotation. The threshold is defined as a fraction of
65
+ the particle's radius (default: 0.8 = 80% of radius).
66
+
67
+ Computed metrics include:
68
+ • Precision: Fraction of predictions that match ground truth
69
+ • Recall: Fraction of ground truth particles that were detected
70
+ • F-beta scores: Harmonic mean of precision and recall (with configurable beta)
71
+
72
+ Results are saved as a YAML file containing per-object and aggregate statistics, making it
73
+ easy to track model performance across experiments and compare different localization methods.
74
+
75
+ \b
76
+ Examples:
77
+ # Evaluate predictions against Data Portal annotations
78
+ octopi evaluate -c config.json \\
79
+ -gtuid data-portal -gtsid 0 \\
80
+ -puid octopi -psid 1 \\
81
+ -o evaluation_results.yaml
82
+ """
83
+
84
+ # Call the evaluate function with parsed arguments
85
+ my_evaluator(
86
+ copick_config_path=config,
87
+ ground_truth_user_id=ground_truth_user_id,
88
+ ground_truth_session_id=ground_truth_session_id,
89
+ predict_user_id=predict_user_id,
90
+ predict_session_id=predict_session_id,
91
+ save_path=output,
92
+ distance_threshold_scale=distance_threshold_scale,
93
+ object_names=object_names,
94
+ runIDs=run_ids
95
+ )
96
+
97
+
98
+ if __name__ == "__main__":
99
+ cli()
@@ -0,0 +1,191 @@
1
+ from typing import List, Tuple, Optional
2
+ from octopi.utils import parsers
3
+ import rich_click as click
4
+
5
+ def extract_membrane_bound_picks(
6
+ config: str,
7
+ voxel_size: float,
8
+ distance_threshold: float,
9
+ picks_info: Tuple[str, str, str],
10
+ organelle_info: Tuple[str, str, str],
11
+ membrane_info: Tuple[str, str, str],
12
+ save_user_id: str,
13
+ save_session_id: str,
14
+ runIDs: List[str],
15
+ n_procs: int = None
16
+ ):
17
+ from octopi.extract import membranebound_extract as extract
18
+ import multiprocess as mp
19
+ from tqdm import tqdm
20
+ import copick
21
+
22
+ # Load Copick Project for Writing
23
+ root = copick.from_file(config)
24
+
25
+ # Either Specify Input RunIDs or Run on All RunIDs
26
+ if runIDs: print('Extracting Membrane Bound Proteins on the Following RunIDs: ', runIDs)
27
+ run_ids = runIDs if runIDs else [run.name for run in root.runs]
28
+ n_run_ids = len(run_ids)
29
+
30
+ # Determine the number of processes to use
31
+ if n_procs is None:
32
+ n_procs = min(mp.cpu_count(), n_run_ids)
33
+ print(f"Using {n_procs} processes to parallelize across {n_run_ids} run IDs.")
34
+
35
+ # Run Membrane-Protein Isolation - Main Parallelization Loop
36
+ with mp.Pool(processes=n_procs) as pool:
37
+ with tqdm(total=n_run_ids, desc="Membrane-Protein Isolation", unit="run") as pbar:
38
+ worker_func = lambda run_id: extract.process_membrane_bound_extract(
39
+ root.get_run(run_id),
40
+ voxel_size,
41
+ picks_info,
42
+ membrane_info,
43
+ organelle_info,
44
+ save_user_id,
45
+ save_session_id,
46
+ distance_threshold
47
+ )
48
+
49
+ for _ in pool.imap_unordered(worker_func, run_ids, chunksize=1):
50
+ pbar.update(1)
51
+
52
+ print('Extraction of Membrane-Bound Proteins Complete!')
53
+
54
+
55
+ def save_parameters(config: str,
56
+ voxel_size: float,
57
+ picks_info: tuple,
58
+ membrane_info: tuple,
59
+ organelle_info: tuple,
60
+ save_user_id: str,
61
+ save_session_id: str,
62
+ distance_threshold: float,
63
+ runIDs: list,
64
+ output_path: str):
65
+ import octopi.utils.io as io
66
+ import pprint
67
+
68
+ params_dict = {
69
+ "input": {
70
+ "config": config,
71
+ "voxel_size": voxel_size,
72
+ "picks_info": picks_info,
73
+ "membrane_info": membrane_info,
74
+ "organelle_info": organelle_info
75
+ },
76
+ "output": {
77
+ "save_user_id": save_user_id,
78
+ "save_session_id": save_session_id
79
+ },
80
+ "parameters": {
81
+ "distance_threshold": distance_threshold,
82
+ "runIDs": runIDs
83
+ }
84
+ }
85
+
86
+ # Print the parameters
87
+ print(f"\nParameters for Extraction of Membrane-Bound Picks:")
88
+ pprint.pprint(params_dict); print()
89
+
90
+ # Save parameters to YAML file
91
+ io.save_parameters_yaml(params_dict, output_path)
92
+
93
+
94
+ @click.command('membrane-extract')
95
+ # Output Arguments
96
+ @click.option('--save-session-id', type=str, required=True,
97
+ help="Session ID to save the new picks")
98
+ @click.option('--save-user-id', type=str, default=None,
99
+ help="User ID to save the new picks (defaults to picks user ID)")
100
+ # Parameters
101
+ @click.option('--n-procs', type=int, default=None,
102
+ help="Number of processes to use (defaults to CPU count)")
103
+ @click.option('--distance-threshold', type=float, default=10,
104
+ help="Distance threshold for membrane proximity")
105
+ # Input Arguments
106
+ @click.option('--runIDs', type=str, default=None,
107
+ callback=lambda ctx, param, value: parsers.parse_list(value) if value else None,
108
+ help="List of run IDs to process")
109
+ @click.option('--organelle-info', type=str, default=None,
110
+ callback=lambda ctx, param, value: parsers.parse_target(value) if value else None,
111
+ help='Query for the organelles segmentations (e.g., "name" or "name,user_id,session_id")')
112
+ @click.option('--membrane-info', type=str, default=None,
113
+ callback=lambda ctx, param, value: parsers.parse_target(value) if value else None,
114
+ help='Query for the membrane segmentation (e.g., "name" or "name,user_id,session_id")')
115
+ @click.option('--picks-info', type=str, required=True,
116
+ callback=lambda ctx, param, value: parsers.parse_target(value),
117
+ help='Query for the picks (e.g., "name" or "name,user_id,session_id")')
118
+ @click.option('-vs', '--voxel-size', type=float, default=10,
119
+ help="Voxel size")
120
+ @click.option('-c', '--config', type=click.Path(exists=True), required=True,
121
+ help="Path to the configuration file")
122
+ def cli(config, voxel_size, picks_info, membrane_info, organelle_info, runIDs,
123
+ distance_threshold, n_procs,
124
+ save_user_id, save_session_id):
125
+ """
126
+ Extract membrane-bound picks based on proximity to organelle or membrane segmentation.
127
+
128
+ This command isolates membrane-bound proteins from segmented volumes by finding
129
+ particles that are within a specified distance threshold of the membrane segmentation.
130
+ The resulting picks are saved as zarr arrays in your copick project, organized by
131
+ segmentation name, user ID, and session ID for easy tracking and comparison.
132
+
133
+ \b
134
+ Examples:
135
+ # Extract membrane-bound picks with default distance threshold
136
+ octopi membrane-extract -c config.json \\
137
+ --picks-info predictions,octopi,1 \\
138
+ --membrane-info membrane,octopi,1 \\
139
+ --organelle-info organelle,octopi,1 \\
140
+ --save-user-id octopi \\
141
+ --save-session-id 1
142
+ """
143
+
144
+ run_mb_extract(
145
+ config, voxel_size,
146
+ picks_info, membrane_info, organelle_info,
147
+ runIDs, distance_threshold, n_procs,
148
+ save_user_id, save_session_id
149
+ )
150
+
151
+
152
+ def run_mb_extract(
153
+ config, voxel_size, picks_info, membrane_info,
154
+ organelle_info, runIDs, distance_threshold, n_procs,
155
+ save_user_id, save_session_id):
156
+
157
+ # Default save_user_id to picks_info user_id if not specified
158
+ if save_user_id is None:
159
+ save_user_id = picks_info[1]
160
+
161
+ # Save parameters
162
+ output_yaml = f'membrane-extract_{save_user_id}_{save_session_id}.yaml'
163
+ save_parameters(
164
+ config=config,
165
+ voxel_size=voxel_size,
166
+ picks_info=picks_info,
167
+ membrane_info=membrane_info,
168
+ organelle_info=organelle_info,
169
+ save_user_id=save_user_id,
170
+ save_session_id=save_session_id,
171
+ distance_threshold=distance_threshold,
172
+ runIDs=runIDs,
173
+ output_path=output_yaml
174
+ )
175
+
176
+ extract_membrane_bound_picks(
177
+ config=config,
178
+ voxel_size=voxel_size,
179
+ distance_threshold=distance_threshold,
180
+ picks_info=picks_info,
181
+ membrane_info=membrane_info,
182
+ organelle_info=organelle_info,
183
+ save_user_id=save_user_id,
184
+ save_session_id=save_session_id,
185
+ runIDs=runIDs,
186
+ n_procs=n_procs,
187
+ )
188
+
189
+
190
+ if __name__ == "__main__":
191
+ cli()
@@ -0,0 +1,143 @@
1
+ from octopi.extract import midpoint_extract
2
+ from typing import List, Tuple, Optional
3
+ import argparse, pprint, copick
4
+ from octopi import utils
5
+ import multiprocess as mp
6
+ from tqdm import tqdm
7
+
8
+ def extract_midpoint(
9
+ config: str,
10
+ voxel_size: float,
11
+ picks_info: Tuple[str, str, str],
12
+ organelle_info: Tuple[str, str, str],
13
+ distance_min: float,
14
+ distance_max: float,
15
+ distance_threshold: float,
16
+ save_session_id: str,
17
+ runIDs: List[str],
18
+ n_procs: int = None
19
+ ):
20
+
21
+ # Load Copick Project for Writing
22
+ root = copick.from_file( config )
23
+
24
+ # Either Specify Input RunIDs or Run on All RunIDs
25
+ if runIDs: print('Extracting Midpoints on the Following RunIDs: ', runIDs)
26
+ run_ids = runIDs if runIDs else [run.name for run in root.runs]
27
+ n_run_ids = len(run_ids)
28
+
29
+ # Determine the number of processes to use
30
+ if n_procs is None:
31
+ n_procs = min(mp.cpu_count(), n_run_ids)
32
+ print(f"Using {n_procs} processes to parallelize across {n_run_ids} run IDs.")
33
+
34
+ # Initialize tqdm progress bar
35
+ with tqdm(total=n_run_ids, desc="Mid-Point SuperComplex Extraction", unit="run") as pbar:
36
+ for _iz in range(0, n_run_ids, n_procs):
37
+
38
+ start_idx = _iz
39
+ end_idx = min(_iz + n_procs, n_run_ids) # Ensure end_idx does not exceed n_run_ids
40
+ print(f"\nProcessing runIDs from {start_idx} -> {end_idx } (out of {n_run_ids})")
41
+
42
+ processes = []
43
+ for _in in range(n_procs):
44
+ _iz_this = _iz + _in
45
+ if _iz_this >= n_run_ids:
46
+ break
47
+ run_id = run_ids[_iz_this]
48
+ run = root.get_run(run_id)
49
+ p = mp.Process(
50
+ target=midpoint_extract.process_midpoint_extract,
51
+ args=(run,
52
+ voxel_size,
53
+ picks_info,
54
+ organelle_info,
55
+ distance_min,
56
+ distance_max,
57
+ distance_threshold,
58
+ save_session_id)
59
+ )
60
+ processes.append(p)
61
+
62
+ for p in processes:
63
+ p.start()
64
+
65
+ for p in processes:
66
+ p.join()
67
+
68
+ for p in processes:
69
+ p.close()
70
+
71
+ # Update tqdm progress bar
72
+ pbar.update(len(processes))
73
+
74
+ print('Extraction of Midpoints Complete!')
75
+
76
+ def cli():
77
+ parser = argparse.ArgumentParser(
78
+ description='Extract membrane-bound picks based on proximity to segmentation.',
79
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter
80
+ )
81
+ parser.add_argument('--config', type=str, required=True, help='Path to the configuration file.')
82
+ parser.add_argument('--voxel-size', type=float, required=False, default=10, help='Segmentation Voxel size.')
83
+ parser.add_argument('--picks-info', type=utils.parse_target, required=True, help='Query for the picks (e.g., "name" or "name,user_id,session_id".).')
84
+ parser.add_argument('--organelle-info', type=utils.parse_target, required=False, help='Query for the organelles segmentations (e.g., "name" or "name,user_id,session_id".).')
85
+ parser.add_argument('--distance-min', type=float, required=False, default=10, help='Minimum distance for valid nearest neighbors.')
86
+ parser.add_argument('--distance-max', type=float, required=False, default=70, help='Maximum distance for valid nearest neighbors.')
87
+ parser.add_argument('--distance-threshold', type=float, required=False, default=25, help='Distance threshold for picks to associated organelles.')
88
+ parser.add_argument('--save-session-id', type=str, required=False, default=None, help='(Optional)SessionID to save the new picks. If none provided, will use the sessionID from the picks.')
89
+ parser.add_argument('--runIDs', type=utils.parse_list, required=False, help='(Optional) List of run IDs to process.')
90
+ parser.add_argument('--n-procs', type=int, required=False, default=None, help='Number of processes to use. In none providd, will use the total number of CPUs available.')
91
+
92
+ args = parser.parse_args()
93
+
94
+ # Increment session ID for the second class
95
+ if args.save_session_id is None:
96
+ args.save_session_id = args.picks_info[2]
97
+ args.save_user_id = args.picks_info[1]
98
+
99
+ # Save JSON with Parameters
100
+ output_yaml = f'midpoint-extract_{args.picks_info[1]}_{args.save_session_id}.yaml'
101
+ save_parameters(args, output_yaml)
102
+
103
+
104
+ extract_midpoint(
105
+ config=args.config,
106
+ voxel_size=args.voxel_size,
107
+ picks_info=args.picks_info,
108
+ organelle_info=args.organelle_info,
109
+ distance_min=args.distance_min,
110
+ distance_max=args.distance_max,
111
+ distance_threshold=args.distance_threshold,
112
+ save_session_id=args.save_session_id,
113
+ runIDs=args.runIDs,
114
+ n_procs=args.n_procs,
115
+ )
116
+
117
+ def save_parameters(args: argparse.Namespace,
118
+ output_path: str):
119
+
120
+ params_dict = {
121
+ "input": {
122
+ k: getattr(args, k) for k in [
123
+ "config", "voxel_size", "picks_info",
124
+ "organelle_info"
125
+ ]
126
+ },
127
+ "output": {
128
+ k: getattr(args, k) for k in ["save_user_id", "save_session_id"]
129
+ },
130
+ "parameters": {
131
+ k: getattr(args, k) for k in ["distance_min", "distance_max", "distance_threshold", "runIDs"]
132
+ }
133
+ }
134
+
135
+ # Print the parameters
136
+ print(f"\nParameters for Extraction of Membrane-Bound Picks:")
137
+ pprint.pprint(params_dict); print()
138
+
139
+ # Save parameters to YAML file
140
+ utils.save_parameters_yaml(params_dict, output_path)
141
+
142
+ if __name__ == "__main__":
143
+ cli()
@@ -0,0 +1,176 @@
1
+ from octopi.utils import parsers
2
+ from typing import List, Tuple
3
+ import rich_click as click
4
+
5
+ def pick_particles(
6
+ copick_config_path: str,
7
+ method: str,
8
+ seg_info: Tuple[str, str, str],
9
+ voxel_size: float,
10
+ pick_session_id: str,
11
+ pick_user_id: str,
12
+ radius_min_scale: float,
13
+ radius_max_scale: float,
14
+ filter_size: float,
15
+ pick_objects: List[str],
16
+ runIDs: List[str],
17
+ n_procs: int,
18
+ ):
19
+ from octopi.workflows import localize
20
+
21
+ # Run 3D Localization
22
+ localize(
23
+ copick_config_path, voxel_size, seg_info, pick_user_id, pick_session_id, n_procs,
24
+ method, filter_size, radius_min_scale, radius_max_scale,
25
+ run_ids = runIDs, pick_objects = pick_objects
26
+ )
27
+
28
+
29
+ def save_parameters(seg_info: Tuple[str, str, str],
30
+ config: str,
31
+ voxel_size: float,
32
+ pick_session_id: str,
33
+ pick_user_id: str,
34
+ method: str,
35
+ radius_min_scale: float,
36
+ radius_max_scale: float,
37
+ filter_size: float,
38
+ output_path: str):
39
+
40
+ import octopi.utils.io as io
41
+ import pprint
42
+
43
+ # Organize parameters into categories
44
+ params = {
45
+ "input": {
46
+ "config": config,
47
+ "seg_name": seg_info[0],
48
+ "seg_user_id": seg_info[1],
49
+ "seg_session_id": seg_info[2],
50
+ "voxel_size": voxel_size
51
+ },
52
+ "output": {
53
+ "pick_session_id": pick_session_id,
54
+ "pick_user_id": pick_user_id
55
+ },
56
+ "parameters": {
57
+ "method": method,
58
+ "radius_min_scale": radius_min_scale,
59
+ "radius_max_scale": radius_max_scale,
60
+ "filter_size": filter_size,
61
+ }
62
+ }
63
+
64
+ # Print the parameters
65
+ print(f"\nParameters for Localization:")
66
+ pprint.pprint(params); print()
67
+
68
+ # Save to YAML file
69
+ io.save_parameters_yaml(params, output_path)
70
+
71
+
72
+ @click.command('localize')
73
+ # Output Arguments
74
+ @click.option('-pui', '--pick-user-id', type=str, default='octopi',
75
+ help="User ID for the particle picks")
76
+ @click.option('-psid', '--pick-session-id', type=str, default='1',
77
+ help="Session ID for the particle picks")
78
+ # Localize Arguments
79
+ @click.option('-np', '--n-procs', type=int, default=8,
80
+ help="Number of CPU processes to parallelize runs across. Defaults to the max number of cores available or available runs")
81
+ @click.option('-obj', '--pick-objects', type=str, default=None,
82
+ callback=lambda ctx, param, value: parsers.parse_list(value) if value else None,
83
+ help="Specific Objects to Find Picks for")
84
+ @click.option('-fs', '--filter-size', type=int, default=10,
85
+ help="Filter size for localization")
86
+ @click.option('-rmax','--radius-max-scale', type=float, default=1.0,
87
+ help="Maximum radius scale for particles")
88
+ @click.option('-rmin', '--radius-min-scale', type=float, default=0.5,
89
+ help="Minimum radius scale for particles")
90
+ # Input Arguments
91
+ @click.option('--runIDs', type=str, default=None,
92
+ callback=lambda ctx, param, value: parsers.parse_list(value) if value else None,
93
+ help="List of runIDs to run inference on, e.g., run1,run2,run3 or [run1,run2,run3]")
94
+ @click.option('-vs', '--voxel-size', type=float, default=10,
95
+ help="Voxel size for localization")
96
+ @click.option('-sinfo', '--seg-info', type=str, default='predict,octopi,1',
97
+ callback=lambda ctx, param, value: parsers.parse_target(value),
98
+ help='Query for the organelles segmentations (e.g., "name" or "name,user_id,session_id")')
99
+ @click.option('-m', '--method', type=click.Choice(['watershed', 'com'], case_sensitive=False),
100
+ default='watershed',
101
+ help="Localization method to use")
102
+ @click.option('-c', '--config', type=click.Path(exists=True), required=True,
103
+ help="Path to the CoPick configuration file")
104
+ def cli(config, method, seg_info, voxel_size, runids,
105
+ radius_min_scale, radius_max_scale, filter_size, pick_objects, n_procs,
106
+ pick_session_id, pick_user_id):
107
+ """
108
+ Convert Segmentation Masks to 3D Particle Coordinates.
109
+
110
+ This command converts segmentation masks into 3D particle coordinates using size-based filtering.
111
+ It supports two localization methods: watershed and center of mass. The resulting particle coordinates
112
+ in your copick project, organized by segmentation name, user ID, and session ID for easy tracking and comparison.
113
+
114
+ \b
115
+ Examples:
116
+ # Localize particles with default settings
117
+ octopi localize -c config.json --seg-info predict,octopi,1
118
+ """
119
+
120
+ run_localize(config, method, seg_info, voxel_size, runids,
121
+ radius_min_scale, radius_max_scale, filter_size, pick_objects, n_procs,
122
+ pick_session_id, pick_user_id)
123
+
124
+
125
+ def run_localize(config, method, seg_info, voxel_size, runids,
126
+ radius_min_scale, radius_max_scale, filter_size, pick_objects, n_procs,
127
+ pick_session_id, pick_user_id):
128
+ """
129
+ Run the localize command.
130
+ """
131
+ import octopi.utils.io as io
132
+ import multiprocess as mp
133
+ import copick, os
134
+
135
+ # Save JSON with Parameters
136
+ root = copick.from_file(config)
137
+ overlay_root = io.remove_prefix(root.config.overlay_root)
138
+ basepath = os.path.join(overlay_root, 'logs')
139
+ os.makedirs(basepath, exist_ok=True)
140
+ output_path = os.path.join(basepath, f'localize-{pick_user_id}_{pick_session_id}.yaml')
141
+
142
+ save_parameters(
143
+ seg_info=seg_info,
144
+ config=config,
145
+ voxel_size=voxel_size,
146
+ pick_session_id=pick_session_id,
147
+ pick_user_id=pick_user_id,
148
+ method=method,
149
+ radius_min_scale=radius_min_scale,
150
+ radius_max_scale=radius_max_scale,
151
+ filter_size=filter_size,
152
+ output_path=output_path
153
+ )
154
+
155
+ # Set multiprocessing start method
156
+ mp.set_start_method("spawn")
157
+
158
+ pick_particles(
159
+ copick_config_path=config,
160
+ method=method,
161
+ seg_info=seg_info,
162
+ voxel_size=voxel_size,
163
+ pick_session_id=pick_session_id,
164
+ pick_user_id=pick_user_id,
165
+ radius_min_scale=radius_min_scale,
166
+ radius_max_scale=radius_max_scale,
167
+ filter_size=filter_size,
168
+ runIDs=runids,
169
+ pick_objects=pick_objects,
170
+ n_procs=n_procs,
171
+ )
172
+
173
+
174
+ if __name__ == "__main__":
175
+ cli()
176
+