chipstream 0.6.0a5__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.
chipstream/__init__.py ADDED
@@ -0,0 +1,20 @@
1
+ # flake8: noqa: F401
2
+ import os
3
+
4
+ from ._version import __version__, __version_tuple__
5
+
6
+ # Disable multithreading for all major tools that could somehow
7
+ # parallelize our code. We don't want that, because we have linear
8
+ # code and rely on multiprocessing for parallelization. This has
9
+ # to be done before importing numpy or any other library affected.
10
+ # If someone uses chipstream in a script, they either have to import
11
+ # chipstream first, or set the environment variables manually.
12
+ # We use `setdefault` to honor any other variables set by the
13
+ # user for whatever reason.
14
+ os.environ.setdefault("MKL_NUM_THREADS", "1")
15
+ os.environ.setdefault("NUMBA_NUM_THREADS", "1")
16
+ os.environ.setdefault("NUMEXPR_NUM_THREADS", "1")
17
+ os.environ.setdefault("NUMPY_NUM_THREADS", "1")
18
+ os.environ.setdefault("OPENBLAS_NUM_THREADS", "1")
19
+ os.environ.setdefault("OMP_NUM_THREADS", "1")
20
+ os.environ.setdefault("VECLIB_MAXIMUM_THREADS", "1")
chipstream/_version.py ADDED
@@ -0,0 +1,16 @@
1
+ # file generated by setuptools_scm
2
+ # don't change, don't track in version control
3
+ TYPE_CHECKING = False
4
+ if TYPE_CHECKING:
5
+ from typing import Tuple, Union
6
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
7
+ else:
8
+ VERSION_TUPLE = object
9
+
10
+ version: str
11
+ __version__: str
12
+ __version_tuple__: VERSION_TUPLE
13
+ version_tuple: VERSION_TUPLE
14
+
15
+ __version__ = version = '0.6.0a5'
16
+ __version_tuple__ = version_tuple = (0, 6, 0)
@@ -0,0 +1 @@
1
+ from ._main import main # noqa: F401
@@ -0,0 +1,4 @@
1
+ from _main import main
2
+
3
+ if __name__ == '__main__':
4
+ main()
@@ -0,0 +1,10 @@
1
+ try:
2
+ import click
3
+ except ImportError:
4
+ click = None
5
+
6
+ if click is None:
7
+ def main(*args, **kwargs):
8
+ print("Please install the 'click' Python package to access the CLI!")
9
+ else:
10
+ from .cli_main import chipstream_cli as main # noqa: F401
@@ -0,0 +1,95 @@
1
+ import logging
2
+
3
+ from dcnum.feat.feat_background import get_available_background_methods
4
+ from dcnum.feat import Gate, QueueEventExtractor # noqa: F401
5
+ from dcnum.meta.ppid import get_class_method_info
6
+ from dcnum.segm import get_available_segmenters
7
+
8
+
9
+ seg_methods = get_available_segmenters()
10
+ bg_methods = get_available_background_methods()
11
+
12
+
13
+ class PrettyFormatter(logging.Formatter):
14
+ grey = "\x1b[38;20m"
15
+ yellow = "\x1b[33;20m"
16
+ red = "\x1b[31;20m"
17
+ bold_red = "\x1b[31;1m"
18
+ reset = "\x1b[0m"
19
+ format = "%(levelname)s %(name)s: %(message)s"
20
+
21
+ FORMATS = {
22
+ logging.DEBUG: grey + format + reset,
23
+ logging.INFO: grey + format + reset,
24
+ logging.WARNING: yellow + format + reset,
25
+ logging.ERROR: red + format + reset,
26
+ logging.CRITICAL: bold_red + format + reset
27
+ }
28
+
29
+ def format(self, record):
30
+ log_fmt = self.FORMATS.get(record.levelno)
31
+ formatter = logging.Formatter(log_fmt)
32
+ return formatter.format(record)
33
+
34
+
35
+ def format_help_class_method_info(info_list):
36
+ """Format a list of `info` dictionaries from `get_class_method_info`"""
37
+ choices = ["\b"] # "\b" means paragraph is not formatted in help string
38
+ for info in info_list:
39
+ code = info["code"]
40
+ title = info["title"]
41
+ choices.append(f" - '{code}': {title}")
42
+ for mname in info.get("defaults", []):
43
+ # We are flattening through all methods, because the CLI
44
+ # makes them transparently available. There must not be
45
+ # any name clashes, the developer is responsible for
46
+ # that.
47
+ kwds = info["defaults"][mname]
48
+ if kwds:
49
+ for kw in kwds:
50
+ choices.append(f" - {kw}={kwds[kw]}")
51
+ return "\n".join(choices)
52
+
53
+
54
+ def get_choices_help_string(class_dict, static_kw_methods=None):
55
+ """Return a chipstream help string for this class
56
+
57
+ Parameters
58
+ ----------
59
+ class_dict: dictionary of objects
60
+ The dictionary holds the classes to inspect
61
+ static_kw_methods: list of callable
62
+ The methods to inspect; all kwargs-only keyword arguments
63
+ are extracted.
64
+ """
65
+ info_list = []
66
+ for key in class_dict:
67
+ class_obj = class_dict[key]
68
+ info_list.append(get_class_method_info(class_obj, static_kw_methods))
69
+ return format_help_class_method_info(info_list)
70
+
71
+
72
+ def get_choices_help_string_segmenter(class_dict):
73
+ """Return a chipstream help string for a segmenter class
74
+
75
+ This is a custom function, analogous to get_choices_help_string,
76
+ but for segmenters, which might or might not allow mask
77
+ post-processing (depending on whether `mask_postprocessing` is set).
78
+
79
+ Parameters
80
+ ----------
81
+ class_dict: dictionary of objects
82
+ The dictionary holds the classes to inspect
83
+ """
84
+ info_list = []
85
+ for key in class_dict:
86
+ class_obj = class_dict[key]
87
+ static_kw_methods = ["segment_algorithm"]
88
+ static_kw_defaults = {}
89
+ if class_obj.mask_postprocessing:
90
+ static_kw_methods.append("process_mask")
91
+ static_kw_defaults["process_mask"] = class_obj.mask_default_kwargs
92
+ info_list.append(get_class_method_info(class_obj,
93
+ static_kw_methods,
94
+ static_kw_defaults))
95
+ return format_help_class_method_info(info_list)
@@ -0,0 +1,228 @@
1
+ import logging
2
+ import multiprocessing as mp
3
+ import pathlib
4
+ import sys
5
+
6
+ import click
7
+
8
+ from .._version import version
9
+
10
+ from . import cli_common as cm
11
+ from .cli_proc import process_dataset
12
+
13
+
14
+ @click.command(name="chipstream-cli",
15
+ help=f"""
16
+ Segmentation and feature extraction for deformability cytometry data.
17
+
18
+ Read the image data from an input file, perform segmentation, feature
19
+ extraction, and gating.
20
+ If ``PATH_OUT`` is not specified, then the data are written to a new file
21
+ with the suffix "_dcn.rtdc". In recursive mode (``-r``), a target
22
+ directory may be specified.
23
+
24
+ You can specify which segmenter and feature extractor should be used.
25
+ You can also specify additional keyword arguments for
26
+ segmentation (``-ks``), feature extraction (``-kf``), and gating (``-kg``).
27
+
28
+ Available segmenters with keyword arguments:
29
+
30
+ {cm.get_choices_help_string_segmenter(cm.seg_methods)}
31
+
32
+ Available feature extractors:
33
+
34
+ {cm.get_choices_help_string({"legacy": cm.QueueEventExtractor},
35
+ ["get_events_from_masks"])}
36
+
37
+ Available gating options:
38
+
39
+ {cm.get_choices_help_string({"": cm.Gate}, ["__init__"])}
40
+
41
+
42
+ Examples:
43
+
44
+ Process an .rtdc measurement, without computing the Haralick texture features::
45
+
46
+ chipstream-cli process -kf haralick=False M001_data.rtdc
47
+
48
+ Recursively analyze a directory containing .rtdc files::
49
+
50
+ chipstream-cli process --recursive directory_name
51
+
52
+ """)
53
+ @click.argument("path_in",
54
+ type=click.Path(exists=True,
55
+ dir_okay=True,
56
+ resolve_path=True,
57
+ path_type=pathlib.Path))
58
+ @click.argument("path_out",
59
+ required=False,
60
+ type=click.Path(dir_okay=True,
61
+ writable=True,
62
+ resolve_path=True,
63
+ path_type=pathlib.Path),
64
+ )
65
+ @click.option("-b", "--background-method",
66
+ type=click.Choice(sorted(cm.bg_methods.keys()),
67
+ case_sensitive=False),
68
+ default="sparsemed",
69
+ help="Background computation method to use.")
70
+ @click.option("-kb", "background_kwargs",
71
+ multiple=True,
72
+ help="Optional ``KEY=VALUE`` argument for the specified "
73
+ "background method",
74
+ metavar="KEY=VALUE",
75
+ )
76
+ @click.option("-s", "--segmentation-method",
77
+ type=click.Choice(sorted(cm.seg_methods.keys()),
78
+ case_sensitive=False),
79
+ default="thresh",
80
+ help="Segmentation method to use.")
81
+ @click.option("-ks", "segmentation_kwargs",
82
+ multiple=True,
83
+ help="Optional ``KEY=VALUE`` argument for the specified "
84
+ "segmenter.",
85
+ metavar="KEY=VALUE",
86
+ )
87
+ @click.option("-kf", "feature_kwargs",
88
+ multiple=True,
89
+ help="Optional ``KEY=VALUE`` argument for the specified "
90
+ "feature extractor.",
91
+ metavar="KEY=VALUE",
92
+ )
93
+ @click.option("-kg", "gate_kwargs",
94
+ multiple=True,
95
+ help="Optional ``KEY=VALUE`` argument for event gating.",
96
+ metavar="KEY=VALUE",
97
+ )
98
+ @click.option("-p", "--pixel-size", type=float, default=0,
99
+ help="Set/override the pixel size for feature "
100
+ "extraction [µm].")
101
+ @click.option("--limit-events", type=str, default="0",
102
+ help="Limit events of events to analyze. This can be either "
103
+ "a number (e.g. '5000') or a range (e.g. '5000-7000'). "
104
+ "You can also specify a step size (e.g. '5000-7000-2' for "
105
+ "every second event). The convention follows Python slices "
106
+ "with 'n' substituting for 'None'.")
107
+ @click.option("--drain-basins", type=str, is_flag=True,
108
+ help="Write all basin features from input to output file. This "
109
+ "option trades computation time and small file size for "
110
+ "an output file that contains all available features.")
111
+ @click.option("-r", "--recursive", is_flag=True,
112
+ help="Recurse into subdirectories.")
113
+ @click.option("--num-cpus",
114
+ type=click.IntRange(min=1, max=mp.cpu_count(), clamp=True),
115
+ help="Number of processes to create."
116
+ )
117
+ @click.option("--dry-run", is_flag=True,
118
+ help="Only print the pipeline identifiers and exit.")
119
+ @click.option("--verbose", is_flag=True,
120
+ help="Yield a more verbose output.")
121
+ @click.option("--debug", is_flag=True,
122
+ help="Run chipstream in debugging mode. This disables "
123
+ "multiprocessing and yields a more verbose output.")
124
+ @click.version_option(version)
125
+ def chipstream_cli(
126
+ path_in,
127
+ path_out=None,
128
+ background_method="sparsemed",
129
+ background_kwargs=None,
130
+ segmentation_method="thresh",
131
+ segmentation_kwargs=None,
132
+ feature_kwargs=None,
133
+ gate_kwargs=None,
134
+ pixel_size=0,
135
+ limit_events="0",
136
+ drain_basins=False,
137
+ recursive=False,
138
+ num_cpus=None,
139
+ dry_run=False,
140
+ verbose=False,
141
+ debug=False,
142
+ ):
143
+
144
+ if debug:
145
+ click.secho("Running in debug mode (this will be slow)",
146
+ fg="yellow")
147
+ verbose = True
148
+
149
+ # Parse limit_frames to get the HDF5Data index_mapping
150
+ if limit_events == "0":
151
+ index_mapping = None
152
+ elif limit_events.count("-"):
153
+ vals = limit_events.split("-")
154
+ assert len(vals) in [2, 3], "slice definition must have length 2 or 3"
155
+ start = None if vals[0] == "n" else int(vals[0])
156
+ stop = None if vals[1] == "n" else int(vals[1])
157
+ if len(vals) == 3:
158
+ step = None if vals[2] == "n" else int(vals[2])
159
+ else:
160
+ step = None
161
+ index_mapping = slice(start, stop, step)
162
+ else:
163
+ index_mapping = int(limit_events)
164
+
165
+ # Tell the root logger to pretty-print logs
166
+ root_logger = logging.getLogger()
167
+ handler = logging.StreamHandler()
168
+ handler.setFormatter(cm.PrettyFormatter())
169
+ root_logger.addHandler(handler)
170
+ handler.setLevel(logging.DEBUG if verbose else logging.WARNING)
171
+
172
+ mp.freeze_support()
173
+
174
+ process_kwargs = dict(
175
+ background_method=background_method,
176
+ background_kwargs=background_kwargs,
177
+ segmentation_method=segmentation_method,
178
+ segmentation_kwargs=segmentation_kwargs,
179
+ feature_kwargs=feature_kwargs,
180
+ gate_kwargs=gate_kwargs,
181
+ pixel_size=pixel_size,
182
+ index_mapping=index_mapping,
183
+ # Below this line are arguments that do not define the pipeline ID
184
+ basin_strategy="drain" if drain_basins else "tap",
185
+ num_cpus=num_cpus or mp.cpu_count(),
186
+ dry_run=dry_run,
187
+ debug=debug,
188
+ )
189
+
190
+ if recursive:
191
+ failed = 0 # keeps track of files that failed to process
192
+ for pi in sorted(path_in.rglob("*.rtdc")):
193
+ if pi.name.endswith("_dcn.rtdc"):
194
+ continue
195
+ if path_out is not None:
196
+ poi = path_out / pi.relative_to(path_in)
197
+ po = poi.with_name(poi.stem + "_dcn.rtdc")
198
+ po.parent.mkdir(parents=True, exist_ok=True)
199
+ else:
200
+ po = None
201
+ click.secho(f"\nProcessing {pi}")
202
+ failed += process_dataset(path_in=pi,
203
+ path_out=po,
204
+ **process_kwargs)
205
+ if dry_run:
206
+ click.secho("Stopping dry run after one iteration")
207
+ break
208
+ if failed:
209
+ click.secho(f"Could not process {failed} files", fg="red")
210
+ exit_code = bool(failed)
211
+ else:
212
+ if path_in.is_dir():
213
+ click.secho(f"PATH_IN must be a file, but '{path_in}' "
214
+ f"is a directory. Did you forget to specify "
215
+ f"the `--recursive` flag?", fg="red")
216
+ exit_code = 1
217
+ elif path_out and path_out.is_dir():
218
+ click.secho(f"PATH_OUT must be a path to a file, but "
219
+ f"'{path_out}' is a directory")
220
+ exit_code = 1
221
+ else:
222
+ # everything ok
223
+ exit_code = process_dataset(path_in=path_in,
224
+ path_out=path_out,
225
+ **process_kwargs)
226
+ if exit_code:
227
+ click.secho("Encountered problems during processing", fg="red")
228
+ sys.exit(exit_code)
@@ -0,0 +1,141 @@
1
+ import pathlib
2
+ import time
3
+ from typing import List, Literal
4
+
5
+ import click
6
+ import dcnum.logic
7
+ from dcnum.meta import ppid
8
+ import dcnum.read
9
+ import dcnum.segm
10
+
11
+
12
+ from . import cli_common as cm
13
+ from .cli_valid import (
14
+ validate_background_kwargs, validate_feature_kwargs, validate_gate_kwargs,
15
+ validate_pixel_size, validate_segmentation_kwargs
16
+ )
17
+
18
+
19
+ def process_dataset(
20
+ path_in: pathlib.Path,
21
+ path_out: pathlib.Path,
22
+ background_method: str,
23
+ background_kwargs: List[str],
24
+ segmentation_method: str,
25
+ segmentation_kwargs: List[str],
26
+ feature_kwargs: List[str],
27
+ gate_kwargs: List[str],
28
+ pixel_size: float,
29
+ index_mapping: int | slice | None,
30
+ # Below this line are arguments that do not affect the pipeline ID
31
+ basin_strategy: Literal["drain", "tap"],
32
+ num_cpus: int,
33
+ dry_run: bool,
34
+ debug: bool,
35
+ ):
36
+ # Make sure the pixel size makes sense
37
+ if pixel_size == 0:
38
+ pixel_size = validate_pixel_size(data_path=path_in)
39
+
40
+ if path_out is None:
41
+ path_out = path_in.with_name(path_in.stem + "_dcn.rtdc")
42
+ path_out.parent.mkdir(parents=True, exist_ok=True)
43
+
44
+ # data keyword arguments
45
+ data_kwargs = {"pixel_size": pixel_size,
46
+ "index_mapping": index_mapping}
47
+ with dcnum.read.HDF5Data(path_in, **data_kwargs) as data:
48
+ dat_id = data.get_ppid()
49
+ click.echo(f"Data ID:\t{dat_id}")
50
+
51
+ # background keyword arguments
52
+ bg_kwargs = validate_background_kwargs(background_method,
53
+ background_kwargs)
54
+ bg_cls = cm.bg_methods[background_method]
55
+ bg_id = bg_cls.get_ppid_from_ppkw(bg_kwargs)
56
+ click.echo(f"Background ID:\t{bg_id}")
57
+
58
+ # segmenter keyword arguments
59
+ seg_kwargs = validate_segmentation_kwargs(segmentation_method,
60
+ segmentation_kwargs)
61
+ seg_cls = cm.seg_methods[segmentation_method]
62
+ seg_id = seg_cls.get_ppid_from_ppkw(seg_kwargs)
63
+ click.echo(f"Segmenter ID:\t{seg_id}")
64
+
65
+ # feature keyword arguments
66
+ feat_kwargs = validate_feature_kwargs(feature_kwargs)
67
+ feat_cls = cm.QueueEventExtractor
68
+ feat_id = feat_cls.get_ppid_from_ppkw(feat_kwargs)
69
+ click.echo(f"Feature ID:\t{feat_id}")
70
+
71
+ # gate keyword arguments
72
+ gate_cls = cm.Gate
73
+ gate_kwargs = validate_gate_kwargs(gate_kwargs)
74
+ gate_id = gate_cls.get_ppid_from_ppkw(gate_kwargs)
75
+ click.echo(f"Gate ID:\t{gate_id}")
76
+
77
+ # compute pipeline hash
78
+ pph = ppid.compute_pipeline_hash(
79
+ gen_id=ppid.DCNUM_PPID_GENERATION,
80
+ dat_id=dat_id,
81
+ bg_id=bg_id,
82
+ seg_id=seg_id,
83
+ feat_id=feat_id,
84
+ gate_id=gate_id)
85
+ click.secho(f"Pipeline hash:\t{pph}")
86
+
87
+ if dry_run:
88
+ click.echo("Dry run complete")
89
+ return 0
90
+
91
+ job = dcnum.logic.DCNumPipelineJob(
92
+ path_in=path_in,
93
+ path_out=path_out,
94
+ data_code="hdf",
95
+ data_kwargs=data_kwargs,
96
+ background_code=bg_cls.get_ppid_code(),
97
+ background_kwargs=bg_kwargs,
98
+ segmenter_code=seg_cls.get_ppid_code(),
99
+ segmenter_kwargs=seg_kwargs,
100
+ feature_code=feat_cls.get_ppid_code(),
101
+ feature_kwargs=feat_kwargs,
102
+ gate_code=gate_cls.get_ppid_code(),
103
+ gate_kwargs=gate_kwargs,
104
+ basin_strategy=basin_strategy,
105
+ num_procs=num_cpus,
106
+ debug=debug,
107
+ )
108
+
109
+ try:
110
+ job.validate()
111
+ except dcnum.segm.SegmenterNotApplicableError as e:
112
+ click.secho(f"Segmenter '{segmentation_method}' cannot be applied "
113
+ f"to '{path_in}': {', '.join(e.reasons_list)}",
114
+ fg="red")
115
+ return 1
116
+
117
+ runner = dcnum.logic.DCNumJobRunner(job)
118
+ runner.start()
119
+ strlen = 0
120
+ prev_str = ""
121
+ while True:
122
+ status = runner.get_status()
123
+ progress = status["progress"]
124
+ state = status["state"]
125
+ print_str = f"Processing {progress:.0%} ({state})"
126
+ if print_str != prev_str: # don't clutter stdout
127
+ strlen = max(strlen, len(print_str))
128
+ print(print_str.ljust(strlen), end="\r", flush=True)
129
+ prev_str = print_str
130
+ if status["state"] in ["done", "error"]:
131
+ break
132
+ time.sleep(.3) # don't use 100% CPU
133
+ print("") # new line
134
+
135
+ if status["state"] == "error":
136
+ click.secho(runner.error_tb, fg="red")
137
+ runner.join(delete_temporary_files=False)
138
+ return 1 # "exit code" > 0 means error
139
+ else:
140
+ runner.join(delete_temporary_files=True)
141
+ return 0
@@ -0,0 +1,127 @@
1
+ import inspect
2
+ import warnings
3
+
4
+ from dcnum.meta import ppid
5
+ import dcnum.read
6
+ import h5py
7
+
8
+ from . import cli_common as cm
9
+
10
+
11
+ def validate_background_kwargs(bg_method, args):
12
+ """Parse background keyword arguments"""
13
+ # Get list of valid keyword arguments
14
+ bg_cls = cm.bg_methods[bg_method]
15
+ spec = inspect.getfullargspec(bg_cls.check_user_kwargs)
16
+ valid_kw = spec.kwonlyargs
17
+ annot = spec.annotations
18
+ # Convert the input args to key-value pairs
19
+ kwargs = {}
20
+ for key, value in [a.split("=") for a in args]:
21
+ if key not in valid_kw:
22
+ raise ValueError(f"Invalid keyword '{key}' for {bg_method}. "
23
+ + f"Allowed keywords are {valid_kw}!")
24
+ # Convert to correct dtype (default to string)
25
+ kwargs[key] = ppid.convert_to_dtype(value, annot[key])
26
+ return kwargs
27
+
28
+
29
+ def validate_feature_kwargs(args):
30
+ # Get list of valid keyword arguments
31
+ feat_cls = cm.QueueEventExtractor
32
+ feat_code = feat_cls.get_ppid_code()
33
+ # extract_approach
34
+ spec_appr = inspect.getfullargspec(feat_cls.get_events_from_masks)
35
+ valid_kw_appr = spec_appr.kwonlyargs
36
+ annot_appr = spec_appr.annotations
37
+ # Convert the input args to key-value pairs
38
+ kwargs = {}
39
+ for key, value in [a.split("=") for a in args]:
40
+ if key in valid_kw_appr:
41
+ kwargs[key] = ppid.convert_to_dtype(value, annot_appr[key])
42
+ else:
43
+ raise ValueError(
44
+ f"Invalid keyword '{key}' for '{feat_code}'. "
45
+ f"Allowed keywords are {valid_kw_appr}!")
46
+ return kwargs
47
+
48
+
49
+ def validate_gate_kwargs(args):
50
+ spec = inspect.getfullargspec(cm.Gate.__init__)
51
+ valid_kw_appr = spec.kwonlyargs
52
+ annot_appr = spec.annotations
53
+ # Convert the input args to key-value pairs
54
+ kwargs = {}
55
+ for key, value in [a.split("=") for a in args]:
56
+ if key in valid_kw_appr:
57
+ kwargs[key] = ppid.convert_to_dtype(value, annot_appr[key])
58
+ else:
59
+ raise ValueError(
60
+ f"Invalid keyword '{key}' for gating. "
61
+ f"Allowed keywords are {valid_kw_appr} (and box filters)!")
62
+ return kwargs
63
+
64
+
65
+ def validate_pixel_size(data_path):
66
+ with h5py.File(data_path) as h5:
67
+ did = h5.attrs.get("setup:identifier", "EMPTY")
68
+ pixel_size = h5.attrs.get("imaging:pixel size", 0)
69
+ if (did.startswith("RC-")
70
+ and (pixel_size < 0.255 or pixel_size > 0.275)):
71
+ hd = dcnum.read.HDF5Data(h5, pixel_size=0.260) # placeholder
72
+ warnings.warn(
73
+ f"Correcting for invalid pixel size in '{hd.path}'!")
74
+ # Set default pixel size for Rivercyte devices
75
+ # Check the logs for the device name used.
76
+ logdat = hd.logs.get("cytoshot-acquisition", [])
77
+ for line in logdat:
78
+ line = line.strip().lower()
79
+ if line.startswith("device name:"):
80
+ dev_name = line.split(":")[1].strip()
81
+ break
82
+ else:
83
+ # fall-back to old camera
84
+ dev_name = "naiad 1.0"
85
+ if dev_name == "naiad 1.0":
86
+ # Naiad v1.0 camera VLXT06MI
87
+ pixel_size = 0.2645
88
+ elif dev_name == "naiad 1.1":
89
+ # Naiad v1.1 camera VCXU213M
90
+ pixel_size = 0.2675
91
+ else:
92
+ warnings.warn(f"Unknown device name: '{dev_name}'; "
93
+ f"Not changing pixel size {pixel_size}")
94
+ return pixel_size
95
+
96
+
97
+ def validate_segmentation_kwargs(seg_method, args):
98
+ """Parse segmenter keyword arguments"""
99
+ # Get list of valid keyword arguments
100
+ seg_cls = cm.seg_methods[seg_method]
101
+ # segment_algorithm
102
+ spec_appr = inspect.getfullargspec(seg_cls.segment_algorithm)
103
+ valid_kw_appr = spec_appr.kwonlyargs
104
+ annot_appr = spec_appr.annotations
105
+ # process_mask
106
+ if seg_cls.mask_postprocessing:
107
+ spec_mask = inspect.getfullargspec(seg_cls.process_mask)
108
+ valid_kw_mask = spec_mask.kwonlyargs
109
+ annot_mask = spec_mask.annotations
110
+ else:
111
+ valid_kw_mask = []
112
+ annot_mask = {}
113
+
114
+ # Convert the input args to key-value pairs
115
+ kwargs = {}
116
+ kwargs_mask = {}
117
+ for key, value in [a.split("=") for a in args]:
118
+ if key in valid_kw_mask:
119
+ kwargs_mask[key] = ppid.convert_to_dtype(value, annot_mask[key])
120
+ elif key in valid_kw_appr:
121
+ kwargs[key] = ppid.convert_to_dtype(value, annot_appr[key])
122
+ else:
123
+ raise ValueError(
124
+ f"Invalid keyword '{key}' for {seg_method}. "
125
+ f"Allowed keywords are {valid_kw_appr + valid_kw_mask}!")
126
+ kwargs["kwargs_mask"] = kwargs_mask
127
+ return kwargs
@@ -0,0 +1 @@
1
+ from ._main import main # noqa: F401
@@ -0,0 +1,4 @@
1
+ from ._main import main
2
+
3
+ if __name__ == '__main__':
4
+ main()