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.
- celldetective/__init__.py +25 -0
- celldetective/__main__.py +62 -43
- celldetective/_version.py +1 -1
- celldetective/extra_properties.py +477 -399
- celldetective/filters.py +192 -97
- celldetective/gui/InitWindow.py +541 -411
- celldetective/gui/__init__.py +0 -15
- celldetective/gui/about.py +44 -39
- celldetective/gui/analyze_block.py +120 -84
- celldetective/gui/base/__init__.py +0 -0
- celldetective/gui/base/channel_norm_generator.py +335 -0
- celldetective/gui/base/components.py +249 -0
- celldetective/gui/base/feature_choice.py +92 -0
- celldetective/gui/base/figure_canvas.py +52 -0
- celldetective/gui/base/list_widget.py +133 -0
- celldetective/gui/{styles.py → base/styles.py} +92 -36
- celldetective/gui/base/utils.py +33 -0
- celldetective/gui/base_annotator.py +900 -767
- celldetective/gui/classifier_widget.py +6 -22
- celldetective/gui/configure_new_exp.py +777 -671
- celldetective/gui/control_panel.py +635 -524
- celldetective/gui/dynamic_progress.py +449 -0
- celldetective/gui/event_annotator.py +2023 -1662
- celldetective/gui/generic_signal_plot.py +1292 -944
- celldetective/gui/gui_utils.py +899 -1289
- celldetective/gui/interactions_block.py +658 -0
- celldetective/gui/interactive_timeseries_viewer.py +447 -0
- celldetective/gui/json_readers.py +48 -15
- celldetective/gui/layouts/__init__.py +5 -0
- celldetective/gui/layouts/background_model_free_layout.py +537 -0
- celldetective/gui/layouts/channel_offset_layout.py +134 -0
- celldetective/gui/layouts/local_correction_layout.py +91 -0
- celldetective/gui/layouts/model_fit_layout.py +372 -0
- celldetective/gui/layouts/operation_layout.py +68 -0
- celldetective/gui/layouts/protocol_designer_layout.py +96 -0
- celldetective/gui/pair_event_annotator.py +3130 -2435
- celldetective/gui/plot_measurements.py +586 -267
- celldetective/gui/plot_signals_ui.py +724 -506
- celldetective/gui/preprocessing_block.py +395 -0
- celldetective/gui/process_block.py +1678 -1831
- celldetective/gui/seg_model_loader.py +580 -473
- celldetective/gui/settings/__init__.py +0 -7
- celldetective/gui/settings/_cellpose_model_params.py +181 -0
- celldetective/gui/settings/_event_detection_model_params.py +95 -0
- celldetective/gui/settings/_segmentation_model_params.py +159 -0
- celldetective/gui/settings/_settings_base.py +77 -65
- celldetective/gui/settings/_settings_event_model_training.py +752 -526
- celldetective/gui/settings/_settings_measurements.py +1133 -964
- celldetective/gui/settings/_settings_neighborhood.py +574 -488
- celldetective/gui/settings/_settings_segmentation_model_training.py +779 -564
- celldetective/gui/settings/_settings_signal_annotator.py +329 -305
- celldetective/gui/settings/_settings_tracking.py +1304 -1094
- celldetective/gui/settings/_stardist_model_params.py +98 -0
- celldetective/gui/survival_ui.py +422 -312
- celldetective/gui/tableUI.py +1665 -1701
- celldetective/gui/table_ops/_maths.py +295 -0
- celldetective/gui/table_ops/_merge_groups.py +140 -0
- celldetective/gui/table_ops/_merge_one_hot.py +95 -0
- celldetective/gui/table_ops/_query_table.py +43 -0
- celldetective/gui/table_ops/_rename_col.py +44 -0
- celldetective/gui/thresholds_gui.py +382 -179
- celldetective/gui/viewers/__init__.py +0 -0
- celldetective/gui/viewers/base_viewer.py +700 -0
- celldetective/gui/viewers/channel_offset_viewer.py +331 -0
- celldetective/gui/viewers/contour_viewer.py +394 -0
- celldetective/gui/viewers/size_viewer.py +153 -0
- celldetective/gui/viewers/spot_detection_viewer.py +341 -0
- celldetective/gui/viewers/threshold_viewer.py +309 -0
- celldetective/gui/workers.py +403 -126
- celldetective/log_manager.py +92 -0
- celldetective/measure.py +1895 -1478
- celldetective/napari/__init__.py +0 -0
- celldetective/napari/utils.py +1025 -0
- celldetective/neighborhood.py +1914 -1448
- celldetective/preprocessing.py +1620 -1220
- celldetective/processes/__init__.py +0 -0
- celldetective/processes/background_correction.py +271 -0
- celldetective/processes/compute_neighborhood.py +894 -0
- celldetective/processes/detect_events.py +246 -0
- celldetective/processes/downloader.py +137 -0
- celldetective/processes/measure_cells.py +565 -0
- celldetective/processes/segment_cells.py +760 -0
- celldetective/processes/track_cells.py +435 -0
- celldetective/processes/train_segmentation_model.py +694 -0
- celldetective/processes/train_signal_model.py +265 -0
- celldetective/processes/unified_process.py +292 -0
- celldetective/regionprops/_regionprops.py +358 -317
- celldetective/relative_measurements.py +987 -710
- celldetective/scripts/measure_cells.py +313 -212
- celldetective/scripts/measure_relative.py +90 -46
- celldetective/scripts/segment_cells.py +165 -104
- celldetective/scripts/segment_cells_thresholds.py +96 -68
- celldetective/scripts/track_cells.py +198 -149
- celldetective/scripts/train_segmentation_model.py +324 -201
- celldetective/scripts/train_signal_model.py +87 -45
- celldetective/segmentation.py +844 -749
- celldetective/signals.py +3514 -2861
- celldetective/tracking.py +30 -15
- celldetective/utils/__init__.py +0 -0
- celldetective/utils/cellpose_utils/__init__.py +133 -0
- celldetective/utils/color_mappings.py +42 -0
- celldetective/utils/data_cleaning.py +630 -0
- celldetective/utils/data_loaders.py +450 -0
- celldetective/utils/dataset_helpers.py +207 -0
- celldetective/utils/downloaders.py +235 -0
- celldetective/utils/event_detection/__init__.py +8 -0
- celldetective/utils/experiment.py +1782 -0
- celldetective/utils/image_augmenters.py +308 -0
- celldetective/utils/image_cleaning.py +74 -0
- celldetective/utils/image_loaders.py +926 -0
- celldetective/utils/image_transforms.py +335 -0
- celldetective/utils/io.py +62 -0
- celldetective/utils/mask_cleaning.py +348 -0
- celldetective/utils/mask_transforms.py +5 -0
- celldetective/utils/masks.py +184 -0
- celldetective/utils/maths.py +351 -0
- celldetective/utils/model_getters.py +325 -0
- celldetective/utils/model_loaders.py +296 -0
- celldetective/utils/normalization.py +380 -0
- celldetective/utils/parsing.py +465 -0
- celldetective/utils/plots/__init__.py +0 -0
- celldetective/utils/plots/regression.py +53 -0
- celldetective/utils/resources.py +34 -0
- celldetective/utils/stardist_utils/__init__.py +104 -0
- celldetective/utils/stats.py +90 -0
- celldetective/utils/types.py +21 -0
- {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/METADATA +1 -1
- celldetective-1.5.0b1.dist-info/RECORD +187 -0
- {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/WHEEL +1 -1
- tests/gui/test_new_project.py +129 -117
- tests/gui/test_project.py +127 -79
- tests/test_filters.py +39 -15
- tests/test_notebooks.py +8 -0
- tests/test_tracking.py +232 -13
- tests/test_utils.py +123 -77
- celldetective/gui/base_components.py +0 -23
- celldetective/gui/layouts.py +0 -1602
- celldetective/gui/processes/compute_neighborhood.py +0 -594
- celldetective/gui/processes/downloader.py +0 -111
- celldetective/gui/processes/measure_cells.py +0 -360
- celldetective/gui/processes/segment_cells.py +0 -499
- celldetective/gui/processes/track_cells.py +0 -303
- celldetective/gui/processes/train_segmentation_model.py +0 -270
- celldetective/gui/processes/train_signal_model.py +0 -108
- celldetective/gui/table_ops/merge_groups.py +0 -118
- celldetective/gui/viewers.py +0 -1354
- celldetective/io.py +0 -3663
- celldetective/utils.py +0 -3108
- celldetective-1.4.2.dist-info/RECORD +0 -123
- {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/entry_points.txt +0 -0
- {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/licenses/LICENSE +0 -0
- {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
|