celldetective 1.4.2__py3-none-any.whl → 1.5.0b1__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 (152) hide show
  1. celldetective/__init__.py +25 -0
  2. celldetective/__main__.py +62 -43
  3. celldetective/_version.py +1 -1
  4. celldetective/extra_properties.py +477 -399
  5. celldetective/filters.py +192 -97
  6. celldetective/gui/InitWindow.py +541 -411
  7. celldetective/gui/__init__.py +0 -15
  8. celldetective/gui/about.py +44 -39
  9. celldetective/gui/analyze_block.py +120 -84
  10. celldetective/gui/base/__init__.py +0 -0
  11. celldetective/gui/base/channel_norm_generator.py +335 -0
  12. celldetective/gui/base/components.py +249 -0
  13. celldetective/gui/base/feature_choice.py +92 -0
  14. celldetective/gui/base/figure_canvas.py +52 -0
  15. celldetective/gui/base/list_widget.py +133 -0
  16. celldetective/gui/{styles.py → base/styles.py} +92 -36
  17. celldetective/gui/base/utils.py +33 -0
  18. celldetective/gui/base_annotator.py +900 -767
  19. celldetective/gui/classifier_widget.py +6 -22
  20. celldetective/gui/configure_new_exp.py +777 -671
  21. celldetective/gui/control_panel.py +635 -524
  22. celldetective/gui/dynamic_progress.py +449 -0
  23. celldetective/gui/event_annotator.py +2023 -1662
  24. celldetective/gui/generic_signal_plot.py +1292 -944
  25. celldetective/gui/gui_utils.py +899 -1289
  26. celldetective/gui/interactions_block.py +658 -0
  27. celldetective/gui/interactive_timeseries_viewer.py +447 -0
  28. celldetective/gui/json_readers.py +48 -15
  29. celldetective/gui/layouts/__init__.py +5 -0
  30. celldetective/gui/layouts/background_model_free_layout.py +537 -0
  31. celldetective/gui/layouts/channel_offset_layout.py +134 -0
  32. celldetective/gui/layouts/local_correction_layout.py +91 -0
  33. celldetective/gui/layouts/model_fit_layout.py +372 -0
  34. celldetective/gui/layouts/operation_layout.py +68 -0
  35. celldetective/gui/layouts/protocol_designer_layout.py +96 -0
  36. celldetective/gui/pair_event_annotator.py +3130 -2435
  37. celldetective/gui/plot_measurements.py +586 -267
  38. celldetective/gui/plot_signals_ui.py +724 -506
  39. celldetective/gui/preprocessing_block.py +395 -0
  40. celldetective/gui/process_block.py +1678 -1831
  41. celldetective/gui/seg_model_loader.py +580 -473
  42. celldetective/gui/settings/__init__.py +0 -7
  43. celldetective/gui/settings/_cellpose_model_params.py +181 -0
  44. celldetective/gui/settings/_event_detection_model_params.py +95 -0
  45. celldetective/gui/settings/_segmentation_model_params.py +159 -0
  46. celldetective/gui/settings/_settings_base.py +77 -65
  47. celldetective/gui/settings/_settings_event_model_training.py +752 -526
  48. celldetective/gui/settings/_settings_measurements.py +1133 -964
  49. celldetective/gui/settings/_settings_neighborhood.py +574 -488
  50. celldetective/gui/settings/_settings_segmentation_model_training.py +779 -564
  51. celldetective/gui/settings/_settings_signal_annotator.py +329 -305
  52. celldetective/gui/settings/_settings_tracking.py +1304 -1094
  53. celldetective/gui/settings/_stardist_model_params.py +98 -0
  54. celldetective/gui/survival_ui.py +422 -312
  55. celldetective/gui/tableUI.py +1665 -1701
  56. celldetective/gui/table_ops/_maths.py +295 -0
  57. celldetective/gui/table_ops/_merge_groups.py +140 -0
  58. celldetective/gui/table_ops/_merge_one_hot.py +95 -0
  59. celldetective/gui/table_ops/_query_table.py +43 -0
  60. celldetective/gui/table_ops/_rename_col.py +44 -0
  61. celldetective/gui/thresholds_gui.py +382 -179
  62. celldetective/gui/viewers/__init__.py +0 -0
  63. celldetective/gui/viewers/base_viewer.py +700 -0
  64. celldetective/gui/viewers/channel_offset_viewer.py +331 -0
  65. celldetective/gui/viewers/contour_viewer.py +394 -0
  66. celldetective/gui/viewers/size_viewer.py +153 -0
  67. celldetective/gui/viewers/spot_detection_viewer.py +341 -0
  68. celldetective/gui/viewers/threshold_viewer.py +309 -0
  69. celldetective/gui/workers.py +403 -126
  70. celldetective/log_manager.py +92 -0
  71. celldetective/measure.py +1895 -1478
  72. celldetective/napari/__init__.py +0 -0
  73. celldetective/napari/utils.py +1025 -0
  74. celldetective/neighborhood.py +1914 -1448
  75. celldetective/preprocessing.py +1620 -1220
  76. celldetective/processes/__init__.py +0 -0
  77. celldetective/processes/background_correction.py +271 -0
  78. celldetective/processes/compute_neighborhood.py +894 -0
  79. celldetective/processes/detect_events.py +246 -0
  80. celldetective/processes/downloader.py +137 -0
  81. celldetective/processes/measure_cells.py +565 -0
  82. celldetective/processes/segment_cells.py +760 -0
  83. celldetective/processes/track_cells.py +435 -0
  84. celldetective/processes/train_segmentation_model.py +694 -0
  85. celldetective/processes/train_signal_model.py +265 -0
  86. celldetective/processes/unified_process.py +292 -0
  87. celldetective/regionprops/_regionprops.py +358 -317
  88. celldetective/relative_measurements.py +987 -710
  89. celldetective/scripts/measure_cells.py +313 -212
  90. celldetective/scripts/measure_relative.py +90 -46
  91. celldetective/scripts/segment_cells.py +165 -104
  92. celldetective/scripts/segment_cells_thresholds.py +96 -68
  93. celldetective/scripts/track_cells.py +198 -149
  94. celldetective/scripts/train_segmentation_model.py +324 -201
  95. celldetective/scripts/train_signal_model.py +87 -45
  96. celldetective/segmentation.py +844 -749
  97. celldetective/signals.py +3514 -2861
  98. celldetective/tracking.py +30 -15
  99. celldetective/utils/__init__.py +0 -0
  100. celldetective/utils/cellpose_utils/__init__.py +133 -0
  101. celldetective/utils/color_mappings.py +42 -0
  102. celldetective/utils/data_cleaning.py +630 -0
  103. celldetective/utils/data_loaders.py +450 -0
  104. celldetective/utils/dataset_helpers.py +207 -0
  105. celldetective/utils/downloaders.py +235 -0
  106. celldetective/utils/event_detection/__init__.py +8 -0
  107. celldetective/utils/experiment.py +1782 -0
  108. celldetective/utils/image_augmenters.py +308 -0
  109. celldetective/utils/image_cleaning.py +74 -0
  110. celldetective/utils/image_loaders.py +926 -0
  111. celldetective/utils/image_transforms.py +335 -0
  112. celldetective/utils/io.py +62 -0
  113. celldetective/utils/mask_cleaning.py +348 -0
  114. celldetective/utils/mask_transforms.py +5 -0
  115. celldetective/utils/masks.py +184 -0
  116. celldetective/utils/maths.py +351 -0
  117. celldetective/utils/model_getters.py +325 -0
  118. celldetective/utils/model_loaders.py +296 -0
  119. celldetective/utils/normalization.py +380 -0
  120. celldetective/utils/parsing.py +465 -0
  121. celldetective/utils/plots/__init__.py +0 -0
  122. celldetective/utils/plots/regression.py +53 -0
  123. celldetective/utils/resources.py +34 -0
  124. celldetective/utils/stardist_utils/__init__.py +104 -0
  125. celldetective/utils/stats.py +90 -0
  126. celldetective/utils/types.py +21 -0
  127. {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/METADATA +1 -1
  128. celldetective-1.5.0b1.dist-info/RECORD +187 -0
  129. {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/WHEEL +1 -1
  130. tests/gui/test_new_project.py +129 -117
  131. tests/gui/test_project.py +127 -79
  132. tests/test_filters.py +39 -15
  133. tests/test_notebooks.py +8 -0
  134. tests/test_tracking.py +232 -13
  135. tests/test_utils.py +123 -77
  136. celldetective/gui/base_components.py +0 -23
  137. celldetective/gui/layouts.py +0 -1602
  138. celldetective/gui/processes/compute_neighborhood.py +0 -594
  139. celldetective/gui/processes/downloader.py +0 -111
  140. celldetective/gui/processes/measure_cells.py +0 -360
  141. celldetective/gui/processes/segment_cells.py +0 -499
  142. celldetective/gui/processes/track_cells.py +0 -303
  143. celldetective/gui/processes/train_segmentation_model.py +0 -270
  144. celldetective/gui/processes/train_signal_model.py +0 -108
  145. celldetective/gui/table_ops/merge_groups.py +0 -118
  146. celldetective/gui/viewers.py +0 -1354
  147. celldetective/io.py +0 -3663
  148. celldetective/utils.py +0 -3108
  149. celldetective-1.4.2.dist-info/RECORD +0 -123
  150. {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/entry_points.txt +0 -0
  151. {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/licenses/LICENSE +0 -0
  152. {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,465 @@
1
+ import configparser
2
+ import json
3
+ import os
4
+ import re
5
+ from pathlib import PurePath, Path
6
+ from typing import Union, Dict
7
+
8
+ import numpy as np
9
+
10
+
11
+ def _get_normalize_kwargs_from_config(config):
12
+
13
+ if isinstance(config, str):
14
+ if os.path.exists(config):
15
+ with open(config) as cfg:
16
+ config = json.load(cfg)
17
+ else:
18
+ print("Configuration could not be loaded...")
19
+ os.abort()
20
+
21
+ normalization_percentile = config["normalization_percentile"]
22
+ normalization_clip = config["normalization_clip"]
23
+ normalization_values = config["normalization_values"]
24
+ normalize_kwargs = _get_normalize_kwargs(
25
+ normalization_percentile, normalization_values, normalization_clip
26
+ )
27
+
28
+ return normalize_kwargs
29
+
30
+
31
+ def config_section_to_dict(
32
+ path: Union[str, PurePath, Path], section: str
33
+ ) -> Union[Dict, None]:
34
+ """
35
+ Parse the config file to extract experiment parameters
36
+ following https://wiki.python.org/moin/ConfigParserExamples
37
+
38
+ Parameters
39
+ ----------
40
+
41
+ path: str
42
+ path to the config.ini file
43
+
44
+ section: str
45
+ name of the section that contains the parameter
46
+
47
+ Returns
48
+ -------
49
+
50
+ dict1: dictionary
51
+
52
+ Examples
53
+ --------
54
+ >>> config = "path/to/config_file.ini"
55
+ >>> section = "Channels"
56
+ >>> channel_dictionary = config_section_to_dict(config,section)
57
+ >>> print(channel_dictionary)
58
+ # {'brightfield_channel': '0',
59
+ # 'live_nuclei_channel': 'nan',
60
+ # 'dead_nuclei_channel': 'nan',
61
+ # 'effector_fluo_channel': 'nan',
62
+ # 'adhesion_channel': '1',
63
+ # 'fluo_channel_1': 'nan',
64
+ # 'fluo_channel_2': 'nan',
65
+ # 'fitc_channel': '2',
66
+ # 'cy5_channel': '3'}
67
+ """
68
+
69
+ Config = configparser.ConfigParser(interpolation=None)
70
+ Config.read(path)
71
+ dict1 = {}
72
+ try:
73
+ options = Config.options(section)
74
+ except:
75
+ return None
76
+ for option in options:
77
+ try:
78
+ dict1[option] = Config.get(section, option)
79
+ if dict1[option] == -1:
80
+ print("skip: %s" % option)
81
+ except:
82
+ print("exception on %s!" % option)
83
+ dict1[option] = None
84
+ return dict1
85
+
86
+
87
+ def _extract_channel_indices_from_config(config, channels_to_extract):
88
+ """
89
+ Extracts the indices of specified channels from a configuration object.
90
+
91
+ This function attempts to map required channel names to their respective indices as specified in a
92
+ configuration file. It supports two versions of configuration parsing: a primary method (V2) and a
93
+ fallback legacy method. If the required channels are not found using the primary method, the function
94
+ attempts to find them using the legacy configuration settings.
95
+
96
+ Parameters
97
+ ----------
98
+ config : ConfigParser object
99
+ The configuration object parsed from a .ini or similar configuration file that includes channel settings.
100
+ channels_to_extract : list of str
101
+ A list of channel names for which indices are to be extracted from the configuration settings.
102
+
103
+ Returns
104
+ -------
105
+ list of int or None
106
+ A list containing the indices of the specified channels as found in the configuration settings.
107
+ If a channel cannot be found, None is appended in its place. If an error occurs during the extraction
108
+ process, the function returns None.
109
+
110
+ Notes
111
+ -----
112
+ - This function is designed to be flexible, accommodating changes in configuration file structure by
113
+ checking multiple sections for the required information.
114
+ - The configuration file is expected to contain either "Channels" or "MovieSettings" sections with mappings
115
+ from channel names to indices.
116
+ - An error message is printed if a required channel cannot be found, advising the user to check the
117
+ configuration file.
118
+
119
+ Examples
120
+ --------
121
+ >>> config = "path/to/config_file.ini"
122
+ >>> channels_to_extract = ['adhesion_channel', 'brightfield_channel']
123
+ >>> channel_indices = _extract_channel_indices_from_config(config, channels_to_extract)
124
+ >>> print(channel_indices)
125
+ # [1, 0] or None if an error occurs or the channels are not found.
126
+ """
127
+
128
+ if isinstance(channels_to_extract, str):
129
+ channels_to_extract = [channels_to_extract]
130
+
131
+ channels = []
132
+ for c in channels_to_extract:
133
+ try:
134
+ c1 = int(config_section_to_dict(config, "Channels")[c])
135
+ channels.append(c1)
136
+ except Exception as e:
137
+ print(
138
+ f"Warning: The channel {c} required by the model is not available in your data..."
139
+ )
140
+ channels.append(None)
141
+ if np.all([c is None for c in channels]):
142
+ channels = None
143
+
144
+ return channels
145
+
146
+
147
+ def _extract_nbr_channels_from_config(config, return_names=False):
148
+ """
149
+
150
+ Examples
151
+ --------
152
+ >>> config = "path/to/config_file.ini"
153
+ >>> nbr_channels = _extract_channel_indices_from_config(config)
154
+ >>> print(nbr_channels)
155
+ # 4
156
+ """
157
+
158
+ # V2
159
+ nbr_channels = 0
160
+ channels = []
161
+ try:
162
+ fields = config_section_to_dict(config, "Channels")
163
+ for c in fields:
164
+ try:
165
+ channel = int(config_section_to_dict(config, "Channels")[c])
166
+ nbr_channels += 1
167
+ channels.append(c)
168
+ except:
169
+ pass
170
+ except:
171
+ pass
172
+
173
+ if nbr_channels == 0:
174
+
175
+ # Read channels LEGACY
176
+ nbr_channels = 0
177
+ channels = []
178
+ try:
179
+ brightfield_channel = int(
180
+ config_section_to_dict(config, "MovieSettings")["brightfield_channel"]
181
+ )
182
+ nbr_channels += 1
183
+ channels.append("brightfield_channel")
184
+ except:
185
+ brightfield_channel = None
186
+
187
+ try:
188
+ live_nuclei_channel = int(
189
+ config_section_to_dict(config, "MovieSettings")["live_nuclei_channel"]
190
+ )
191
+ nbr_channels += 1
192
+ channels.append("live_nuclei_channel")
193
+ except:
194
+ live_nuclei_channel = None
195
+
196
+ try:
197
+ dead_nuclei_channel = int(
198
+ config_section_to_dict(config, "MovieSettings")["dead_nuclei_channel"]
199
+ )
200
+ nbr_channels += 1
201
+ channels.append("dead_nuclei_channel")
202
+ except:
203
+ dead_nuclei_channel = None
204
+
205
+ try:
206
+ effector_fluo_channel = int(
207
+ config_section_to_dict(config, "MovieSettings")["effector_fluo_channel"]
208
+ )
209
+ nbr_channels += 1
210
+ channels.append("effector_fluo_channel")
211
+ except:
212
+ effector_fluo_channel = None
213
+
214
+ try:
215
+ adhesion_channel = int(
216
+ config_section_to_dict(config, "MovieSettings")["adhesion_channel"]
217
+ )
218
+ nbr_channels += 1
219
+ channels.append("adhesion_channel")
220
+ except:
221
+ adhesion_channel = None
222
+
223
+ try:
224
+ fluo_channel_1 = int(
225
+ config_section_to_dict(config, "MovieSettings")["fluo_channel_1"]
226
+ )
227
+ nbr_channels += 1
228
+ channels.append("fluo_channel_1")
229
+ except:
230
+ fluo_channel_1 = None
231
+
232
+ try:
233
+ fluo_channel_2 = int(
234
+ config_section_to_dict(config, "MovieSettings")["fluo_channel_2"]
235
+ )
236
+ nbr_channels += 1
237
+ channels.append("fluo_channel_2")
238
+ except:
239
+ fluo_channel_2 = None
240
+
241
+ if return_names:
242
+ return nbr_channels, channels
243
+ else:
244
+ return nbr_channels
245
+
246
+
247
+ def _extract_labels_from_config(config, number_of_wells):
248
+ """
249
+
250
+ Extract each well's biological condition from the configuration file
251
+
252
+ Parameters
253
+ ----------
254
+
255
+ config: str,
256
+ path to the configuration file
257
+
258
+ number_of_wells: int,
259
+ total number of wells in the experiment
260
+
261
+ Returns
262
+ -------
263
+
264
+ labels: string of the biological condition for each well
265
+
266
+ """
267
+
268
+ # Deprecated, need to read metadata to extract concentration units and discard non essential fields
269
+
270
+ try:
271
+ concentrations = config_section_to_dict(config, "Labels")[
272
+ "concentrations"
273
+ ].split(",")
274
+ cell_types = config_section_to_dict(config, "Labels")["cell_types"].split(",")
275
+ antibodies = config_section_to_dict(config, "Labels")["antibodies"].split(",")
276
+ pharmaceutical_agents = config_section_to_dict(config, "Labels")[
277
+ "pharmaceutical_agents"
278
+ ].split(",")
279
+ index = np.arange(len(concentrations)).astype(int) + 1
280
+ if not np.all(pharmaceutical_agents == "None"):
281
+ labels = [
282
+ f"W{idx}: [CT] " + a + "; [Ab] " + b + " @ " + c + " pM " + d
283
+ for idx, a, b, c, d in zip(
284
+ index, cell_types, antibodies, concentrations, pharmaceutical_agents
285
+ )
286
+ ]
287
+ else:
288
+ labels = [
289
+ f"W{idx}: [CT] " + a + "; [Ab] " + b + " @ " + c + " pM "
290
+ for idx, a, b, c in zip(index, cell_types, antibodies, concentrations)
291
+ ]
292
+
293
+ except Exception as e:
294
+ print(
295
+ f"{e}: the well labels cannot be read from the concentration and cell_type fields"
296
+ )
297
+ labels = np.linspace(0, number_of_wells - 1, number_of_wells, dtype=str)
298
+
299
+ return labels
300
+
301
+
302
+ def _extract_channels_from_config(config):
303
+ """
304
+ Extracts channel names and their indices from an experiment configuration.
305
+
306
+ Parameters
307
+ ----------
308
+ config : path to config file (.ini)
309
+ The configuration object parsed from an experiment's .ini or similar configuration file.
310
+
311
+ Returns
312
+ -------
313
+ tuple
314
+ A tuple containing two numpy arrays: `channel_names` and `channel_indices`. `channel_names` includes
315
+ the names of the channels as specified in the configuration, and `channel_indices` includes their
316
+ corresponding indices. Both arrays are ordered according to the channel indices.
317
+
318
+ Examples
319
+ --------
320
+ >>> config = "path/to/config_file.ini"
321
+ >>> channels, indices = _extract_channels_from_config(config)
322
+ >>> print(channels)
323
+ # array(['brightfield_channel', 'adhesion_channel', 'fitc_channel',
324
+ # 'cy5_channel'], dtype='<U19')
325
+ >>> print(indices)
326
+ # array([0, 1, 2, 3])
327
+ """
328
+
329
+ channel_names = []
330
+ channel_indices = []
331
+ try:
332
+ fields = config_section_to_dict(config, "Channels")
333
+ for c in fields:
334
+ try:
335
+ idx = int(config_section_to_dict(config, "Channels")[c])
336
+ channel_names.append(c)
337
+ channel_indices.append(idx)
338
+ except:
339
+ pass
340
+ except:
341
+ pass
342
+
343
+ channel_indices = np.array(channel_indices)
344
+ channel_names = np.array(channel_names)
345
+ reorder = np.argsort(channel_indices)
346
+ channel_indices = channel_indices[reorder]
347
+ channel_names = channel_names[reorder]
348
+
349
+ return channel_names, channel_indices
350
+
351
+
352
+ def _get_normalize_kwargs(
353
+ normalization_percentile, normalization_values, normalization_clip
354
+ ):
355
+
356
+ values = []
357
+ percentiles = []
358
+ for k in range(len(normalization_percentile)):
359
+ if normalization_percentile[k]:
360
+ percentiles.append(normalization_values[k])
361
+ values.append(None)
362
+ else:
363
+ percentiles.append(None)
364
+ values.append(normalization_values[k])
365
+
366
+ return {"percentiles": percentiles, "values": values, "clip": normalization_clip}
367
+
368
+
369
+ def demangle_column_name(name):
370
+ if name.startswith("BACKTICK_QUOTED_STRING_"):
371
+ # Unquote backtick-quoted string.
372
+ return (
373
+ name[len("BACKTICK_QUOTED_STRING_") :]
374
+ .replace("_DOT_", ".")
375
+ .replace("_SLASH_", "/")
376
+ .replace("_MINUS_", "-")
377
+ .replace("_PLUS_", "+")
378
+ .replace("_PERCENT_", "%")
379
+ .replace("_STAR_", "*")
380
+ .replace("_LPAR_", "(")
381
+ .replace("_RPAR_", ")")
382
+ .replace("_AMPER_", "&")
383
+ )
384
+ return name
385
+
386
+
387
+ def extract_cols_from_query(query: str):
388
+
389
+ backtick_pattern = r"`([^`]+)`"
390
+ backticked = set(re.findall(backtick_pattern, query))
391
+
392
+ # 2. Remove backtick sections so they don't get double-counted
393
+ cleaned_query = re.sub(backtick_pattern, "", query)
394
+
395
+ # 3. Extract bare identifiers from the remaining string
396
+ identifier_pattern = r"\b([A-Za-z_]\w*)\b"
397
+ bare = set(re.findall(identifier_pattern, cleaned_query))
398
+
399
+ # 4. Remove Python keywords, operators, and pandas builtins
400
+ import pandas as pd
401
+
402
+ blacklist = (
403
+ set(dir(pd))
404
+ | set(dir(__builtins__))
405
+ | {"and", "or", "not", "in", "True", "False"}
406
+ )
407
+ bare = {c for c in bare if c not in blacklist}
408
+ cols = backticked | bare
409
+
410
+ return list([demangle_column_name(c) for c in cols])
411
+
412
+
413
+ def parse_isotropic_radii(string):
414
+ """
415
+ Parse a string representing isotropic radii into a structured list.
416
+
417
+ This function extracts integer values and ranges (denoted by square brackets)
418
+ from a string input and returns them as a list. Single values are stored as integers,
419
+ while ranges are represented as lists of two integers.
420
+
421
+ Parameters
422
+ ----------
423
+ string : str
424
+ The input string containing radii and ranges, separated by commas or spaces.
425
+ Ranges should be enclosed in square brackets, e.g., `[1 2]`.
426
+
427
+ Returns
428
+ -------
429
+ list
430
+ A list of parsed radii where:
431
+ - Single integers are included as `int`.
432
+ - Ranges are included as two-element lists `[start, end]`.
433
+
434
+ Examples
435
+ --------
436
+ Parse a string with single radii and ranges:
437
+
438
+ >>> parse_isotropic_radii("1, [2 3], 4")
439
+ [1, [2, 3], 4]
440
+
441
+ Handle inputs with mixed delimiters:
442
+
443
+ >>> parse_isotropic_radii("5 [6 7], 8")
444
+ [5, [6, 7], 8]
445
+
446
+ Notes
447
+ -----
448
+ - The function splits the input string by commas or spaces.
449
+ - It identifies ranges using square brackets and assumes that ranges are always
450
+ two consecutive values.
451
+ - Non-integer sections of the string are ignored.
452
+
453
+ """
454
+
455
+ sections = re.split(r"[ ,]", string)
456
+ radii = []
457
+ for k, s in enumerate(sections):
458
+ if s.isdigit():
459
+ radii.append(int(s))
460
+ if "[" in s:
461
+ ring = [int(s.replace("[", "")), int(sections[k + 1].replace("]", ""))]
462
+ radii.append(ring)
463
+ else:
464
+ pass
465
+ return radii
File without changes
@@ -0,0 +1,53 @@
1
+ import numpy as np
2
+ from matplotlib import pyplot as plt
3
+
4
+
5
+ def regression_plot(y_pred, y_true, savepath=None):
6
+ """
7
+
8
+ Create a regression plot to compare predicted and ground truth values.
9
+
10
+ Parameters
11
+ ----------
12
+ y_pred : array-like
13
+ Predicted values.
14
+ y_true : array-like
15
+ Ground truth values.
16
+ savepath : str or None, optional
17
+ File path to save the plot. If None, the plot is displayed but not saved. Default is None.
18
+
19
+ Returns
20
+ -------
21
+ None
22
+
23
+ Notes
24
+ -----
25
+ This function creates a scatter plot comparing the predicted values (`y_pred`) to the ground truth values (`y_true`)
26
+ for regression analysis. The plot also includes a diagonal reference line to visualize the ideal prediction scenario.
27
+
28
+ If `savepath` is provided, the plot is saved as an image file at the specified path. The file format and other
29
+ parameters can be controlled by the `savepath` argument.
30
+
31
+ Examples
32
+ --------
33
+ >>> y_pred = [1.5, 2.0, 3.2, 4.1]
34
+ >>> y_true = [1.7, 2.1, 3.5, 4.2]
35
+ >>> regression_plot(y_pred, y_true)
36
+ # Create a scatter plot comparing the predicted values to the ground truth values.
37
+
38
+ >>> regression_plot(y_pred, y_true, savepath="regression_plot.png")
39
+ # Create a scatter plot and save it as "regression_plot.png".
40
+
41
+ """
42
+
43
+ fig, ax = plt.subplots(1, 1, figsize=(4, 3))
44
+ ax.scatter(y_pred, y_true)
45
+ ax.set_xlabel("prediction")
46
+ ax.set_ylabel("ground truth")
47
+ line = np.linspace(np.amin([y_pred, y_true]), np.amax([y_pred, y_true]), 1000)
48
+ ax.plot(line, line, linestyle="--", c="k", alpha=0.7)
49
+ plt.tight_layout()
50
+ if savepath is not None:
51
+ plt.savefig(savepath, bbox_inches="tight", dpi=300)
52
+ # plt.pause(2)
53
+ plt.close()
@@ -0,0 +1,34 @@
1
+ def auto_find_gpu():
2
+ """
3
+ Automatically detects the presence of GPU devices in the system.
4
+
5
+ This function checks if any GPU devices are available for use by querying the system's physical devices.
6
+ It is a utility function to simplify the process of determining whether GPU-accelerated computing can be
7
+ leveraged in data processing or model training tasks.
8
+
9
+ Returns
10
+ -------
11
+ bool
12
+ True if one or more GPU devices are detected, False otherwise.
13
+
14
+ Notes
15
+ -----
16
+ - The function uses TensorFlow's `list_physical_devices` method to query available devices, specifically
17
+ looking for 'GPU' devices.
18
+ - This function is useful for dynamically adjusting computation strategies based on available hardware resources.
19
+
20
+ Examples
21
+ --------
22
+ >>> has_gpu = auto_find_gpu()
23
+ >>> print(f"GPU available: {has_gpu}")
24
+ # GPU available: True or False based on the system's hardware configuration.
25
+ """
26
+ from tensorflow.config import list_physical_devices
27
+
28
+ gpus = list_physical_devices("GPU")
29
+ if len(gpus) > 0:
30
+ use_gpu = True
31
+ else:
32
+ use_gpu = False
33
+
34
+ return use_gpu
@@ -0,0 +1,104 @@
1
+ from pathlib import Path
2
+ from typing import Union
3
+ import os
4
+
5
+ os.environ["TF_CPP_MIN_VLOG_LEVEL"] = "3"
6
+ os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"
7
+
8
+ import numpy as np
9
+
10
+
11
+ def _prep_stardist_model(
12
+ model_name: str, path: Union[str, Path], use_gpu: bool = False, scale: float = 1
13
+ ):
14
+ """
15
+ Prepares and loads a StarDist2D model for segmentation tasks.
16
+
17
+ This function initializes a StarDist2D model with the specified parameters, sets GPU usage if desired,
18
+ and allows scaling to adapt the model for specific applications.
19
+
20
+ Parameters
21
+ ----------
22
+ model_name : str
23
+ The name of the StarDist2D model to load. This name should match the model saved in the specified path.
24
+ path : str
25
+ The directory where the model is stored.
26
+ use_gpu : bool, optional
27
+ If `True`, the model will be configured to use GPU acceleration for computations. Default is `False`.
28
+ scale : int or float, optional
29
+ A scaling factor for the model. This can be used to adapt the model for specific image resolutions.
30
+ Default is `1`.
31
+
32
+ Returns
33
+ -------
34
+ tuple
35
+ - model : StarDist2D
36
+ The loaded StarDist2D model configured with the specified parameters.
37
+ - scale_model : int or float
38
+ The scaling factor passed to the function.
39
+
40
+ Notes
41
+ -----
42
+ - Ensure the StarDist2D package is installed and the model files are correctly stored in the provided path.
43
+ - GPU support depends on the availability of compatible hardware and software setup.
44
+ """
45
+
46
+ from stardist.models import StarDist2D
47
+
48
+ model = StarDist2D(None, name=model_name, basedir=path)
49
+ model.config.use_gpu = use_gpu
50
+ model.use_gpu = use_gpu
51
+
52
+ scale_model = scale
53
+
54
+ print(f"StarDist model {model_name} successfully loaded...")
55
+ return model, scale_model
56
+
57
+
58
+ def _segment_image_with_stardist_model(
59
+ img, model=None, return_details=False, channel_axis=-1
60
+ ):
61
+ """
62
+ Segments an input image using a StarDist model.
63
+
64
+ This function applies a preloaded StarDist model to segment an input image and returns the resulting labeled mask.
65
+ Optionally, additional details about the segmentation can also be returned.
66
+
67
+ Parameters
68
+ ----------
69
+ img : ndarray
70
+ The input image to be segmented. It is expected to have a channel axis specified by `channel_axis`.
71
+ model : StarDist2D, optional
72
+ A preloaded StarDist model instance used for segmentation.
73
+ return_details : bool, optional
74
+ Whether to return additional details from the model alongside the labeled mask. Default is `False`.
75
+ channel_axis : int, optional
76
+ The axis of the input image that represents the channels. Default is `-1` (channel-last format).
77
+
78
+ Returns
79
+ -------
80
+ ndarray
81
+ A labeled mask of the same spatial dimensions as the input image, with segmented regions assigned unique
82
+ integer labels. The dtype of the mask is `uint16`.
83
+ tuple of (ndarray, dict), optional
84
+ If `return_details` is `True`, returns a tuple where the first element is the labeled mask and the second
85
+ element is a dictionary containing additional details about the segmentation.
86
+
87
+ Notes
88
+ -----
89
+ - The `img` array is internally rearranged to move the specified `channel_axis` to the last dimension to comply
90
+ with the StarDist model's input requirements.
91
+ - Ensure the provided `model` is a properly initialized StarDist model instance.
92
+ - The model automatically determines the number of tiles (`n_tiles`) required for processing large images.
93
+ """
94
+
95
+ if channel_axis != -1:
96
+ img = np.moveaxis(img, channel_axis, -1)
97
+
98
+ lbl, details = model.predict_instances(
99
+ img, n_tiles=model._guess_n_tiles(img), show_tile_progress=False, verbose=False
100
+ )
101
+ if not return_details:
102
+ return lbl.astype(np.uint16)
103
+ else:
104
+ return lbl.astype(np.uint16), details