celldetective 1.1.1.post3__py3-none-any.whl → 1.2.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.
- celldetective/__init__.py +2 -1
- celldetective/__main__.py +17 -0
- celldetective/extra_properties.py +62 -34
- celldetective/gui/__init__.py +1 -0
- celldetective/gui/analyze_block.py +2 -1
- celldetective/gui/classifier_widget.py +18 -10
- celldetective/gui/control_panel.py +57 -6
- celldetective/gui/layouts.py +14 -11
- celldetective/gui/neighborhood_options.py +21 -13
- celldetective/gui/plot_signals_ui.py +39 -11
- celldetective/gui/process_block.py +413 -95
- celldetective/gui/retrain_segmentation_model_options.py +17 -4
- celldetective/gui/retrain_signal_model_options.py +106 -6
- celldetective/gui/signal_annotator.py +110 -30
- celldetective/gui/signal_annotator2.py +2708 -0
- celldetective/gui/signal_annotator_options.py +3 -1
- celldetective/gui/survival_ui.py +15 -6
- celldetective/gui/tableUI.py +248 -43
- celldetective/io.py +598 -416
- celldetective/measure.py +919 -969
- celldetective/models/pair_signal_detection/blank +0 -0
- celldetective/neighborhood.py +482 -340
- celldetective/preprocessing.py +81 -61
- celldetective/relative_measurements.py +648 -0
- celldetective/scripts/analyze_signals.py +1 -1
- celldetective/scripts/measure_cells.py +28 -8
- celldetective/scripts/measure_relative.py +103 -0
- celldetective/scripts/segment_cells.py +5 -5
- celldetective/scripts/track_cells.py +4 -1
- celldetective/scripts/train_segmentation_model.py +23 -18
- celldetective/scripts/train_signal_model.py +33 -0
- celldetective/segmentation.py +67 -29
- celldetective/signals.py +402 -8
- celldetective/tracking.py +8 -2
- celldetective/utils.py +144 -12
- {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/METADATA +8 -8
- {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/RECORD +42 -38
- {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/WHEEL +1 -1
- tests/test_segmentation.py +1 -1
- {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/LICENSE +0 -0
- {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/entry_points.txt +0 -0
- {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/top_level.txt +0 -0
celldetective/io.py
CHANGED
|
@@ -26,10 +26,11 @@ from stardist import fill_label_holes
|
|
|
26
26
|
from celldetective.utils import interpolate_nan
|
|
27
27
|
from scipy.interpolate import griddata
|
|
28
28
|
|
|
29
|
+
|
|
29
30
|
def get_experiment_wells(experiment):
|
|
30
31
|
|
|
31
32
|
"""
|
|
32
|
-
Retrieves the list of well directories from a given experiment directory, sorted
|
|
33
|
+
Retrieves the list of well directories from a given experiment directory, sorted
|
|
33
34
|
naturally and returned as a NumPy array of strings.
|
|
34
35
|
|
|
35
36
|
Parameters
|
|
@@ -40,104 +41,107 @@ def get_experiment_wells(experiment):
|
|
|
40
41
|
Returns
|
|
41
42
|
-------
|
|
42
43
|
np.ndarray
|
|
43
|
-
An array of strings, each representing the full path to a well directory within the specified
|
|
44
|
+
An array of strings, each representing the full path to a well directory within the specified
|
|
44
45
|
experiment. The array is empty if no well directories are found.
|
|
45
46
|
|
|
46
47
|
Notes
|
|
47
48
|
-----
|
|
48
|
-
- The function assumes well directories are prefixed with 'W' and uses this to filter directories
|
|
49
|
+
- The function assumes well directories are prefixed with 'W' and uses this to filter directories
|
|
49
50
|
within the experiment folder.
|
|
50
51
|
|
|
51
|
-
- Natural sorting is applied to the list of wells to ensure that the order is intuitive (e.g., 'W2'
|
|
52
|
-
comes before 'W10'). This sorting method is especially useful when dealing with numerical sequences
|
|
52
|
+
- Natural sorting is applied to the list of wells to ensure that the order is intuitive (e.g., 'W2'
|
|
53
|
+
comes before 'W10'). This sorting method is especially useful when dealing with numerical sequences
|
|
53
54
|
that are part of the directory names.
|
|
54
|
-
|
|
55
|
+
|
|
55
56
|
"""
|
|
56
57
|
|
|
57
58
|
if not experiment.endswith(os.sep):
|
|
58
59
|
experiment += os.sep
|
|
59
|
-
|
|
60
|
+
|
|
60
61
|
wells = natsorted(glob(experiment + "W*" + os.sep))
|
|
61
|
-
return np.array(wells,dtype=str)
|
|
62
|
+
return np.array(wells, dtype=str)
|
|
63
|
+
|
|
62
64
|
|
|
63
65
|
def get_config(experiment):
|
|
64
66
|
|
|
65
67
|
if not experiment.endswith(os.sep):
|
|
66
68
|
experiment += os.sep
|
|
67
|
-
|
|
69
|
+
|
|
68
70
|
config = experiment + 'config.ini'
|
|
69
71
|
config = rf"{config}"
|
|
70
|
-
assert os.path.exists(config),'The experiment configuration could not be located...'
|
|
71
|
-
return config
|
|
72
|
+
assert os.path.exists(config), 'The experiment configuration could not be located...'
|
|
73
|
+
return config
|
|
72
74
|
|
|
73
75
|
|
|
74
76
|
def get_spatial_calibration(experiment):
|
|
75
77
|
|
|
76
78
|
|
|
77
79
|
config = get_config(experiment)
|
|
78
|
-
PxToUm = float(ConfigSectionMap(config,"MovieSettings")["pxtoum"])
|
|
79
|
-
|
|
80
|
+
PxToUm = float(ConfigSectionMap(config, "MovieSettings")["pxtoum"])
|
|
81
|
+
|
|
80
82
|
return PxToUm
|
|
81
83
|
|
|
84
|
+
|
|
82
85
|
def get_temporal_calibration(experiment):
|
|
83
86
|
|
|
84
87
|
config = get_config(experiment)
|
|
85
|
-
FrameToMin = float(ConfigSectionMap(config,"MovieSettings")["frametomin"])
|
|
86
|
-
|
|
88
|
+
FrameToMin = float(ConfigSectionMap(config, "MovieSettings")["frametomin"])
|
|
89
|
+
|
|
87
90
|
return FrameToMin
|
|
88
91
|
|
|
92
|
+
|
|
89
93
|
def get_experiment_concentrations(experiment, dtype=str):
|
|
90
94
|
|
|
91
95
|
|
|
92
96
|
config = get_config(experiment)
|
|
93
97
|
wells = get_experiment_wells(experiment)
|
|
94
98
|
nbr_of_wells = len(wells)
|
|
95
|
-
|
|
96
|
-
concentrations = ConfigSectionMap(config,"Labels")["concentrations"].split(",")
|
|
99
|
+
|
|
100
|
+
concentrations = ConfigSectionMap(config, "Labels")["concentrations"].split(",")
|
|
97
101
|
if nbr_of_wells != len(concentrations):
|
|
98
|
-
concentrations = [str(s) for s in np.linspace(0,nbr_of_wells-1,nbr_of_wells)]
|
|
99
|
-
|
|
102
|
+
concentrations = [str(s) for s in np.linspace(0, nbr_of_wells - 1, nbr_of_wells)]
|
|
103
|
+
|
|
100
104
|
return np.array([dtype(c) for c in concentrations])
|
|
101
105
|
|
|
106
|
+
|
|
102
107
|
def get_experiment_cell_types(experiment, dtype=str):
|
|
103
|
-
|
|
104
108
|
config = get_config(experiment)
|
|
105
109
|
wells = get_experiment_wells(experiment)
|
|
106
110
|
nbr_of_wells = len(wells)
|
|
107
|
-
|
|
108
|
-
cell_types = ConfigSectionMap(config,"Labels")["cell_types"].split(",")
|
|
111
|
+
|
|
112
|
+
cell_types = ConfigSectionMap(config, "Labels")["cell_types"].split(",")
|
|
109
113
|
if nbr_of_wells != len(cell_types):
|
|
110
|
-
cell_types = [str(s) for s in np.linspace(0,nbr_of_wells-1,nbr_of_wells)]
|
|
111
|
-
|
|
114
|
+
cell_types = [str(s) for s in np.linspace(0, nbr_of_wells - 1, nbr_of_wells)]
|
|
115
|
+
|
|
112
116
|
return np.array([dtype(c) for c in cell_types])
|
|
113
117
|
|
|
118
|
+
|
|
114
119
|
def get_experiment_antibodies(experiment, dtype=str):
|
|
115
120
|
|
|
116
121
|
config = get_config(experiment)
|
|
117
122
|
wells = get_experiment_wells(experiment)
|
|
118
123
|
nbr_of_wells = len(wells)
|
|
119
|
-
|
|
120
|
-
antibodies = ConfigSectionMap(config,"Labels")["antibodies"].split(",")
|
|
124
|
+
|
|
125
|
+
antibodies = ConfigSectionMap(config, "Labels")["antibodies"].split(",")
|
|
121
126
|
if nbr_of_wells != len(antibodies):
|
|
122
|
-
antibodies = [str(s) for s in np.linspace(0,nbr_of_wells-1,nbr_of_wells)]
|
|
123
|
-
|
|
127
|
+
antibodies = [str(s) for s in np.linspace(0, nbr_of_wells - 1, nbr_of_wells)]
|
|
128
|
+
|
|
124
129
|
return np.array([dtype(c) for c in antibodies])
|
|
125
130
|
|
|
131
|
+
|
|
126
132
|
def get_experiment_pharmaceutical_agents(experiment, dtype=str):
|
|
127
|
-
|
|
128
133
|
config = get_config(experiment)
|
|
129
134
|
wells = get_experiment_wells(experiment)
|
|
130
135
|
nbr_of_wells = len(wells)
|
|
131
|
-
|
|
132
|
-
pharmaceutical_agents = ConfigSectionMap(config,"Labels")["pharmaceutical_agents"].split(",")
|
|
136
|
+
|
|
137
|
+
pharmaceutical_agents = ConfigSectionMap(config, "Labels")["pharmaceutical_agents"].split(",")
|
|
133
138
|
if nbr_of_wells != len(pharmaceutical_agents):
|
|
134
|
-
pharmaceutical_agents = [str(s) for s in np.linspace(0,nbr_of_wells-1,nbr_of_wells)]
|
|
135
|
-
|
|
139
|
+
pharmaceutical_agents = [str(s) for s in np.linspace(0, nbr_of_wells - 1, nbr_of_wells)]
|
|
140
|
+
|
|
136
141
|
return np.array([dtype(c) for c in pharmaceutical_agents])
|
|
137
142
|
|
|
138
143
|
|
|
139
144
|
def interpret_wells_and_positions(experiment, well_option, position_option):
|
|
140
|
-
|
|
141
145
|
"""
|
|
142
146
|
Interpret well and position options for a given experiment.
|
|
143
147
|
|
|
@@ -172,26 +176,26 @@ def interpret_wells_and_positions(experiment, well_option, position_option):
|
|
|
172
176
|
>>> experiment = ... # Some experiment object
|
|
173
177
|
>>> interpret_wells_and_positions(experiment, '*', '*')
|
|
174
178
|
(array([0, 1, 2, ..., n-1]), None)
|
|
175
|
-
|
|
179
|
+
|
|
176
180
|
>>> interpret_wells_and_positions(experiment, 2, '*')
|
|
177
181
|
([2], None)
|
|
178
|
-
|
|
182
|
+
|
|
179
183
|
>>> interpret_wells_and_positions(experiment, [1, 3, 5], 2)
|
|
180
184
|
([1, 3, 5], array([2]))
|
|
181
185
|
|
|
182
186
|
"""
|
|
183
|
-
|
|
187
|
+
|
|
184
188
|
wells = get_experiment_wells(experiment)
|
|
185
189
|
nbr_of_wells = len(wells)
|
|
186
190
|
|
|
187
|
-
if well_option=='*':
|
|
191
|
+
if well_option == '*':
|
|
188
192
|
well_indices = np.arange(nbr_of_wells)
|
|
189
193
|
elif isinstance(well_option, int) or isinstance(well_option, np.int_):
|
|
190
194
|
well_indices = [int(well_option)]
|
|
191
195
|
elif isinstance(well_option, list):
|
|
192
196
|
well_indices = well_option
|
|
193
|
-
|
|
194
|
-
if position_option=='*':
|
|
197
|
+
|
|
198
|
+
if position_option == '*':
|
|
195
199
|
position_indices = None
|
|
196
200
|
elif isinstance(position_option, int):
|
|
197
201
|
position_indices = np.array([position_option], dtype=int)
|
|
@@ -199,9 +203,9 @@ def interpret_wells_and_positions(experiment, well_option, position_option):
|
|
|
199
203
|
position_indices = position_option
|
|
200
204
|
|
|
201
205
|
return well_indices, position_indices
|
|
202
|
-
|
|
206
|
+
|
|
207
|
+
|
|
203
208
|
def extract_well_name_and_number(well):
|
|
204
|
-
|
|
205
209
|
"""
|
|
206
210
|
Extract the well name and number from a given well path.
|
|
207
211
|
|
|
@@ -237,10 +241,11 @@ def extract_well_name_and_number(well):
|
|
|
237
241
|
split_well_path = well.split(os.sep)
|
|
238
242
|
split_well_path = list(filter(None, split_well_path))
|
|
239
243
|
well_name = split_well_path[-1]
|
|
240
|
-
well_number = int(split_well_path[-1].replace('W',''))
|
|
241
|
-
|
|
244
|
+
well_number = int(split_well_path[-1].replace('W', ''))
|
|
245
|
+
|
|
242
246
|
return well_name, well_number
|
|
243
247
|
|
|
248
|
+
|
|
244
249
|
def extract_position_name(pos):
|
|
245
250
|
|
|
246
251
|
"""
|
|
@@ -274,17 +279,18 @@ def extract_position_name(pos):
|
|
|
274
279
|
split_pos_path = pos.split(os.sep)
|
|
275
280
|
split_pos_path = list(filter(None, split_pos_path))
|
|
276
281
|
pos_name = split_pos_path[-1]
|
|
277
|
-
|
|
282
|
+
|
|
278
283
|
return pos_name
|
|
279
284
|
|
|
285
|
+
|
|
280
286
|
def get_position_table(pos, population, return_path=False):
|
|
281
287
|
|
|
282
288
|
"""
|
|
283
289
|
Retrieves the data table for a specified population at a given position, optionally returning the table's file path.
|
|
284
290
|
|
|
285
|
-
This function locates and loads a CSV data table associated with a specific population (e.g., 'targets', 'cells')
|
|
286
|
-
from a specified position directory. The position directory should contain an 'output/tables' subdirectory where
|
|
287
|
-
the CSV file named 'trajectories_{population}.csv' is expected to be found. If the file exists, it is loaded into
|
|
291
|
+
This function locates and loads a CSV data table associated with a specific population (e.g., 'targets', 'cells')
|
|
292
|
+
from a specified position directory. The position directory should contain an 'output/tables' subdirectory where
|
|
293
|
+
the CSV file named 'trajectories_{population}.csv' is expected to be found. If the file exists, it is loaded into
|
|
288
294
|
a pandas DataFrame; otherwise, None is returned.
|
|
289
295
|
|
|
290
296
|
Parameters
|
|
@@ -292,17 +298,17 @@ def get_position_table(pos, population, return_path=False):
|
|
|
292
298
|
pos : str
|
|
293
299
|
The path to the position directory from which to load the data table.
|
|
294
300
|
population : str
|
|
295
|
-
The name of the population for which the data table is to be retrieved. This name is used to construct the
|
|
301
|
+
The name of the population for which the data table is to be retrieved. This name is used to construct the
|
|
296
302
|
file name of the CSV file to be loaded.
|
|
297
303
|
return_path : bool, optional
|
|
298
|
-
If True, returns a tuple containing the loaded data table (or None) and the path to the CSV file. If False,
|
|
304
|
+
If True, returns a tuple containing the loaded data table (or None) and the path to the CSV file. If False,
|
|
299
305
|
only the loaded data table (or None) is returned (default is False).
|
|
300
306
|
|
|
301
307
|
Returns
|
|
302
308
|
-------
|
|
303
309
|
pandas.DataFrame or None, or (pandas.DataFrame or None, str)
|
|
304
|
-
If return_path is False, returns the loaded data table as a pandas DataFrame, or None if the table file does
|
|
305
|
-
not exist. If return_path is True, returns a tuple where the first element is the data table (or None) and the
|
|
310
|
+
If return_path is False, returns the loaded data table as a pandas DataFrame, or None if the table file does
|
|
311
|
+
not exist. If return_path is True, returns a tuple where the first element is the data table (or None) and the
|
|
306
312
|
second element is the path to the CSV file.
|
|
307
313
|
|
|
308
314
|
Examples
|
|
@@ -312,13 +318,13 @@ def get_position_table(pos, population, return_path=False):
|
|
|
312
318
|
|
|
313
319
|
>>> df_pos, table_path = get_position_table('/path/to/position', 'targets', return_path=True)
|
|
314
320
|
# This will load the 'trajectories_targets.csv' table and also return the path to the CSV file.
|
|
315
|
-
|
|
321
|
+
|
|
316
322
|
"""
|
|
317
|
-
|
|
323
|
+
|
|
318
324
|
if not pos.endswith(os.sep):
|
|
319
|
-
table = os.sep.join([pos,'output','tables',f'trajectories_{population}.csv'])
|
|
325
|
+
table = os.sep.join([pos, 'output', 'tables', f'trajectories_{population}.csv'])
|
|
320
326
|
else:
|
|
321
|
-
table = pos + os.sep.join(['output','tables',f'trajectories_{population}.csv'])
|
|
327
|
+
table = pos + os.sep.join(['output', 'tables', f'trajectories_{population}.csv'])
|
|
322
328
|
|
|
323
329
|
if os.path.exists(table):
|
|
324
330
|
df_pos = pd.read_csv(table, low_memory=False)
|
|
@@ -331,13 +337,13 @@ def get_position_table(pos, population, return_path=False):
|
|
|
331
337
|
return df_pos
|
|
332
338
|
|
|
333
339
|
def get_position_pickle(pos, population, return_path=False):
|
|
334
|
-
|
|
340
|
+
|
|
335
341
|
"""
|
|
336
342
|
Retrieves the data table for a specified population at a given position, optionally returning the table's file path.
|
|
337
343
|
|
|
338
|
-
This function locates and loads a CSV data table associated with a specific population (e.g., 'targets', 'cells')
|
|
339
|
-
from a specified position directory. The position directory should contain an 'output/tables' subdirectory where
|
|
340
|
-
the CSV file named 'trajectories_{population}.csv' is expected to be found. If the file exists, it is loaded into
|
|
344
|
+
This function locates and loads a CSV data table associated with a specific population (e.g., 'targets', 'cells')
|
|
345
|
+
from a specified position directory. The position directory should contain an 'output/tables' subdirectory where
|
|
346
|
+
the CSV file named 'trajectories_{population}.csv' is expected to be found. If the file exists, it is loaded into
|
|
341
347
|
a pandas DataFrame; otherwise, None is returned.
|
|
342
348
|
|
|
343
349
|
Parameters
|
|
@@ -345,17 +351,17 @@ def get_position_pickle(pos, population, return_path=False):
|
|
|
345
351
|
pos : str
|
|
346
352
|
The path to the position directory from which to load the data table.
|
|
347
353
|
population : str
|
|
348
|
-
The name of the population for which the data table is to be retrieved. This name is used to construct the
|
|
354
|
+
The name of the population for which the data table is to be retrieved. This name is used to construct the
|
|
349
355
|
file name of the CSV file to be loaded.
|
|
350
356
|
return_path : bool, optional
|
|
351
|
-
If True, returns a tuple containing the loaded data table (or None) and the path to the CSV file. If False,
|
|
357
|
+
If True, returns a tuple containing the loaded data table (or None) and the path to the CSV file. If False,
|
|
352
358
|
only the loaded data table (or None) is returned (default is False).
|
|
353
359
|
|
|
354
360
|
Returns
|
|
355
361
|
-------
|
|
356
362
|
pandas.DataFrame or None, or (pandas.DataFrame or None, str)
|
|
357
|
-
If return_path is False, returns the loaded data table as a pandas DataFrame, or None if the table file does
|
|
358
|
-
not exist. If return_path is True, returns a tuple where the first element is the data table (or None) and the
|
|
363
|
+
If return_path is False, returns the loaded data table as a pandas DataFrame, or None if the table file does
|
|
364
|
+
not exist. If return_path is True, returns a tuple where the first element is the data table (or None) and the
|
|
359
365
|
second element is the path to the CSV file.
|
|
360
366
|
|
|
361
367
|
Examples
|
|
@@ -365,19 +371,19 @@ def get_position_pickle(pos, population, return_path=False):
|
|
|
365
371
|
|
|
366
372
|
>>> df_pos, table_path = get_position_table('/path/to/position', 'targets', return_path=True)
|
|
367
373
|
# This will load the 'trajectories_targets.csv' table and also return the path to the CSV file.
|
|
368
|
-
|
|
374
|
+
|
|
369
375
|
"""
|
|
370
|
-
|
|
376
|
+
|
|
371
377
|
if not pos.endswith(os.sep):
|
|
372
378
|
table = os.sep.join([pos,'output','tables',f'trajectories_{population}.pkl'])
|
|
373
379
|
else:
|
|
374
|
-
table = pos + os.sep.join(['output','tables',f'trajectories_{population}.pkl'])
|
|
380
|
+
table = pos + os.sep.join(['output','tables',f'trajectories_{population}.pkl'])
|
|
375
381
|
|
|
376
382
|
if os.path.exists(table):
|
|
377
383
|
df_pos = np.load(table, allow_pickle=True)
|
|
378
384
|
else:
|
|
379
385
|
df_pos = None
|
|
380
|
-
|
|
386
|
+
|
|
381
387
|
if return_path:
|
|
382
388
|
return df_pos, table
|
|
383
389
|
else:
|
|
@@ -422,29 +428,27 @@ def get_position_movie_path(pos, prefix=''):
|
|
|
422
428
|
|
|
423
429
|
|
|
424
430
|
if not pos.endswith(os.sep):
|
|
425
|
-
pos+=os.sep
|
|
426
|
-
movies = glob(pos+os.sep.join(['movie',prefix+'*.tif']))
|
|
427
|
-
if len(movies)>0:
|
|
431
|
+
pos += os.sep
|
|
432
|
+
movies = glob(pos + os.sep.join(['movie', prefix + '*.tif']))
|
|
433
|
+
if len(movies) > 0:
|
|
428
434
|
stack_path = movies[0]
|
|
429
435
|
else:
|
|
430
436
|
stack_path = None
|
|
431
|
-
|
|
432
|
-
return stack_path
|
|
433
|
-
|
|
434
437
|
|
|
438
|
+
return stack_path
|
|
435
439
|
|
|
436
|
-
def load_experiment_tables(experiment, population='targets', well_option='*',position_option='*', return_pos_info=False):
|
|
437
|
-
|
|
438
440
|
|
|
441
|
+
def load_experiment_tables(experiment, population='targets', well_option='*', position_option='*',
|
|
442
|
+
return_pos_info=False, load_pickle=False):
|
|
439
443
|
"""
|
|
440
|
-
Loads and aggregates data tables for specified wells and positions within an experiment,
|
|
444
|
+
Loads and aggregates data tables for specified wells and positions within an experiment,
|
|
441
445
|
optionally returning position information alongside the aggregated data table.
|
|
442
446
|
|
|
443
|
-
This function collects data from tables associated with specific population types across
|
|
444
|
-
various wells and positions within an experiment. It uses the experiment's configuration
|
|
445
|
-
to gather metadata such as movie prefix, concentrations, cell types, antibodies, and
|
|
446
|
-
pharmaceutical agents. Users can specify which wells and positions to include in the
|
|
447
|
-
aggregation through pattern matching, and whether to include detailed position information
|
|
447
|
+
This function collects data from tables associated with specific population types across
|
|
448
|
+
various wells and positions within an experiment. It uses the experiment's configuration
|
|
449
|
+
to gather metadata such as movie prefix, concentrations, cell types, antibodies, and
|
|
450
|
+
pharmaceutical agents. Users can specify which wells and positions to include in the
|
|
451
|
+
aggregation through pattern matching, and whether to include detailed position information
|
|
448
452
|
in the output.
|
|
449
453
|
|
|
450
454
|
Parameters
|
|
@@ -458,15 +462,15 @@ def load_experiment_tables(experiment, population='targets', well_option='*',pos
|
|
|
458
462
|
position_option : str, optional
|
|
459
463
|
A pattern to specify which positions to include (default is '*', which includes all positions).
|
|
460
464
|
return_pos_info : bool, optional
|
|
461
|
-
If True, returns a tuple where the first element is the aggregated data table and the
|
|
465
|
+
If True, returns a tuple where the first element is the aggregated data table and the
|
|
462
466
|
second element is detailed position information (default is False).
|
|
463
467
|
|
|
464
468
|
Returns
|
|
465
469
|
-------
|
|
466
470
|
pandas.DataFrame or (pandas.DataFrame, pandas.DataFrame)
|
|
467
|
-
If return_pos_info is False, returns a pandas DataFrame aggregating the data from the
|
|
468
|
-
specified tables. If return_pos_info is True, returns a tuple where the first element
|
|
469
|
-
is the aggregated data table and the second element is a DataFrame with detailed position
|
|
471
|
+
If return_pos_info is False, returns a pandas DataFrame aggregating the data from the
|
|
472
|
+
specified tables. If return_pos_info is True, returns a tuple where the first element
|
|
473
|
+
is the aggregated data table and the second element is a DataFrame with detailed position
|
|
470
474
|
information.
|
|
471
475
|
|
|
472
476
|
Raises
|
|
@@ -478,50 +482,49 @@ def load_experiment_tables(experiment, population='targets', well_option='*',pos
|
|
|
478
482
|
|
|
479
483
|
Notes
|
|
480
484
|
-----
|
|
481
|
-
- This function assumes that the naming conventions and directory structure of the experiment
|
|
485
|
+
- This function assumes that the naming conventions and directory structure of the experiment
|
|
482
486
|
follow a specific format, as outlined in the experiment's configuration file.
|
|
483
|
-
- The function utilizes several helper functions to extract metadata, interpret well and
|
|
484
|
-
position patterns, and load individual position tables. Errors in these helper functions
|
|
487
|
+
- The function utilizes several helper functions to extract metadata, interpret well and
|
|
488
|
+
position patterns, and load individual position tables. Errors in these helper functions
|
|
485
489
|
may propagate up and affect the behavior of this function.
|
|
486
490
|
|
|
487
491
|
Examples
|
|
488
492
|
--------
|
|
489
493
|
>>> load_experiment_tables('/path/to/experiment', population='targets', well_option='W1', position_option='1-*')
|
|
490
494
|
# This will load and aggregate tables for the 'targets' population within well 'W1' and positions matching '1-*'.
|
|
491
|
-
|
|
492
|
-
"""
|
|
493
495
|
|
|
496
|
+
"""
|
|
494
497
|
|
|
495
498
|
config = get_config(experiment)
|
|
496
499
|
wells = get_experiment_wells(experiment)
|
|
497
500
|
|
|
498
|
-
movie_prefix = ConfigSectionMap(config,"MovieSettings")["movie_prefix"]
|
|
501
|
+
movie_prefix = ConfigSectionMap(config, "MovieSettings")["movie_prefix"]
|
|
499
502
|
concentrations = get_experiment_concentrations(experiment, dtype=float)
|
|
500
503
|
cell_types = get_experiment_cell_types(experiment)
|
|
501
504
|
antibodies = get_experiment_antibodies(experiment)
|
|
502
505
|
pharmaceutical_agents = get_experiment_pharmaceutical_agents(experiment)
|
|
503
|
-
well_labels = _extract_labels_from_config(config,len(wells))
|
|
504
|
-
|
|
506
|
+
well_labels = _extract_labels_from_config(config, len(wells))
|
|
507
|
+
|
|
505
508
|
well_indices, position_indices = interpret_wells_and_positions(experiment, well_option, position_option)
|
|
506
509
|
|
|
507
510
|
df = []
|
|
508
511
|
df_pos_info = []
|
|
509
512
|
real_well_index = 0
|
|
510
|
-
|
|
513
|
+
|
|
511
514
|
for k, well_path in enumerate(tqdm(wells[well_indices])):
|
|
512
|
-
|
|
513
|
-
any_table = False
|
|
514
|
-
|
|
515
|
+
|
|
516
|
+
any_table = False # assume no table
|
|
517
|
+
|
|
515
518
|
well_name, well_number = extract_well_name_and_number(well_path)
|
|
516
519
|
widx = well_indices[k]
|
|
517
520
|
|
|
518
521
|
well_alias = well_labels[widx]
|
|
519
|
-
|
|
522
|
+
|
|
520
523
|
well_concentration = concentrations[widx]
|
|
521
524
|
well_antibody = antibodies[widx]
|
|
522
525
|
well_cell_type = cell_types[widx]
|
|
523
526
|
well_pharmaceutical_agent = pharmaceutical_agents[widx]
|
|
524
|
-
|
|
527
|
+
|
|
525
528
|
positions = get_positions_in_well(well_path)
|
|
526
529
|
if position_indices is not None:
|
|
527
530
|
try:
|
|
@@ -531,13 +534,17 @@ def load_experiment_tables(experiment, population='targets', well_option='*',pos
|
|
|
531
534
|
continue
|
|
532
535
|
|
|
533
536
|
real_pos_index = 0
|
|
534
|
-
for pidx,pos_path in enumerate(positions):
|
|
535
|
-
|
|
537
|
+
for pidx, pos_path in enumerate(positions):
|
|
538
|
+
|
|
536
539
|
pos_name = extract_position_name(pos_path)
|
|
537
|
-
|
|
540
|
+
|
|
538
541
|
stack_path = get_position_movie_path(pos_path, prefix=movie_prefix)
|
|
539
|
-
|
|
540
|
-
|
|
542
|
+
|
|
543
|
+
if not load_pickle:
|
|
544
|
+
df_pos, table = get_position_table(pos_path, population=population, return_path=True)
|
|
545
|
+
else:
|
|
546
|
+
df_pos, table = get_position_pickle(pos_path, population=population, return_path=True)
|
|
547
|
+
|
|
541
548
|
if df_pos is not None:
|
|
542
549
|
|
|
543
550
|
df_pos['position'] = pos_path
|
|
@@ -550,25 +557,28 @@ def load_experiment_tables(experiment, population='targets', well_option='*',pos
|
|
|
550
557
|
df_pos['antibody'] = well_antibody
|
|
551
558
|
df_pos['cell_type'] = well_cell_type
|
|
552
559
|
df_pos['pharmaceutical_agent'] = well_pharmaceutical_agent
|
|
553
|
-
|
|
560
|
+
|
|
554
561
|
df.append(df_pos)
|
|
555
562
|
any_table = True
|
|
556
|
-
|
|
557
|
-
df_pos_info.append(
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
563
|
+
|
|
564
|
+
df_pos_info.append(
|
|
565
|
+
{'pos_path': pos_path, 'pos_index': real_pos_index, 'pos_name': pos_name, 'table_path': table,
|
|
566
|
+
'stack_path': stack_path,
|
|
567
|
+
'well_path': well_path, 'well_index': real_well_index, 'well_name': well_name,
|
|
568
|
+
'well_number': well_number, 'well_alias': well_alias})
|
|
569
|
+
|
|
570
|
+
real_pos_index += 1
|
|
571
|
+
|
|
562
572
|
if any_table:
|
|
563
573
|
real_well_index += 1
|
|
564
|
-
|
|
574
|
+
|
|
565
575
|
df_pos_info = pd.DataFrame(df_pos_info)
|
|
566
|
-
if len(df)>0:
|
|
576
|
+
if len(df) > 0:
|
|
567
577
|
df = pd.concat(df)
|
|
568
578
|
df = df.reset_index(drop=True)
|
|
569
579
|
else:
|
|
570
580
|
df = None
|
|
571
|
-
|
|
581
|
+
|
|
572
582
|
if return_pos_info:
|
|
573
583
|
return df, df_pos_info
|
|
574
584
|
else:
|
|
@@ -614,15 +624,15 @@ def locate_stack(position, prefix='Aligned'):
|
|
|
614
624
|
"""
|
|
615
625
|
|
|
616
626
|
if not position.endswith(os.sep):
|
|
617
|
-
position+=os.sep
|
|
627
|
+
position += os.sep
|
|
618
628
|
|
|
619
|
-
stack_path = glob(position+os.sep.join(['movie', f'{prefix}*.tif']))
|
|
620
|
-
assert len(stack_path)>0,f"No movie with prefix {prefix} found..."
|
|
621
|
-
stack = imread(stack_path[0].replace('\\','/'))
|
|
622
|
-
if stack.ndim==4:
|
|
629
|
+
stack_path = glob(position + os.sep.join(['movie', f'{prefix}*.tif']))
|
|
630
|
+
assert len(stack_path) > 0, f"No movie with prefix {prefix} found..."
|
|
631
|
+
stack = imread(stack_path[0].replace('\\', '/'), is_mmstack=False)
|
|
632
|
+
if stack.ndim == 4:
|
|
623
633
|
stack = np.moveaxis(stack, 1, -1)
|
|
624
|
-
elif stack.ndim==3:
|
|
625
|
-
stack = stack[
|
|
634
|
+
elif stack.ndim == 3:
|
|
635
|
+
stack = stack[:, :, :, np.newaxis]
|
|
626
636
|
|
|
627
637
|
return stack
|
|
628
638
|
|
|
@@ -637,7 +647,7 @@ def locate_labels(position, population='target'):
|
|
|
637
647
|
position : str
|
|
638
648
|
The position folder within the well where the stack is located.
|
|
639
649
|
population : str, optional
|
|
640
|
-
The population for which to locate the labels.
|
|
650
|
+
The population for which to locate the labels.
|
|
641
651
|
Valid options are 'target' and 'effector'.
|
|
642
652
|
The default is 'target'.
|
|
643
653
|
|
|
@@ -661,13 +671,13 @@ def locate_labels(position, population='target'):
|
|
|
661
671
|
"""
|
|
662
672
|
|
|
663
673
|
if not position.endswith(os.sep):
|
|
664
|
-
position+=os.sep
|
|
665
|
-
|
|
666
|
-
if population.lower()=="target" or population.lower()=="targets":
|
|
667
|
-
label_path = natsorted(glob(position+os.sep.join(["labels_targets", "*.tif"])))
|
|
668
|
-
elif population.lower()=="effector" or population.lower()=="effectors":
|
|
669
|
-
label_path = natsorted(glob(position+os.sep.join(["labels_effectors", "*.tif"])))
|
|
670
|
-
labels = np.array([imread(i.replace('\\','/')) for i in label_path])
|
|
674
|
+
position += os.sep
|
|
675
|
+
|
|
676
|
+
if population.lower() == "target" or population.lower() == "targets":
|
|
677
|
+
label_path = natsorted(glob(position + os.sep.join(["labels_targets", "*.tif"])))
|
|
678
|
+
elif population.lower() == "effector" or population.lower() == "effectors":
|
|
679
|
+
label_path = natsorted(glob(position + os.sep.join(["labels_effectors", "*.tif"])))
|
|
680
|
+
labels = np.array([imread(i.replace('\\', '/')) for i in label_path])
|
|
671
681
|
|
|
672
682
|
return labels
|
|
673
683
|
|
|
@@ -710,20 +720,20 @@ def locate_stack_and_labels(position, prefix='Aligned', population="target"):
|
|
|
710
720
|
--------
|
|
711
721
|
>>> stack, labels = locate_stack_and_labels(position, prefix='Aligned', population="target")
|
|
712
722
|
# Locate and load the stack and segmentation labels for further processing.
|
|
713
|
-
|
|
723
|
+
|
|
714
724
|
"""
|
|
715
725
|
|
|
716
|
-
position = position.replace('\\','/')
|
|
726
|
+
position = position.replace('\\', '/')
|
|
717
727
|
labels = locate_labels(position, population=population)
|
|
718
728
|
stack = locate_stack(position, prefix=prefix)
|
|
719
|
-
assert len(stack)==len(
|
|
729
|
+
assert len(stack) == len(
|
|
730
|
+
labels), f"The shape of the stack {stack.shape} does not match with the shape of the labels {labels.shape}"
|
|
720
731
|
|
|
721
|
-
return stack,labels
|
|
732
|
+
return stack, labels
|
|
722
733
|
|
|
723
734
|
def load_tracking_data(position, prefix="Aligned", population="target"):
|
|
724
|
-
|
|
725
735
|
"""
|
|
726
|
-
|
|
736
|
+
|
|
727
737
|
Load the tracking data, labels, and stack for a given position and population.
|
|
728
738
|
|
|
729
739
|
Parameters
|
|
@@ -758,15 +768,15 @@ def load_tracking_data(position, prefix="Aligned", population="target"):
|
|
|
758
768
|
|
|
759
769
|
"""
|
|
760
770
|
|
|
761
|
-
position = position.replace('\\','/')
|
|
762
|
-
if population.lower()=="target" or population.lower()=="targets":
|
|
763
|
-
trajectories = pd.read_csv(position+os.sep.join(['output', 'tables', 'trajectories_targets.csv']))
|
|
764
|
-
elif population.lower()=="effector" or population.lower()=="effectors":
|
|
765
|
-
trajectories = pd.read_csv(position+os.sep.join(['output', 'tables', 'trajectories_effectors.csv']))
|
|
771
|
+
position = position.replace('\\', '/')
|
|
772
|
+
if population.lower() == "target" or population.lower() == "targets":
|
|
773
|
+
trajectories = pd.read_csv(position + os.sep.join(['output', 'tables', 'trajectories_targets.csv']))
|
|
774
|
+
elif population.lower() == "effector" or population.lower() == "effectors":
|
|
775
|
+
trajectories = pd.read_csv(position + os.sep.join(['output', 'tables', 'trajectories_effectors.csv']))
|
|
766
776
|
|
|
767
|
-
stack,labels = locate_stack_and_labels(position, prefix=prefix, population=population)
|
|
777
|
+
stack, labels = locate_stack_and_labels(position, prefix=prefix, population=population)
|
|
768
778
|
|
|
769
|
-
return trajectories,labels,stack
|
|
779
|
+
return trajectories, labels, stack
|
|
770
780
|
|
|
771
781
|
|
|
772
782
|
def auto_load_number_of_frames(stack_path):
|
|
@@ -797,10 +807,14 @@ def auto_load_number_of_frames(stack_path):
|
|
|
797
807
|
--------
|
|
798
808
|
>>> len_movie = auto_load_number_of_frames(stack_path)
|
|
799
809
|
# Automatically estimate the number of frames in the stack.
|
|
800
|
-
|
|
810
|
+
|
|
801
811
|
"""
|
|
802
812
|
|
|
803
813
|
# Try to estimate automatically # frames
|
|
814
|
+
|
|
815
|
+
if stack_path is None:
|
|
816
|
+
return None
|
|
817
|
+
|
|
804
818
|
stack_path = stack_path.replace('\\','/')
|
|
805
819
|
|
|
806
820
|
with TiffFile(stack_path) as tif:
|
|
@@ -816,7 +830,7 @@ def auto_load_number_of_frames(stack_path):
|
|
|
816
830
|
try:
|
|
817
831
|
# Try nframes
|
|
818
832
|
nslices = int(attr[np.argmax([s.startswith("frames") for s in attr])].split("=")[-1])
|
|
819
|
-
if nslices>1:
|
|
833
|
+
if nslices > 1:
|
|
820
834
|
len_movie = nslices
|
|
821
835
|
print(f"Auto-detected movie length movie: {len_movie}")
|
|
822
836
|
else:
|
|
@@ -836,27 +850,35 @@ def auto_load_number_of_frames(stack_path):
|
|
|
836
850
|
del img_desc;
|
|
837
851
|
except:
|
|
838
852
|
pass
|
|
853
|
+
|
|
854
|
+
if 'len_movie' not in locals():
|
|
855
|
+
stack = imread(stack_path)
|
|
856
|
+
len_movie = len(stack)
|
|
857
|
+
del stack
|
|
839
858
|
gc.collect()
|
|
840
859
|
|
|
860
|
+
print(f'Automatically detected stack length: {len_movie}...')
|
|
861
|
+
|
|
841
862
|
return len_movie if 'len_movie' in locals() else None
|
|
842
863
|
|
|
864
|
+
|
|
843
865
|
def parse_isotropic_radii(string):
|
|
844
866
|
sections = re.split(',| ', string)
|
|
845
867
|
radii = []
|
|
846
|
-
for k,s in enumerate(sections):
|
|
868
|
+
for k, s in enumerate(sections):
|
|
847
869
|
if s.isdigit():
|
|
848
870
|
radii.append(int(s))
|
|
849
871
|
if '[' in s:
|
|
850
|
-
ring = [int(s.replace('[','')), int(sections[k+1].replace(']',''))]
|
|
872
|
+
ring = [int(s.replace('[', '')), int(sections[k + 1].replace(']', ''))]
|
|
851
873
|
radii.append(ring)
|
|
852
874
|
else:
|
|
853
875
|
pass
|
|
854
876
|
return radii
|
|
855
877
|
|
|
856
|
-
def get_tracking_configs_list(return_path=False):
|
|
857
878
|
|
|
879
|
+
def get_tracking_configs_list(return_path=False):
|
|
858
880
|
"""
|
|
859
|
-
|
|
881
|
+
|
|
860
882
|
Retrieve a list of available tracking configurations.
|
|
861
883
|
|
|
862
884
|
Parameters
|
|
@@ -887,26 +909,30 @@ def get_tracking_configs_list(return_path=False):
|
|
|
887
909
|
|
|
888
910
|
"""
|
|
889
911
|
|
|
890
|
-
modelpath = os.sep.join(
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
available_models =
|
|
894
|
-
|
|
912
|
+
modelpath = os.sep.join(
|
|
913
|
+
[os.path.split(os.path.dirname(os.path.realpath(__file__)))[0], "celldetective", "models", "tracking_configs",
|
|
914
|
+
os.sep])
|
|
915
|
+
available_models = glob(modelpath + '*.json')
|
|
916
|
+
available_models = [m.replace('\\', '/').split('/')[-1] for m in available_models]
|
|
917
|
+
available_models = [m.replace('\\', '/').split('.')[0] for m in available_models]
|
|
895
918
|
|
|
896
919
|
if not return_path:
|
|
897
920
|
return available_models
|
|
898
921
|
else:
|
|
899
922
|
return available_models, modelpath
|
|
900
923
|
|
|
924
|
+
|
|
901
925
|
def interpret_tracking_configuration(config):
|
|
902
926
|
|
|
903
927
|
if isinstance(config, str):
|
|
904
928
|
if os.path.exists(config):
|
|
905
929
|
return config
|
|
906
930
|
else:
|
|
907
|
-
modelpath = os.sep.join(
|
|
908
|
-
|
|
909
|
-
|
|
931
|
+
modelpath = os.sep.join(
|
|
932
|
+
[os.path.split(os.path.dirname(os.path.realpath(__file__)))[0], "celldetective", "models",
|
|
933
|
+
"tracking_configs", os.sep])
|
|
934
|
+
if os.path.exists(modelpath + config + '.json'):
|
|
935
|
+
return modelpath + config + '.json'
|
|
910
936
|
else:
|
|
911
937
|
config = cell_config()
|
|
912
938
|
elif config is None:
|
|
@@ -914,10 +940,11 @@ def interpret_tracking_configuration(config):
|
|
|
914
940
|
|
|
915
941
|
return config
|
|
916
942
|
|
|
943
|
+
|
|
917
944
|
def get_signal_models_list(return_path=False):
|
|
918
945
|
|
|
919
946
|
"""
|
|
920
|
-
|
|
947
|
+
|
|
921
948
|
Retrieve a list of available signal detection models.
|
|
922
949
|
|
|
923
950
|
Parameters
|
|
@@ -948,11 +975,13 @@ def get_signal_models_list(return_path=False):
|
|
|
948
975
|
|
|
949
976
|
"""
|
|
950
977
|
|
|
951
|
-
modelpath = os.sep.join(
|
|
952
|
-
|
|
978
|
+
modelpath = os.sep.join(
|
|
979
|
+
[os.path.split(os.path.dirname(os.path.realpath(__file__)))[0], "celldetective", "models", "signal_detection",
|
|
980
|
+
os.sep])
|
|
981
|
+
repository_models = get_zenodo_files(cat=os.sep.join(["models", "signal_detection"]))
|
|
953
982
|
|
|
954
|
-
available_models = glob(modelpath+f'*{os.sep}')
|
|
955
|
-
available_models = [m.replace('\\','/').split('/')[-2] for m in available_models]
|
|
983
|
+
available_models = glob(modelpath + f'*{os.sep}')
|
|
984
|
+
available_models = [m.replace('\\', '/').split('/')[-2] for m in available_models]
|
|
956
985
|
for rm in repository_models:
|
|
957
986
|
if rm not in available_models:
|
|
958
987
|
available_models.append(rm)
|
|
@@ -962,21 +991,72 @@ def get_signal_models_list(return_path=False):
|
|
|
962
991
|
else:
|
|
963
992
|
return available_models, modelpath
|
|
964
993
|
|
|
994
|
+
def get_pair_signal_models_list(return_path=False):
|
|
995
|
+
"""
|
|
965
996
|
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
997
|
+
Retrieve a list of available signal detection models.
|
|
998
|
+
|
|
999
|
+
Parameters
|
|
1000
|
+
----------
|
|
1001
|
+
return_path : bool, optional
|
|
1002
|
+
If True, also returns the path to the models. Default is False.
|
|
1003
|
+
|
|
1004
|
+
Returns
|
|
1005
|
+
-------
|
|
1006
|
+
list or tuple
|
|
1007
|
+
If return_path is False, returns a list of available signal detection models.
|
|
1008
|
+
If return_path is True, returns a tuple containing the list of models and the path to the models.
|
|
1009
|
+
|
|
1010
|
+
Notes
|
|
1011
|
+
-----
|
|
1012
|
+
This function retrieves the list of available signal detection models by searching for model directories
|
|
1013
|
+
in the predefined model path. The model path is derived from the parent directory of the current script
|
|
1014
|
+
location and the path to the model directory. By default, it returns only the names of the models.
|
|
1015
|
+
If return_path is set to True, it also returns the path to the models.
|
|
1016
|
+
|
|
1017
|
+
Examples
|
|
1018
|
+
--------
|
|
1019
|
+
>>> models = get_signal_models_list()
|
|
1020
|
+
# Retrieve a list of available signal detection models.
|
|
1021
|
+
|
|
1022
|
+
>>> models, path = get_signal_models_list(return_path=True)
|
|
1023
|
+
# Retrieve a list of available signal detection models and the path to the models.
|
|
1024
|
+
|
|
1025
|
+
"""
|
|
1026
|
+
|
|
1027
|
+
modelpath = os.sep.join(
|
|
1028
|
+
[os.path.split(os.path.dirname(os.path.realpath(__file__)))[0], "celldetective", "models", "pair_signal_detection",
|
|
1029
|
+
os.sep])
|
|
1030
|
+
#repository_models = get_zenodo_files(cat=os.sep.join(["models", "pair_signal_detection"]))
|
|
1031
|
+
|
|
1032
|
+
available_models = glob(modelpath + f'*{os.sep}')
|
|
1033
|
+
available_models = [m.replace('\\', '/').split('/')[-2] for m in available_models]
|
|
1034
|
+
#for rm in repository_models:
|
|
1035
|
+
# if rm not in available_models:
|
|
1036
|
+
# available_models.append(rm)
|
|
1037
|
+
|
|
1038
|
+
if not return_path:
|
|
1039
|
+
return available_models
|
|
1040
|
+
else:
|
|
1041
|
+
return available_models, modelpath
|
|
1042
|
+
|
|
1043
|
+
|
|
1044
|
+
def locate_signal_model(name, path=None, pairs=False):
|
|
1045
|
+
|
|
1046
|
+
main_dir = os.sep.join([os.path.split(os.path.dirname(os.path.realpath(__file__)))[0], "celldetective"])
|
|
969
1047
|
modelpath = os.sep.join([main_dir, "models", "signal_detection", os.sep])
|
|
1048
|
+
if pairs:
|
|
1049
|
+
modelpath = os.sep.join([main_dir, "models", "pair_signal_detection", os.sep])
|
|
970
1050
|
print(f'Looking for {name} in {modelpath}')
|
|
971
|
-
models = glob(modelpath+f'*{os.sep}')
|
|
1051
|
+
models = glob(modelpath + f'*{os.sep}')
|
|
972
1052
|
if path is not None:
|
|
973
1053
|
if not path.endswith(os.sep):
|
|
974
1054
|
path += os.sep
|
|
975
|
-
models += glob(path+f'*{os.sep}')
|
|
1055
|
+
models += glob(path + f'*{os.sep}')
|
|
976
1056
|
|
|
977
|
-
match=None
|
|
1057
|
+
match = None
|
|
978
1058
|
for m in models:
|
|
979
|
-
if name==m.replace('\\',os.sep).split(os.sep)[-2]:
|
|
1059
|
+
if name == m.replace('\\', os.sep).split(os.sep)[-2]:
|
|
980
1060
|
match = m
|
|
981
1061
|
return match
|
|
982
1062
|
# else no match, try zenodo
|
|
@@ -985,9 +1065,18 @@ def locate_signal_model(name, path=None):
|
|
|
985
1065
|
index = files.index(name)
|
|
986
1066
|
cat = categories[index]
|
|
987
1067
|
download_zenodo_file(name, os.sep.join([main_dir, cat]))
|
|
988
|
-
match = os.sep.join([main_dir, cat, name])+os.sep
|
|
1068
|
+
match = os.sep.join([main_dir, cat, name]) + os.sep
|
|
989
1069
|
return match
|
|
990
1070
|
|
|
1071
|
+
def locate_pair_signal_model(name, path=None):
|
|
1072
|
+
main_dir = os.sep.join([os.path.split(os.path.dirname(os.path.realpath(__file__)))[0], "celldetective"])
|
|
1073
|
+
modelpath = os.sep.join([main_dir, "models", "pair_signal_detection", os.sep])
|
|
1074
|
+
print(f'Looking for {name} in {modelpath}')
|
|
1075
|
+
models = glob(modelpath + f'*{os.sep}')
|
|
1076
|
+
if path is not None:
|
|
1077
|
+
if not path.endswith(os.sep):
|
|
1078
|
+
path += os.sep
|
|
1079
|
+
models += glob(path + f'*{os.sep}')
|
|
991
1080
|
|
|
992
1081
|
def relabel_segmentation(labels, data, properties, column_labels={'track': "track", 'frame': 'frame', 'y': 'y', 'x': 'x', 'label': 'class_id'}, threads=1):
|
|
993
1082
|
|
|
@@ -1029,7 +1118,11 @@ def relabel_segmentation(labels, data, properties, column_labels={'track': "trac
|
|
|
1029
1118
|
|
|
1030
1119
|
|
|
1031
1120
|
n_threads = threads
|
|
1032
|
-
|
|
1121
|
+
if data.shape[1]==4:
|
|
1122
|
+
df = pd.DataFrame(data,columns=[column_labels['track'],column_labels['frame'],column_labels['y'],column_labels['x']])
|
|
1123
|
+
else:
|
|
1124
|
+
df = pd.DataFrame(data,columns=[column_labels['track'],column_labels['frame'],'z', column_labels['y'],column_labels['x']])
|
|
1125
|
+
df = df.drop(columns=['z'])
|
|
1033
1126
|
df = df.merge(pd.DataFrame(properties),left_index=True, right_index=True)
|
|
1034
1127
|
df = df.sort_values(by=[column_labels['track'],column_labels['frame']])
|
|
1035
1128
|
|
|
@@ -1039,15 +1132,15 @@ def relabel_segmentation(labels, data, properties, column_labels={'track': "trac
|
|
|
1039
1132
|
|
|
1040
1133
|
for t in tqdm(indices):
|
|
1041
1134
|
f = int(t)
|
|
1042
|
-
tracks_at_t = df.loc[df[column_labels['frame']]==f, column_labels['track']].to_numpy()
|
|
1043
|
-
identities = df.loc[df[column_labels['frame']]==f, column_labels['label']].to_numpy()
|
|
1135
|
+
tracks_at_t = df.loc[df[column_labels['frame']] == f, column_labels['track']].to_numpy()
|
|
1136
|
+
identities = df.loc[df[column_labels['frame']] == f, column_labels['label']].to_numpy()
|
|
1044
1137
|
|
|
1045
|
-
tracks_at_t = tracks_at_t[identities==identities]
|
|
1046
|
-
identities = identities[identities==identities]
|
|
1138
|
+
tracks_at_t = tracks_at_t[identities == identities]
|
|
1139
|
+
identities = identities[identities == identities]
|
|
1047
1140
|
|
|
1048
1141
|
for k in range(len(identities)):
|
|
1049
|
-
loc_i,loc_j = np.where(labels[f]==identities[k])
|
|
1050
|
-
new_labels[f,loc_i,loc_j] = int(tracks_at_t[k])
|
|
1142
|
+
loc_i, loc_j = np.where(labels[f] == identities[k])
|
|
1143
|
+
new_labels[f, loc_i, loc_j] = int(tracks_at_t[k])
|
|
1051
1144
|
|
|
1052
1145
|
# Multithreading
|
|
1053
1146
|
indices = list(df[column_labels['frame']].unique())
|
|
@@ -1147,11 +1240,13 @@ def control_tracking_btrack(position, prefix="Aligned", population="target", rel
|
|
|
1147
1240
|
|
|
1148
1241
|
"""
|
|
1149
1242
|
|
|
1150
|
-
data,properties,graph,labels,stack = load_napari_data(position, prefix=prefix, population=population)
|
|
1151
|
-
view_on_napari_btrack(data,properties,graph,labels=labels, stack=stack, relabel=relabel,
|
|
1243
|
+
data, properties, graph, labels, stack = load_napari_data(position, prefix=prefix, population=population)
|
|
1244
|
+
view_on_napari_btrack(data, properties, graph, labels=labels, stack=stack, relabel=relabel,
|
|
1245
|
+
flush_memory=flush_memory, threads=threads)
|
|
1152
1246
|
|
|
1153
|
-
|
|
1154
|
-
|
|
1247
|
+
|
|
1248
|
+
def view_on_napari_btrack(data, properties, graph, stack=None, labels=None, relabel=True, flush_memory=True,
|
|
1249
|
+
position=None, threads=1):
|
|
1155
1250
|
"""
|
|
1156
1251
|
|
|
1157
1252
|
Visualize btrack data, including stack, labels, points, and tracks, using the napari viewer.
|
|
@@ -1184,20 +1279,28 @@ def view_on_napari_btrack(data,properties,graph,stack=None,labels=None,relabel=T
|
|
|
1184
1279
|
|
|
1185
1280
|
"""
|
|
1186
1281
|
|
|
1187
|
-
if (labels is not None)*relabel:
|
|
1282
|
+
if (labels is not None) * relabel:
|
|
1188
1283
|
print('Relabeling the cell masks with the track ID.')
|
|
1189
1284
|
labels = relabel_segmentation(labels, data, properties, threads=threads)
|
|
1190
1285
|
|
|
1191
|
-
|
|
1286
|
+
if data.shape[1]==4:
|
|
1287
|
+
vertices = data[:, 1:]
|
|
1288
|
+
else:
|
|
1289
|
+
vertices = data[:, 2:]
|
|
1192
1290
|
viewer = napari.Viewer()
|
|
1193
1291
|
if stack is not None:
|
|
1194
|
-
|
|
1292
|
+
print(f'{stack.shape=}')
|
|
1293
|
+
viewer.add_image(stack, channel_axis=-1, colormap=["gray"] * stack.shape[-1])
|
|
1195
1294
|
if labels is not None:
|
|
1196
|
-
viewer.add_labels(labels, name='segmentation',opacity=0.4)
|
|
1295
|
+
viewer.add_labels(labels, name='segmentation', opacity=0.4)
|
|
1197
1296
|
viewer.add_points(vertices, size=4, name='points', opacity=0.3)
|
|
1198
|
-
|
|
1297
|
+
if data.shape[1]==4:
|
|
1298
|
+
viewer.add_tracks(data, properties=properties, graph=graph, name='tracks')
|
|
1299
|
+
else:
|
|
1300
|
+
print(data)
|
|
1301
|
+
viewer.add_tracks(data[:,[0,1,3,4]], properties=properties, graph=graph, name='tracks')
|
|
1199
1302
|
viewer.show(block=True)
|
|
1200
|
-
|
|
1303
|
+
|
|
1201
1304
|
if flush_memory:
|
|
1202
1305
|
# temporary fix for slight napari memory leak
|
|
1203
1306
|
for i in range(10000):
|
|
@@ -1211,8 +1314,8 @@ def view_on_napari_btrack(data,properties,graph,stack=None,labels=None,relabel=T
|
|
|
1211
1314
|
del labels
|
|
1212
1315
|
gc.collect()
|
|
1213
1316
|
|
|
1214
|
-
def load_napari_data(position, prefix="Aligned", population="target", return_stack=True):
|
|
1215
1317
|
|
|
1318
|
+
def load_napari_data(position, prefix="Aligned", population="target", return_stack=True):
|
|
1216
1319
|
"""
|
|
1217
1320
|
Load the necessary data for visualization in napari.
|
|
1218
1321
|
|
|
@@ -1234,7 +1337,7 @@ def load_napari_data(position, prefix="Aligned", population="target", return_sta
|
|
|
1234
1337
|
--------
|
|
1235
1338
|
>>> data, properties, graph, labels, stack = load_napari_data("path/to/position")
|
|
1236
1339
|
# Load the necessary data for visualization of target trajectories.
|
|
1237
|
-
|
|
1340
|
+
|
|
1238
1341
|
"""
|
|
1239
1342
|
position = position.replace('\\','/')
|
|
1240
1343
|
if population.lower()=="target" or population.lower()=="targets":
|
|
@@ -1256,14 +1359,16 @@ def load_napari_data(position, prefix="Aligned", population="target", return_sta
|
|
|
1256
1359
|
properties = None
|
|
1257
1360
|
graph = None
|
|
1258
1361
|
if return_stack:
|
|
1259
|
-
stack,labels = locate_stack_and_labels(position, prefix=prefix, population=population)
|
|
1362
|
+
stack, labels = locate_stack_and_labels(position, prefix=prefix, population=population)
|
|
1260
1363
|
else:
|
|
1261
|
-
labels=locate_labels(position,population=population)
|
|
1364
|
+
labels = locate_labels(position, population=population)
|
|
1262
1365
|
stack = None
|
|
1263
|
-
return data,properties,graph,labels,stack
|
|
1366
|
+
return data, properties, graph, labels, stack
|
|
1367
|
+
|
|
1264
1368
|
|
|
1265
1369
|
from skimage.measure import label
|
|
1266
1370
|
|
|
1371
|
+
|
|
1267
1372
|
def auto_correct_masks(masks):
|
|
1268
1373
|
|
|
1269
1374
|
"""
|
|
@@ -1295,25 +1400,25 @@ def auto_correct_masks(masks):
|
|
|
1295
1400
|
|
|
1296
1401
|
"""
|
|
1297
1402
|
|
|
1298
|
-
props = pd.DataFrame(regionprops_table(masks,properties=('label','area','area_bbox')))
|
|
1403
|
+
props = pd.DataFrame(regionprops_table(masks, properties=('label', 'area', 'area_bbox')))
|
|
1299
1404
|
max_lbl = props['label'].max()
|
|
1300
1405
|
corrected_lbl = masks.copy().astype(int)
|
|
1301
1406
|
|
|
1302
1407
|
for cell in props['label'].unique():
|
|
1303
1408
|
|
|
1304
|
-
bbox_area = props.loc[props['label']==cell, 'area_bbox'].values
|
|
1305
|
-
area = props.loc[props['label']==cell, 'area'].values
|
|
1409
|
+
bbox_area = props.loc[props['label'] == cell, 'area_bbox'].values
|
|
1410
|
+
area = props.loc[props['label'] == cell, 'area'].values
|
|
1306
1411
|
|
|
1307
|
-
if bbox_area > 1.75*area:
|
|
1412
|
+
if bbox_area > 1.75 * area: # condition for anomaly
|
|
1308
1413
|
|
|
1309
|
-
lbl = masks==cell
|
|
1414
|
+
lbl = masks == cell
|
|
1310
1415
|
lbl = lbl.astype(int)
|
|
1311
1416
|
|
|
1312
|
-
relabelled = label(lbl,connectivity=2)
|
|
1417
|
+
relabelled = label(lbl, connectivity=2)
|
|
1313
1418
|
relabelled += max_lbl
|
|
1314
|
-
relabelled[np.where(lbl==0)] = 0
|
|
1419
|
+
relabelled[np.where(lbl == 0)] = 0
|
|
1315
1420
|
|
|
1316
|
-
corrected_lbl[np.where(relabelled != 0)] = relabelled[np.where(relabelled!=0)]
|
|
1421
|
+
corrected_lbl[np.where(relabelled != 0)] = relabelled[np.where(relabelled != 0)]
|
|
1317
1422
|
|
|
1318
1423
|
max_lbl = np.amax(corrected_lbl)
|
|
1319
1424
|
|
|
@@ -1324,7 +1429,7 @@ def auto_correct_masks(masks):
|
|
|
1324
1429
|
def control_segmentation_napari(position, prefix='Aligned', population="target", flush_memory=False):
|
|
1325
1430
|
|
|
1326
1431
|
"""
|
|
1327
|
-
|
|
1432
|
+
|
|
1328
1433
|
Control the visualization of segmentation labels using the napari viewer.
|
|
1329
1434
|
|
|
1330
1435
|
Parameters
|
|
@@ -1350,26 +1455,34 @@ def control_segmentation_napari(position, prefix='Aligned', population="target",
|
|
|
1350
1455
|
|
|
1351
1456
|
def export_labels():
|
|
1352
1457
|
labels_layer = viewer.layers['segmentation'].data
|
|
1353
|
-
for t,im in enumerate(tqdm(labels_layer)):
|
|
1354
|
-
|
|
1458
|
+
for t, im in enumerate(tqdm(labels_layer)):
|
|
1459
|
+
|
|
1355
1460
|
try:
|
|
1356
1461
|
im = auto_correct_masks(im)
|
|
1357
1462
|
except Exception as e:
|
|
1358
1463
|
print(e)
|
|
1359
1464
|
|
|
1360
|
-
save_tiff_imagej_compatible(output_folder+f"{str(t).zfill(4)}.tif", im.astype(np.int16), axes='YX')
|
|
1465
|
+
save_tiff_imagej_compatible(output_folder + f"{str(t).zfill(4)}.tif", im.astype(np.int16), axes='YX')
|
|
1361
1466
|
print("The labels have been successfully rewritten.")
|
|
1362
1467
|
|
|
1363
1468
|
def export_annotation():
|
|
1364
|
-
|
|
1469
|
+
|
|
1365
1470
|
# Locate experiment config
|
|
1366
1471
|
parent1 = Path(position).parent
|
|
1367
1472
|
expfolder = parent1.parent
|
|
1368
|
-
config = PurePath(expfolder,Path("config.ini"))
|
|
1473
|
+
config = PurePath(expfolder, Path("config.ini"))
|
|
1369
1474
|
expfolder = str(expfolder)
|
|
1370
1475
|
exp_name = os.path.split(expfolder)[-1]
|
|
1371
1476
|
print(exp_name)
|
|
1372
1477
|
|
|
1478
|
+
wells = get_experiment_wells(expfolder)
|
|
1479
|
+
well_idx = list(wells).index(str(parent1)+os.sep)
|
|
1480
|
+
ab = get_experiment_antibodies(expfolder)[well_idx]
|
|
1481
|
+
conc = get_experiment_concentrations(expfolder)[well_idx]
|
|
1482
|
+
ct = get_experiment_cell_types(expfolder)[well_idx]
|
|
1483
|
+
pa = get_experiment_pharmaceutical_agents(expfolder)[well_idx]
|
|
1484
|
+
|
|
1485
|
+
|
|
1373
1486
|
spatial_calibration = float(ConfigSectionMap(config,"MovieSettings")["pxtoum"])
|
|
1374
1487
|
channel_names, channel_indices = extract_experiment_channels(config)
|
|
1375
1488
|
|
|
@@ -1379,7 +1492,7 @@ def control_segmentation_napari(position, prefix='Aligned', population="target",
|
|
|
1379
1492
|
|
|
1380
1493
|
print('exporting!')
|
|
1381
1494
|
t = viewer.dims.current_step[0]
|
|
1382
|
-
labels_layer = viewer.layers['segmentation'].data[t]
|
|
1495
|
+
labels_layer = viewer.layers['segmentation'].data[t] # at current time
|
|
1383
1496
|
|
|
1384
1497
|
try:
|
|
1385
1498
|
labels_layer = auto_correct_masks(labels_layer)
|
|
@@ -1387,61 +1500,61 @@ def control_segmentation_napari(position, prefix='Aligned', population="target",
|
|
|
1387
1500
|
print(e)
|
|
1388
1501
|
|
|
1389
1502
|
fov_export = True
|
|
1390
|
-
|
|
1503
|
+
|
|
1391
1504
|
if "Shapes" in viewer.layers:
|
|
1392
1505
|
squares = viewer.layers['Shapes'].data
|
|
1393
|
-
test_in_frame = np.array([squares[i][0,0]==t and len(squares[i])==4 for i in range(len(squares))])
|
|
1506
|
+
test_in_frame = np.array([squares[i][0, 0] == t and len(squares[i]) == 4 for i in range(len(squares))])
|
|
1394
1507
|
squares = np.array(squares)
|
|
1395
1508
|
squares = squares[test_in_frame]
|
|
1396
1509
|
nbr_squares = len(squares)
|
|
1397
1510
|
print(f"Found {nbr_squares} ROIS")
|
|
1398
|
-
if nbr_squares>0:
|
|
1511
|
+
if nbr_squares > 0:
|
|
1399
1512
|
# deactivate field of view mode
|
|
1400
1513
|
fov_export = False
|
|
1401
1514
|
|
|
1402
|
-
for k,sq in enumerate(squares):
|
|
1515
|
+
for k, sq in enumerate(squares):
|
|
1403
1516
|
print(f"ROI: {sq}")
|
|
1404
|
-
xmin = int(sq[0,1])
|
|
1405
|
-
xmax = int(sq[2,1])
|
|
1406
|
-
if xmax<xmin:
|
|
1407
|
-
xmax,xmin = xmin,xmax
|
|
1408
|
-
ymin = int(sq[0,2])
|
|
1409
|
-
ymax = int(sq[1,2])
|
|
1410
|
-
if ymax<ymin:
|
|
1411
|
-
ymax,ymin = ymin,ymax
|
|
1517
|
+
xmin = int(sq[0, 1])
|
|
1518
|
+
xmax = int(sq[2, 1])
|
|
1519
|
+
if xmax < xmin:
|
|
1520
|
+
xmax, xmin = xmin, xmax
|
|
1521
|
+
ymin = int(sq[0, 2])
|
|
1522
|
+
ymax = int(sq[1, 2])
|
|
1523
|
+
if ymax < ymin:
|
|
1524
|
+
ymax, ymin = ymin, ymax
|
|
1412
1525
|
print(f"{xmin=};{xmax=};{ymin=};{ymax=}")
|
|
1413
|
-
frame = viewer.layers['Image'].data[t][xmin:xmax,ymin:ymax]
|
|
1526
|
+
frame = viewer.layers['Image'].data[t][xmin:xmax, ymin:ymax]
|
|
1414
1527
|
if frame.shape[1] < 256 or frame.shape[0] < 256:
|
|
1415
1528
|
print("crop too small!")
|
|
1416
1529
|
continue
|
|
1417
1530
|
multichannel = [frame]
|
|
1418
|
-
for i in range(len(channel_indices)-1):
|
|
1531
|
+
for i in range(len(channel_indices) - 1):
|
|
1419
1532
|
try:
|
|
1420
|
-
frame = viewer.layers[f'Image [{i+1}]'].data[t][xmin:xmax,ymin:ymax]
|
|
1533
|
+
frame = viewer.layers[f'Image [{i + 1}]'].data[t][xmin:xmax, ymin:ymax]
|
|
1421
1534
|
multichannel.append(frame)
|
|
1422
1535
|
except:
|
|
1423
1536
|
pass
|
|
1424
1537
|
multichannel = np.array(multichannel)
|
|
1425
1538
|
save_tiff_imagej_compatible(annotation_folder + f"{exp_name}_{position.split(os.sep)[-2]}_{str(t).zfill(4)}_roi_{xmin}_{xmax}_{ymin}_{ymax}_labelled.tif", labels_layer[xmin:xmax,ymin:ymax].astype(np.int16), axes='YX')
|
|
1426
1539
|
save_tiff_imagej_compatible(annotation_folder + f"{exp_name}_{position.split(os.sep)[-2]}_{str(t).zfill(4)}_roi_{xmin}_{xmax}_{ymin}_{ymax}.tif", multichannel, axes='CYX')
|
|
1427
|
-
info = {"spatial_calibration": spatial_calibration, "channels": list(channel_names)}
|
|
1540
|
+
info = {"spatial_calibration": spatial_calibration, "channels": list(channel_names), 'cell_type': ct, 'antibody': ab, 'concentration': conc, 'pharmaceutical_agent': pa}
|
|
1428
1541
|
info_name = annotation_folder + f"{exp_name}_{position.split(os.sep)[-2]}_{str(t).zfill(4)}_roi_{xmin}_{xmax}_{ymin}_{ymax}.json"
|
|
1429
1542
|
with open(info_name, 'w') as f:
|
|
1430
1543
|
json.dump(info, f, indent=4)
|
|
1431
|
-
|
|
1544
|
+
|
|
1432
1545
|
if fov_export:
|
|
1433
1546
|
frame = viewer.layers['Image'].data[t]
|
|
1434
1547
|
multichannel = [frame]
|
|
1435
|
-
for i in range(len(channel_indices)-1):
|
|
1548
|
+
for i in range(len(channel_indices) - 1):
|
|
1436
1549
|
try:
|
|
1437
|
-
frame = viewer.layers[f'Image [{i+1}]'].data[t]
|
|
1550
|
+
frame = viewer.layers[f'Image [{i + 1}]'].data[t]
|
|
1438
1551
|
multichannel.append(frame)
|
|
1439
1552
|
except:
|
|
1440
1553
|
pass
|
|
1441
1554
|
multichannel = np.array(multichannel)
|
|
1442
1555
|
save_tiff_imagej_compatible(annotation_folder + f"{exp_name}_{position.split(os.sep)[-2]}_{str(t).zfill(4)}_labelled.tif", labels_layer, axes='YX')
|
|
1443
1556
|
save_tiff_imagej_compatible(annotation_folder + f"{exp_name}_{position.split(os.sep)[-2]}_{str(t).zfill(4)}.tif", multichannel, axes='CYX')
|
|
1444
|
-
info = {"spatial_calibration": spatial_calibration, "channels": list(channel_names)}
|
|
1557
|
+
info = {"spatial_calibration": spatial_calibration, "channels": list(channel_names), 'cell_type': ct, 'antibody': ab, 'concentration': conc, 'pharmaceutical_agent': pa}
|
|
1445
1558
|
info_name = annotation_folder + f"{exp_name}_{position.split(os.sep)[-2]}_{str(t).zfill(4)}.json"
|
|
1446
1559
|
with open(info_name, 'w') as f:
|
|
1447
1560
|
json.dump(info, f, indent=4)
|
|
@@ -1455,15 +1568,15 @@ def control_segmentation_napari(position, prefix='Aligned', population="target",
|
|
|
1455
1568
|
def export_widget():
|
|
1456
1569
|
return export_annotation()
|
|
1457
1570
|
|
|
1458
|
-
stack,labels = locate_stack_and_labels(position, prefix=prefix, population=population)
|
|
1571
|
+
stack, labels = locate_stack_and_labels(position, prefix=prefix, population=population)
|
|
1459
1572
|
|
|
1460
1573
|
if not population.endswith('s'):
|
|
1461
|
-
population+='s'
|
|
1462
|
-
output_folder = position+f'labels_{population}{os.sep}'
|
|
1574
|
+
population += 's'
|
|
1575
|
+
output_folder = position + f'labels_{population}{os.sep}'
|
|
1463
1576
|
|
|
1464
1577
|
viewer = napari.Viewer()
|
|
1465
|
-
viewer.add_image(stack,channel_axis=-1,colormap=["gray"]*stack.shape[-1])
|
|
1466
|
-
viewer.add_labels(labels.astype(int), name='segmentation',opacity=0.4)
|
|
1578
|
+
viewer.add_image(stack, channel_axis=-1, colormap=["gray"] * stack.shape[-1])
|
|
1579
|
+
viewer.add_labels(labels.astype(int), name='segmentation', opacity=0.4)
|
|
1467
1580
|
viewer.window.add_dock_widget(save_widget, area='right')
|
|
1468
1581
|
viewer.window.add_dock_widget(export_widget, area='right')
|
|
1469
1582
|
viewer.show(block=True)
|
|
@@ -1481,6 +1594,59 @@ def control_segmentation_napari(position, prefix='Aligned', population="target",
|
|
|
1481
1594
|
del labels
|
|
1482
1595
|
gc.collect()
|
|
1483
1596
|
|
|
1597
|
+
def correct_annotation(filename):
|
|
1598
|
+
|
|
1599
|
+
"""
|
|
1600
|
+
New function to reannotate an annotation image in post, using napari and save update inplace.
|
|
1601
|
+
"""
|
|
1602
|
+
|
|
1603
|
+
def export_labels():
|
|
1604
|
+
labels_layer = viewer.layers['segmentation'].data
|
|
1605
|
+
for t,im in enumerate(tqdm(labels_layer)):
|
|
1606
|
+
|
|
1607
|
+
try:
|
|
1608
|
+
im = auto_correct_masks(im)
|
|
1609
|
+
except Exception as e:
|
|
1610
|
+
print(e)
|
|
1611
|
+
|
|
1612
|
+
save_tiff_imagej_compatible(existing_lbl, im.astype(np.int16), axes='YX')
|
|
1613
|
+
print("The labels have been successfully rewritten.")
|
|
1614
|
+
|
|
1615
|
+
@magicgui(call_button='Save the modified labels')
|
|
1616
|
+
def save_widget():
|
|
1617
|
+
return export_labels()
|
|
1618
|
+
|
|
1619
|
+
img = imread(filename.replace('\\','/'),is_mmstack=False)
|
|
1620
|
+
if img.ndim==3:
|
|
1621
|
+
img = np.moveaxis(img, 0, -1)
|
|
1622
|
+
elif img.ndim==2:
|
|
1623
|
+
img = img[:,:,np.newaxis]
|
|
1624
|
+
|
|
1625
|
+
existing_lbl = filename.replace('.tif','_labelled.tif')
|
|
1626
|
+
if os.path.exists(existing_lbl):
|
|
1627
|
+
labels = imread(existing_lbl)[np.newaxis,:,:].astype(int)
|
|
1628
|
+
else:
|
|
1629
|
+
labels = np.zeros_like(img[:,:,0]).astype(int)[np.newaxis,:,:]
|
|
1630
|
+
|
|
1631
|
+
stack = img[np.newaxis,:,:,:]
|
|
1632
|
+
|
|
1633
|
+
viewer = napari.Viewer()
|
|
1634
|
+
viewer.add_image(stack,channel_axis=-1,colormap=["gray"]*stack.shape[-1])
|
|
1635
|
+
viewer.add_labels(labels, name='segmentation',opacity=0.4)
|
|
1636
|
+
viewer.window.add_dock_widget(save_widget, area='right')
|
|
1637
|
+
viewer.show(block=True)
|
|
1638
|
+
|
|
1639
|
+
# temporary fix for slight napari memory leak
|
|
1640
|
+
for i in range(100):
|
|
1641
|
+
try:
|
|
1642
|
+
viewer.layers.pop()
|
|
1643
|
+
except:
|
|
1644
|
+
pass
|
|
1645
|
+
del viewer
|
|
1646
|
+
del stack
|
|
1647
|
+
del labels
|
|
1648
|
+
gc.collect()
|
|
1649
|
+
|
|
1484
1650
|
|
|
1485
1651
|
def _view_on_napari(tracks=None, stack=None, labels=None):
|
|
1486
1652
|
|
|
@@ -1516,23 +1682,24 @@ def _view_on_napari(tracks=None, stack=None, labels=None):
|
|
|
1516
1682
|
>>> labels = np.random.randint(0, 2, (100, 100))
|
|
1517
1683
|
>>> view_on_napari(tracks, stack=stack, labels=labels)
|
|
1518
1684
|
# Visualize tracks, stack, and labels using Napari.
|
|
1519
|
-
|
|
1685
|
+
|
|
1520
1686
|
"""
|
|
1521
1687
|
|
|
1522
1688
|
viewer = napari.Viewer()
|
|
1523
1689
|
if stack is not None:
|
|
1524
|
-
viewer.add_image(stack,channel_axis=-1,colormap=["gray"]*stack.shape[-1])
|
|
1690
|
+
viewer.add_image(stack, channel_axis=-1, colormap=["gray"] * stack.shape[-1])
|
|
1525
1691
|
if labels is not None:
|
|
1526
|
-
viewer.add_labels(labels, name='segmentation',opacity=0.4)
|
|
1692
|
+
viewer.add_labels(labels, name='segmentation', opacity=0.4)
|
|
1527
1693
|
if tracks is not None:
|
|
1528
1694
|
viewer.add_tracks(tracks, name='tracks')
|
|
1529
1695
|
viewer.show(block=True)
|
|
1530
1696
|
|
|
1531
|
-
def control_tracking_table(position, calibration=1, prefix="Aligned", population="target",
|
|
1532
|
-
column_labels={'track': "TRACK_ID", 'frame': 'FRAME', 'y': 'POSITION_Y', 'x': 'POSITION_X', 'label': 'class_id'}):
|
|
1533
1697
|
|
|
1698
|
+
def control_tracking_table(position, calibration=1, prefix="Aligned", population="target",
|
|
1699
|
+
column_labels={'track': "TRACK_ID", 'frame': 'FRAME', 'y': 'POSITION_Y', 'x': 'POSITION_X',
|
|
1700
|
+
'label': 'class_id'}):
|
|
1534
1701
|
"""
|
|
1535
|
-
|
|
1702
|
+
|
|
1536
1703
|
Control the tracking table and visualize tracks using Napari.
|
|
1537
1704
|
|
|
1538
1705
|
Parameters
|
|
@@ -1567,27 +1734,33 @@ def control_tracking_table(position, calibration=1, prefix="Aligned", population
|
|
|
1567
1734
|
|
|
1568
1735
|
"""
|
|
1569
1736
|
|
|
1570
|
-
position = position.replace('\\','/')
|
|
1571
|
-
tracks,labels,stack = load_tracking_data(position, prefix=prefix, population=population)
|
|
1572
|
-
tracks = tracks.loc[:,
|
|
1573
|
-
|
|
1574
|
-
|
|
1737
|
+
position = position.replace('\\', '/')
|
|
1738
|
+
tracks, labels, stack = load_tracking_data(position, prefix=prefix, population=population)
|
|
1739
|
+
tracks = tracks.loc[:,
|
|
1740
|
+
[column_labels['track'], column_labels['frame'], column_labels['y'], column_labels['x']]].to_numpy()
|
|
1741
|
+
tracks[:, -2:] /= calibration
|
|
1742
|
+
_view_on_napari(tracks, labels=labels, stack=stack)
|
|
1575
1743
|
|
|
1576
1744
|
|
|
1577
1745
|
def get_segmentation_models_list(mode='targets', return_path=False):
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
repository_models = get_zenodo_files(cat=os.sep.join(["models","
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1746
|
+
if mode == 'targets':
|
|
1747
|
+
modelpath = os.sep.join(
|
|
1748
|
+
[os.path.split(os.path.dirname(os.path.realpath(__file__)))[0], "celldetective", "models",
|
|
1749
|
+
"segmentation_targets", os.sep])
|
|
1750
|
+
repository_models = get_zenodo_files(cat=os.sep.join(["models", "segmentation_targets"]))
|
|
1751
|
+
elif mode == 'effectors':
|
|
1752
|
+
modelpath = os.sep.join(
|
|
1753
|
+
[os.path.split(os.path.dirname(os.path.realpath(__file__)))[0], "celldetective", "models",
|
|
1754
|
+
"segmentation_effectors", os.sep])
|
|
1755
|
+
repository_models = get_zenodo_files(cat=os.sep.join(["models", "segmentation_effectors"]))
|
|
1756
|
+
elif mode == 'generic':
|
|
1757
|
+
modelpath = os.sep.join(
|
|
1758
|
+
[os.path.split(os.path.dirname(os.path.realpath(__file__)))[0], "celldetective", "models",
|
|
1759
|
+
"segmentation_generic", os.sep])
|
|
1760
|
+
repository_models = get_zenodo_files(cat=os.sep.join(["models", "segmentation_generic"]))
|
|
1761
|
+
|
|
1762
|
+
available_models = natsorted(glob(modelpath + '*/'))
|
|
1763
|
+
available_models = [m.replace('\\', '/').split('/')[-2] for m in available_models]
|
|
1591
1764
|
for rm in repository_models:
|
|
1592
1765
|
if rm not in available_models:
|
|
1593
1766
|
available_models.append(rm)
|
|
@@ -1597,16 +1770,17 @@ def get_segmentation_models_list(mode='targets', return_path=False):
|
|
|
1597
1770
|
else:
|
|
1598
1771
|
return available_models, modelpath
|
|
1599
1772
|
|
|
1773
|
+
|
|
1600
1774
|
def locate_segmentation_model(name):
|
|
1601
1775
|
|
|
1602
1776
|
"""
|
|
1603
|
-
Locates a specified segmentation model within the local 'celldetective' directory or
|
|
1777
|
+
Locates a specified segmentation model within the local 'celldetective' directory or
|
|
1604
1778
|
downloads it from Zenodo if not found locally.
|
|
1605
1779
|
|
|
1606
|
-
This function attempts to find a segmentation model by name within a predefined directory
|
|
1607
|
-
structure starting from the 'celldetective/models/segmentation*' path. If the model is not
|
|
1608
|
-
found locally, it then tries to locate and download the model from Zenodo, placing it into
|
|
1609
|
-
the appropriate category directory within 'celldetective'. The function prints the search
|
|
1780
|
+
This function attempts to find a segmentation model by name within a predefined directory
|
|
1781
|
+
structure starting from the 'celldetective/models/segmentation*' path. If the model is not
|
|
1782
|
+
found locally, it then tries to locate and download the model from Zenodo, placing it into
|
|
1783
|
+
the appropriate category directory within 'celldetective'. The function prints the search
|
|
1610
1784
|
directory path and returns the path to the found or downloaded model.
|
|
1611
1785
|
|
|
1612
1786
|
Parameters
|
|
@@ -1617,7 +1791,7 @@ def locate_segmentation_model(name):
|
|
|
1617
1791
|
Returns
|
|
1618
1792
|
-------
|
|
1619
1793
|
str or None
|
|
1620
|
-
The full path to the located or downloaded segmentation model directory, or None if the
|
|
1794
|
+
The full path to the located or downloaded segmentation model directory, or None if the
|
|
1621
1795
|
model could not be found or downloaded.
|
|
1622
1796
|
|
|
1623
1797
|
Raises
|
|
@@ -1628,13 +1802,13 @@ def locate_segmentation_model(name):
|
|
|
1628
1802
|
"""
|
|
1629
1803
|
|
|
1630
1804
|
main_dir = os.sep.join([os.path.split(os.path.dirname(os.path.realpath(__file__)))[0],"celldetective"])
|
|
1631
|
-
modelpath = os.sep.join([main_dir, "models", "segmentation*"
|
|
1805
|
+
modelpath = os.sep.join([main_dir, "models", "segmentation*"]) + os.sep
|
|
1632
1806
|
print(f'Looking for {name} in {modelpath}')
|
|
1633
|
-
models = glob(modelpath+f'*{os.sep}')
|
|
1807
|
+
models = glob(modelpath + f'*{os.sep}')
|
|
1634
1808
|
|
|
1635
|
-
match=None
|
|
1809
|
+
match = None
|
|
1636
1810
|
for m in models:
|
|
1637
|
-
if name==m.replace('\\',os.sep).split(os.sep)[-2]:
|
|
1811
|
+
if name == m.replace('\\', os.sep).split(os.sep)[-2]:
|
|
1638
1812
|
match = m
|
|
1639
1813
|
return match
|
|
1640
1814
|
# else no match, try zenodo
|
|
@@ -1643,42 +1817,42 @@ def locate_segmentation_model(name):
|
|
|
1643
1817
|
index = files.index(name)
|
|
1644
1818
|
cat = categories[index]
|
|
1645
1819
|
download_zenodo_file(name, os.sep.join([main_dir, cat]))
|
|
1646
|
-
match = os.sep.join([main_dir, cat, name])+os.sep
|
|
1820
|
+
match = os.sep.join([main_dir, cat, name]) + os.sep
|
|
1647
1821
|
return match
|
|
1648
1822
|
|
|
1649
1823
|
|
|
1650
1824
|
def get_segmentation_datasets_list(return_path=False):
|
|
1651
|
-
|
|
1652
1825
|
"""
|
|
1653
|
-
Retrieves a list of available segmentation datasets from both the local 'celldetective/datasets/segmentation_annotations'
|
|
1826
|
+
Retrieves a list of available segmentation datasets from both the local 'celldetective/datasets/segmentation_annotations'
|
|
1654
1827
|
directory and a Zenodo repository, optionally returning the path to the local datasets directory.
|
|
1655
1828
|
|
|
1656
|
-
This function compiles a list of available segmentation datasets by first identifying datasets stored locally
|
|
1657
|
-
within a specified path related to the script's directory. It then extends this list with datasets available
|
|
1658
|
-
in a Zenodo repository, ensuring no duplicates are added. The function can return just the list of dataset
|
|
1829
|
+
This function compiles a list of available segmentation datasets by first identifying datasets stored locally
|
|
1830
|
+
within a specified path related to the script's directory. It then extends this list with datasets available
|
|
1831
|
+
in a Zenodo repository, ensuring no duplicates are added. The function can return just the list of dataset
|
|
1659
1832
|
names or, if specified, also return the path to the local datasets directory.
|
|
1660
1833
|
|
|
1661
1834
|
Parameters
|
|
1662
1835
|
----------
|
|
1663
1836
|
return_path : bool, optional
|
|
1664
|
-
If True, the function returns a tuple containing the list of available dataset names and the path to the
|
|
1837
|
+
If True, the function returns a tuple containing the list of available dataset names and the path to the
|
|
1665
1838
|
local datasets directory. If False, only the list of dataset names is returned (default is False).
|
|
1666
1839
|
|
|
1667
1840
|
Returns
|
|
1668
1841
|
-------
|
|
1669
1842
|
list or (list, str)
|
|
1670
|
-
If return_path is False, returns a list of strings, each string being the name of an available dataset.
|
|
1671
|
-
If return_path is True, returns a tuple where the first element is this list and the second element is a
|
|
1843
|
+
If return_path is False, returns a list of strings, each string being the name of an available dataset.
|
|
1844
|
+
If return_path is True, returns a tuple where the first element is this list and the second element is a
|
|
1672
1845
|
string representing the path to the local datasets directory.
|
|
1673
|
-
|
|
1846
|
+
|
|
1674
1847
|
"""
|
|
1675
1848
|
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1849
|
+
datasets_path = os.sep.join(
|
|
1850
|
+
[os.path.split(os.path.dirname(os.path.realpath(__file__)))[0], "celldetective", "datasets",
|
|
1851
|
+
"segmentation_annotations", os.sep])
|
|
1852
|
+
repository_datasets = get_zenodo_files(cat=os.sep.join(["datasets", "segmentation_annotations"]))
|
|
1679
1853
|
|
|
1680
|
-
available_datasets = natsorted(glob(datasets_path+'*/'))
|
|
1681
|
-
available_datasets = [m.replace('\\','/').split('/')[-2] for m in available_datasets]
|
|
1854
|
+
available_datasets = natsorted(glob(datasets_path + '*/'))
|
|
1855
|
+
available_datasets = [m.replace('\\', '/').split('/')[-2] for m in available_datasets]
|
|
1682
1856
|
for rm in repository_datasets:
|
|
1683
1857
|
if rm not in available_datasets:
|
|
1684
1858
|
available_datasets.append(rm)
|
|
@@ -1693,12 +1867,12 @@ def get_segmentation_datasets_list(return_path=False):
|
|
|
1693
1867
|
def locate_segmentation_dataset(name):
|
|
1694
1868
|
|
|
1695
1869
|
"""
|
|
1696
|
-
Locates a specified segmentation dataset within the local 'celldetective/datasets/segmentation_annotations' directory
|
|
1870
|
+
Locates a specified segmentation dataset within the local 'celldetective/datasets/segmentation_annotations' directory
|
|
1697
1871
|
or downloads it from Zenodo if not found locally.
|
|
1698
1872
|
|
|
1699
|
-
This function attempts to find a segmentation dataset by name within a predefined directory structure. If the dataset
|
|
1700
|
-
is not found locally, it then tries to locate and download the dataset from Zenodo, placing it into the appropriate
|
|
1701
|
-
category directory within 'celldetective'. The function prints the search directory path and returns the path to the
|
|
1873
|
+
This function attempts to find a segmentation dataset by name within a predefined directory structure. If the dataset
|
|
1874
|
+
is not found locally, it then tries to locate and download the dataset from Zenodo, placing it into the appropriate
|
|
1875
|
+
category directory within 'celldetective'. The function prints the search directory path and returns the path to the
|
|
1702
1876
|
found or downloaded dataset.
|
|
1703
1877
|
|
|
1704
1878
|
Parameters
|
|
@@ -1709,24 +1883,24 @@ def locate_segmentation_dataset(name):
|
|
|
1709
1883
|
Returns
|
|
1710
1884
|
-------
|
|
1711
1885
|
str or None
|
|
1712
|
-
The full path to the located or downloaded segmentation dataset directory, or None if the dataset could not be
|
|
1886
|
+
The full path to the located or downloaded segmentation dataset directory, or None if the dataset could not be
|
|
1713
1887
|
found or downloaded.
|
|
1714
1888
|
|
|
1715
1889
|
Raises
|
|
1716
1890
|
------
|
|
1717
1891
|
FileNotFoundError
|
|
1718
1892
|
If the dataset cannot be found locally and also cannot be found or downloaded from Zenodo.
|
|
1719
|
-
|
|
1893
|
+
|
|
1720
1894
|
"""
|
|
1721
1895
|
|
|
1722
|
-
main_dir = os.sep.join([os.path.split(os.path.dirname(os.path.realpath(__file__)))[0],"celldetective"])
|
|
1896
|
+
main_dir = os.sep.join([os.path.split(os.path.dirname(os.path.realpath(__file__)))[0], "celldetective"])
|
|
1723
1897
|
modelpath = os.sep.join([main_dir, "datasets", "segmentation_annotations", os.sep])
|
|
1724
1898
|
print(f'Looking for {name} in {modelpath}')
|
|
1725
|
-
models = glob(modelpath+f'*{os.sep}')
|
|
1899
|
+
models = glob(modelpath + f'*{os.sep}')
|
|
1726
1900
|
|
|
1727
|
-
match=None
|
|
1901
|
+
match = None
|
|
1728
1902
|
for m in models:
|
|
1729
|
-
if name==m.replace('\\',os.sep).split(os.sep)[-2]:
|
|
1903
|
+
if name == m.replace('\\', os.sep).split(os.sep)[-2]:
|
|
1730
1904
|
match = m
|
|
1731
1905
|
return match
|
|
1732
1906
|
# else no match, try zenodo
|
|
@@ -1735,41 +1909,43 @@ def locate_segmentation_dataset(name):
|
|
|
1735
1909
|
index = files.index(name)
|
|
1736
1910
|
cat = categories[index]
|
|
1737
1911
|
download_zenodo_file(name, os.sep.join([main_dir, cat]))
|
|
1738
|
-
match = os.sep.join([main_dir, cat, name])+os.sep
|
|
1912
|
+
match = os.sep.join([main_dir, cat, name]) + os.sep
|
|
1739
1913
|
return match
|
|
1740
1914
|
|
|
1741
1915
|
|
|
1742
1916
|
def get_signal_datasets_list(return_path=False):
|
|
1743
1917
|
|
|
1744
1918
|
"""
|
|
1745
|
-
Retrieves a list of available signal datasets from both the local 'celldetective/datasets/signal_annotations' directory
|
|
1919
|
+
Retrieves a list of available signal datasets from both the local 'celldetective/datasets/signal_annotations' directory
|
|
1746
1920
|
and a Zenodo repository, optionally returning the path to the local datasets directory.
|
|
1747
1921
|
|
|
1748
|
-
This function compiles a list of available signal datasets by first identifying datasets stored locally within a specified
|
|
1749
|
-
path related to the script's directory. It then extends this list with datasets available in a Zenodo repository, ensuring
|
|
1750
|
-
no duplicates are added. The function can return just the list of dataset names or, if specified, also return the path to
|
|
1922
|
+
This function compiles a list of available signal datasets by first identifying datasets stored locally within a specified
|
|
1923
|
+
path related to the script's directory. It then extends this list with datasets available in a Zenodo repository, ensuring
|
|
1924
|
+
no duplicates are added. The function can return just the list of dataset names or, if specified, also return the path to
|
|
1751
1925
|
the local datasets directory.
|
|
1752
1926
|
|
|
1753
1927
|
Parameters
|
|
1754
1928
|
----------
|
|
1755
1929
|
return_path : bool, optional
|
|
1756
|
-
If True, the function returns a tuple containing the list of available dataset names and the path to the local datasets
|
|
1930
|
+
If True, the function returns a tuple containing the list of available dataset names and the path to the local datasets
|
|
1757
1931
|
directory. If False, only the list of dataset names is returned (default is False).
|
|
1758
1932
|
|
|
1759
1933
|
Returns
|
|
1760
1934
|
-------
|
|
1761
1935
|
list or (list, str)
|
|
1762
|
-
If return_path is False, returns a list of strings, each string being the name of an available dataset. If return_path
|
|
1763
|
-
is True, returns a tuple where the first element is this list and the second element is a string representing the path
|
|
1936
|
+
If return_path is False, returns a list of strings, each string being the name of an available dataset. If return_path
|
|
1937
|
+
is True, returns a tuple where the first element is this list and the second element is a string representing the path
|
|
1764
1938
|
to the local datasets directory.
|
|
1765
1939
|
|
|
1766
|
-
"""
|
|
1940
|
+
"""
|
|
1767
1941
|
|
|
1768
|
-
datasets_path = os.sep.join(
|
|
1769
|
-
|
|
1942
|
+
datasets_path = os.sep.join(
|
|
1943
|
+
[os.path.split(os.path.dirname(os.path.realpath(__file__)))[0], "celldetective", "datasets",
|
|
1944
|
+
"signal_annotations", os.sep])
|
|
1945
|
+
repository_datasets = get_zenodo_files(cat=os.sep.join(["datasets", "signal_annotations"]))
|
|
1770
1946
|
|
|
1771
|
-
available_datasets = natsorted(glob(datasets_path+'*/'))
|
|
1772
|
-
available_datasets = [m.replace('\\','/').split('/')[-2] for m in available_datasets]
|
|
1947
|
+
available_datasets = natsorted(glob(datasets_path + '*/'))
|
|
1948
|
+
available_datasets = [m.replace('\\', '/').split('/')[-2] for m in available_datasets]
|
|
1773
1949
|
for rm in repository_datasets:
|
|
1774
1950
|
if rm not in available_datasets:
|
|
1775
1951
|
available_datasets.append(rm)
|
|
@@ -1779,15 +1955,16 @@ def get_signal_datasets_list(return_path=False):
|
|
|
1779
1955
|
else:
|
|
1780
1956
|
return available_datasets, datasets_path
|
|
1781
1957
|
|
|
1958
|
+
|
|
1782
1959
|
def locate_signal_dataset(name):
|
|
1783
1960
|
|
|
1784
1961
|
"""
|
|
1785
|
-
Locates a specified signal dataset within the local 'celldetective/datasets/signal_annotations' directory or downloads
|
|
1962
|
+
Locates a specified signal dataset within the local 'celldetective/datasets/signal_annotations' directory or downloads
|
|
1786
1963
|
it from Zenodo if not found locally.
|
|
1787
1964
|
|
|
1788
|
-
This function attempts to find a signal dataset by name within a predefined directory structure. If the dataset is not
|
|
1789
|
-
found locally, it then tries to locate and download the dataset from Zenodo, placing it into the appropriate category
|
|
1790
|
-
directory within 'celldetective'. The function prints the search directory path and returns the path to the found or
|
|
1965
|
+
This function attempts to find a signal dataset by name within a predefined directory structure. If the dataset is not
|
|
1966
|
+
found locally, it then tries to locate and download the dataset from Zenodo, placing it into the appropriate category
|
|
1967
|
+
directory within 'celldetective'. The function prints the search directory path and returns the path to the found or
|
|
1791
1968
|
downloaded dataset.
|
|
1792
1969
|
|
|
1793
1970
|
Parameters
|
|
@@ -1798,7 +1975,7 @@ def locate_signal_dataset(name):
|
|
|
1798
1975
|
Returns
|
|
1799
1976
|
-------
|
|
1800
1977
|
str or None
|
|
1801
|
-
The full path to the located or downloaded signal dataset directory, or None if the dataset could not be found or
|
|
1978
|
+
The full path to the located or downloaded signal dataset directory, or None if the dataset could not be found or
|
|
1802
1979
|
downloaded.
|
|
1803
1980
|
|
|
1804
1981
|
Raises
|
|
@@ -1808,14 +1985,14 @@ def locate_signal_dataset(name):
|
|
|
1808
1985
|
|
|
1809
1986
|
"""
|
|
1810
1987
|
|
|
1811
|
-
main_dir = os.sep.join([os.path.split(os.path.dirname(os.path.realpath(__file__)))[0],"celldetective"])
|
|
1988
|
+
main_dir = os.sep.join([os.path.split(os.path.dirname(os.path.realpath(__file__)))[0], "celldetective"])
|
|
1812
1989
|
modelpath = os.sep.join([main_dir, "datasets", "signal_annotations", os.sep])
|
|
1813
1990
|
print(f'Looking for {name} in {modelpath}')
|
|
1814
|
-
models = glob(modelpath+f'*{os.sep}')
|
|
1991
|
+
models = glob(modelpath + f'*{os.sep}')
|
|
1815
1992
|
|
|
1816
|
-
match=None
|
|
1993
|
+
match = None
|
|
1817
1994
|
for m in models:
|
|
1818
|
-
if name==m.replace('\\',os.sep).split(os.sep)[-2]:
|
|
1995
|
+
if name == m.replace('\\', os.sep).split(os.sep)[-2]:
|
|
1819
1996
|
match = m
|
|
1820
1997
|
return match
|
|
1821
1998
|
# else no match, try zenodo
|
|
@@ -1824,13 +2001,13 @@ def locate_signal_dataset(name):
|
|
|
1824
2001
|
index = files.index(name)
|
|
1825
2002
|
cat = categories[index]
|
|
1826
2003
|
download_zenodo_file(name, os.sep.join([main_dir, cat]))
|
|
1827
|
-
match = os.sep.join([main_dir, cat, name])+os.sep
|
|
2004
|
+
match = os.sep.join([main_dir, cat, name]) + os.sep
|
|
1828
2005
|
return match
|
|
1829
2006
|
|
|
1830
2007
|
def normalize(frame, percentiles=(0.0,99.99), values=None, ignore_gray_value=0., clip=False, amplification=None, dtype=float):
|
|
1831
2008
|
|
|
1832
2009
|
"""
|
|
1833
|
-
|
|
2010
|
+
|
|
1834
2011
|
Normalize the intensity values of a frame.
|
|
1835
2012
|
|
|
1836
2013
|
Parameters
|
|
@@ -1880,15 +2057,16 @@ def normalize(frame, percentiles=(0.0,99.99), values=None, ignore_gray_value=0.,
|
|
|
1880
2057
|
frame = frame.astype(float)
|
|
1881
2058
|
|
|
1882
2059
|
if ignore_gray_value is not None:
|
|
1883
|
-
subframe = frame[frame!=ignore_gray_value]
|
|
2060
|
+
subframe = frame[frame != ignore_gray_value]
|
|
1884
2061
|
else:
|
|
1885
2062
|
subframe = frame.copy()
|
|
1886
2063
|
|
|
1887
2064
|
if values is not None:
|
|
1888
|
-
mi = values[0];
|
|
2065
|
+
mi = values[0];
|
|
2066
|
+
ma = values[1]
|
|
1889
2067
|
else:
|
|
1890
|
-
mi = np.nanpercentile(subframe.flatten(),percentiles[0],keepdims=True)
|
|
1891
|
-
ma = np.nanpercentile(subframe.flatten(),percentiles[1],keepdims=True)
|
|
2068
|
+
mi = np.nanpercentile(subframe.flatten(), percentiles[0], keepdims=True)
|
|
2069
|
+
ma = np.nanpercentile(subframe.flatten(), percentiles[1], keepdims=True)
|
|
1892
2070
|
|
|
1893
2071
|
frame0 = frame.copy()
|
|
1894
2072
|
frame = normalize_mi_ma(frame0, mi, ma, clip=False, eps=1e-20, dtype=np.float32)
|
|
@@ -1897,41 +2075,42 @@ def normalize(frame, percentiles=(0.0,99.99), values=None, ignore_gray_value=0.,
|
|
|
1897
2075
|
if clip:
|
|
1898
2076
|
if amplification is None:
|
|
1899
2077
|
amplification = 1.
|
|
1900
|
-
frame[frame>=amplification] = amplification
|
|
1901
|
-
frame[frame<=0.] = 0.
|
|
2078
|
+
frame[frame >= amplification] = amplification
|
|
2079
|
+
frame[frame <= 0.] = 0.
|
|
1902
2080
|
if ignore_gray_value is not None:
|
|
1903
|
-
frame[np.where(frame0)==ignore_gray_value] = ignore_gray_value
|
|
2081
|
+
frame[np.where(frame0) == ignore_gray_value] = ignore_gray_value
|
|
1904
2082
|
|
|
1905
2083
|
return frame.copy().astype(dtype)
|
|
1906
2084
|
|
|
2085
|
+
|
|
1907
2086
|
def normalize_multichannel(multichannel_frame, percentiles=None,
|
|
1908
2087
|
values=None, ignore_gray_value=0., clip=False,
|
|
1909
2088
|
amplification=None, dtype=float):
|
|
1910
2089
|
|
|
1911
2090
|
"""
|
|
1912
|
-
Normalizes a multichannel frame by adjusting the intensity values of each channel based on specified percentiles,
|
|
2091
|
+
Normalizes a multichannel frame by adjusting the intensity values of each channel based on specified percentiles,
|
|
1913
2092
|
direct value ranges, or amplification factors, with options to ignore a specific gray value and to clip the output.
|
|
1914
2093
|
|
|
1915
2094
|
Parameters
|
|
1916
2095
|
----------
|
|
1917
2096
|
multichannel_frame : ndarray
|
|
1918
|
-
The input multichannel image frame to be normalized, expected to be a 3-dimensional array where the last dimension
|
|
2097
|
+
The input multichannel image frame to be normalized, expected to be a 3-dimensional array where the last dimension
|
|
1919
2098
|
represents the channels.
|
|
1920
2099
|
percentiles : list of tuples or tuple, optional
|
|
1921
|
-
Percentile ranges (low, high) for each channel used to scale the intensity values. If a single tuple is provided,
|
|
2100
|
+
Percentile ranges (low, high) for each channel used to scale the intensity values. If a single tuple is provided,
|
|
1922
2101
|
it is applied to all channels. If None, the default percentile range of (0., 99.99) is used for each channel.
|
|
1923
2102
|
values : list of tuples or tuple, optional
|
|
1924
|
-
Direct value ranges (min, max) for each channel to scale the intensity values. If a single tuple is provided, it
|
|
2103
|
+
Direct value ranges (min, max) for each channel to scale the intensity values. If a single tuple is provided, it
|
|
1925
2104
|
is applied to all channels. This parameter overrides `percentiles` if provided.
|
|
1926
2105
|
ignore_gray_value : float, optional
|
|
1927
2106
|
A specific gray value to ignore during normalization (default is 0.).
|
|
1928
2107
|
clip : bool, optional
|
|
1929
|
-
If True, clips the output values to the range [0, 1] or the specified `dtype` range if `dtype` is not float
|
|
2108
|
+
If True, clips the output values to the range [0, 1] or the specified `dtype` range if `dtype` is not float
|
|
1930
2109
|
(default is False).
|
|
1931
2110
|
amplification : float, optional
|
|
1932
2111
|
A factor by which to amplify the intensity values after normalization. If None, no amplification is applied.
|
|
1933
2112
|
dtype : data-type, optional
|
|
1934
|
-
The desired data-type for the output normalized frame. The default is float, but other types can be specified
|
|
2113
|
+
The desired data-type for the output normalized frame. The default is float, but other types can be specified
|
|
1935
2114
|
to change the range of the output values.
|
|
1936
2115
|
|
|
1937
2116
|
Returns
|
|
@@ -1942,12 +2121,12 @@ def normalize_multichannel(multichannel_frame, percentiles=None,
|
|
|
1942
2121
|
Raises
|
|
1943
2122
|
------
|
|
1944
2123
|
AssertionError
|
|
1945
|
-
If the input `multichannel_frame` does not have 3 dimensions, or if the length of `values` does not match the
|
|
2124
|
+
If the input `multichannel_frame` does not have 3 dimensions, or if the length of `values` does not match the
|
|
1946
2125
|
number of channels in `multichannel_frame`.
|
|
1947
2126
|
|
|
1948
2127
|
Notes
|
|
1949
2128
|
-----
|
|
1950
|
-
- This function provides flexibility in normalization by allowing the use of percentile ranges, direct value ranges,
|
|
2129
|
+
- This function provides flexibility in normalization by allowing the use of percentile ranges, direct value ranges,
|
|
1951
2130
|
or amplification factors.
|
|
1952
2131
|
- The function makes a copy of the input frame to avoid altering the original data.
|
|
1953
2132
|
- When both `percentiles` and `values` are provided, `values` takes precedence for normalization.
|
|
@@ -1957,21 +2136,22 @@ def normalize_multichannel(multichannel_frame, percentiles=None,
|
|
|
1957
2136
|
>>> multichannel_frame = np.random.rand(100, 100, 3) # Example multichannel frame
|
|
1958
2137
|
>>> normalized_frame = normalize_multichannel(multichannel_frame, percentiles=((1, 99), (2, 98), (0, 100)))
|
|
1959
2138
|
# Normalizes each channel of the frame using specified percentile ranges.
|
|
1960
|
-
|
|
2139
|
+
|
|
1961
2140
|
"""
|
|
1962
2141
|
|
|
1963
2142
|
|
|
1964
2143
|
|
|
1965
2144
|
mf = multichannel_frame.copy().astype(float)
|
|
1966
|
-
assert mf.ndim==3,f'Wrong shape for the multichannel frame: {mf.shape}.'
|
|
2145
|
+
assert mf.ndim == 3, f'Wrong shape for the multichannel frame: {mf.shape}.'
|
|
1967
2146
|
if percentiles is None:
|
|
1968
|
-
percentiles = [(0.,99.99)]*mf.shape[-1]
|
|
1969
|
-
elif isinstance(percentiles,tuple):
|
|
1970
|
-
percentiles = [percentiles]*mf.shape[-1]
|
|
2147
|
+
percentiles = [(0., 99.99)] * mf.shape[-1]
|
|
2148
|
+
elif isinstance(percentiles, tuple):
|
|
2149
|
+
percentiles = [percentiles] * mf.shape[-1]
|
|
1971
2150
|
if values is not None:
|
|
1972
2151
|
if isinstance(values, tuple):
|
|
1973
|
-
values = [values]*mf.shape[-1]
|
|
1974
|
-
assert len(values)==mf.shape[
|
|
2152
|
+
values = [values] * mf.shape[-1]
|
|
2153
|
+
assert len(values) == mf.shape[
|
|
2154
|
+
-1], 'Mismatch between the normalization values provided and the number of channels.'
|
|
1975
2155
|
|
|
1976
2156
|
mf_new = []
|
|
1977
2157
|
for c in range(mf.shape[-1]):
|
|
@@ -2000,9 +2180,9 @@ def load_frames(img_nums, stack_path, scale=None, normalize_input=True, dtype=fl
|
|
|
2000
2180
|
"""
|
|
2001
2181
|
Loads and optionally normalizes and rescales specified frames from a stack located at a given path.
|
|
2002
2182
|
|
|
2003
|
-
This function reads specified frames from a stack file, applying systematic adjustments to ensure
|
|
2004
|
-
the channel axis is last. It supports optional normalization of the input frames and rescaling. An
|
|
2005
|
-
artificial pixel modification is applied to frames with uniform values to prevent errors during
|
|
2183
|
+
This function reads specified frames from a stack file, applying systematic adjustments to ensure
|
|
2184
|
+
the channel axis is last. It supports optional normalization of the input frames and rescaling. An
|
|
2185
|
+
artificial pixel modification is applied to frames with uniform values to prevent errors during
|
|
2006
2186
|
normalization.
|
|
2007
2187
|
|
|
2008
2188
|
Parameters
|
|
@@ -2014,7 +2194,7 @@ def load_frames(img_nums, stack_path, scale=None, normalize_input=True, dtype=fl
|
|
|
2014
2194
|
scale : float, optional
|
|
2015
2195
|
The scaling factor to apply to the frames. If None, no scaling is applied (default is None).
|
|
2016
2196
|
normalize_input : bool, optional
|
|
2017
|
-
Whether to normalize the loaded frames. If True, normalization is applied according to
|
|
2197
|
+
Whether to normalize the loaded frames. If True, normalization is applied according to
|
|
2018
2198
|
`normalize_kwargs` (default is True).
|
|
2019
2199
|
dtype : data-type, optional
|
|
2020
2200
|
The desired data-type for the output frames (default is float).
|
|
@@ -2030,38 +2210,39 @@ def load_frames(img_nums, stack_path, scale=None, normalize_input=True, dtype=fl
|
|
|
2030
2210
|
Raises
|
|
2031
2211
|
------
|
|
2032
2212
|
Exception
|
|
2033
|
-
Prints an error message if the specified frames cannot be loaded or if there is a mismatch between
|
|
2213
|
+
Prints an error message if the specified frames cannot be loaded or if there is a mismatch between
|
|
2034
2214
|
the provided experiment channel information and the stack format.
|
|
2035
2215
|
|
|
2036
2216
|
Notes
|
|
2037
2217
|
-----
|
|
2038
2218
|
- The function uses scikit-image for reading frames and supports multi-frame TIFF stacks.
|
|
2039
2219
|
- Normalization and scaling are optional and can be customized through function parameters.
|
|
2040
|
-
- A workaround is implemented for frames with uniform pixel values to prevent normalization errors by
|
|
2220
|
+
- A workaround is implemented for frames with uniform pixel values to prevent normalization errors by
|
|
2041
2221
|
adding a 'fake' pixel.
|
|
2042
2222
|
|
|
2043
2223
|
Examples
|
|
2044
2224
|
--------
|
|
2045
2225
|
>>> frames = load_frames([0, 1, 2], '/path/to/stack.tif', scale=0.5, normalize_input=True, dtype=np.uint8)
|
|
2046
|
-
# Loads the first three frames from '/path/to/stack.tif', normalizes them, rescales by a factor of 0.5,
|
|
2226
|
+
# Loads the first three frames from '/path/to/stack.tif', normalizes them, rescales by a factor of 0.5,
|
|
2047
2227
|
# and converts them to uint8 data type.
|
|
2048
|
-
|
|
2228
|
+
|
|
2049
2229
|
"""
|
|
2050
2230
|
|
|
2051
2231
|
try:
|
|
2052
2232
|
frames = skio.imread(stack_path, key=img_nums, plugin="tifffile")
|
|
2053
2233
|
except Exception as e:
|
|
2054
|
-
print(
|
|
2234
|
+
print(
|
|
2235
|
+
f'Error in loading the frame {img_nums} {e}. Please check that the experiment channel information is consistent with the movie being read.')
|
|
2055
2236
|
return None
|
|
2056
2237
|
|
|
2057
|
-
if frames.ndim==3:
|
|
2238
|
+
if frames.ndim == 3:
|
|
2058
2239
|
# Systematically move channel axis to the end
|
|
2059
2240
|
channel_axis = np.argmin(frames.shape)
|
|
2060
2241
|
frames = np.moveaxis(frames, channel_axis, -1)
|
|
2061
2242
|
|
|
2062
2243
|
if frames.ndim==2:
|
|
2063
2244
|
frames = frames[:,:,np.newaxis].astype(float)
|
|
2064
|
-
|
|
2245
|
+
|
|
2065
2246
|
if normalize_input:
|
|
2066
2247
|
frames = normalize_multichannel(frames, **normalize_kwargs)
|
|
2067
2248
|
|
|
@@ -2072,9 +2253,9 @@ def load_frames(img_nums, stack_path, scale=None, normalize_input=True, dtype=fl
|
|
|
2072
2253
|
# add a fake pixel to prevent auto normalization errors on images that are uniform
|
|
2073
2254
|
# to revisit
|
|
2074
2255
|
for k in range(frames.shape[2]):
|
|
2075
|
-
unique_values = np.unique(frames[
|
|
2076
|
-
if len(unique_values)==1:
|
|
2077
|
-
frames[0,0,k] += 1
|
|
2256
|
+
unique_values = np.unique(frames[:, :, k])
|
|
2257
|
+
if len(unique_values) == 1:
|
|
2258
|
+
frames[0, 0, k] += 1
|
|
2078
2259
|
|
|
2079
2260
|
return frames.astype(dtype)
|
|
2080
2261
|
|
|
@@ -2084,9 +2265,9 @@ def get_stack_normalization_values(stack, percentiles=None, ignore_gray_value=0.
|
|
|
2084
2265
|
"""
|
|
2085
2266
|
Computes the normalization value ranges (minimum and maximum) for each channel in a 4D stack based on specified percentiles.
|
|
2086
2267
|
|
|
2087
|
-
This function calculates the value ranges for normalizing each channel within a 4-dimensional stack, with dimensions
|
|
2088
|
-
expected to be in the order of Time (T), Y (height), X (width), and Channels (C). The normalization values are determined
|
|
2089
|
-
by the specified percentiles for each channel. An option to ignore a specific gray value during computation is provided,
|
|
2268
|
+
This function calculates the value ranges for normalizing each channel within a 4-dimensional stack, with dimensions
|
|
2269
|
+
expected to be in the order of Time (T), Y (height), X (width), and Channels (C). The normalization values are determined
|
|
2270
|
+
by the specified percentiles for each channel. An option to ignore a specific gray value during computation is provided,
|
|
2090
2271
|
though its effect is not implemented in this snippet.
|
|
2091
2272
|
|
|
2092
2273
|
Parameters
|
|
@@ -2094,30 +2275,30 @@ def get_stack_normalization_values(stack, percentiles=None, ignore_gray_value=0.
|
|
|
2094
2275
|
stack : ndarray
|
|
2095
2276
|
The input 4D stack with dimensions TYXC from which to calculate normalization values.
|
|
2096
2277
|
percentiles : tuple, list of tuples, optional
|
|
2097
|
-
The percentile values (low, high) used to calculate the normalization ranges for each channel. If a single tuple
|
|
2098
|
-
is provided, it is applied to all channels. If a list of tuples is provided, each tuple is applied to the
|
|
2278
|
+
The percentile values (low, high) used to calculate the normalization ranges for each channel. If a single tuple
|
|
2279
|
+
is provided, it is applied to all channels. If a list of tuples is provided, each tuple is applied to the
|
|
2099
2280
|
corresponding channel. If None, defaults to (0., 99.99) for each channel.
|
|
2100
2281
|
ignore_gray_value : float, optional
|
|
2101
|
-
A gray value to potentially ignore during the calculation. This parameter is provided for interface consistency
|
|
2282
|
+
A gray value to potentially ignore during the calculation. This parameter is provided for interface consistency
|
|
2102
2283
|
but is not utilized in the current implementation (default is 0.).
|
|
2103
2284
|
|
|
2104
2285
|
Returns
|
|
2105
2286
|
-------
|
|
2106
2287
|
list of tuples
|
|
2107
|
-
A list where each tuple contains the (minimum, maximum) values for normalizing each channel based on the specified
|
|
2288
|
+
A list where each tuple contains the (minimum, maximum) values for normalizing each channel based on the specified
|
|
2108
2289
|
percentiles.
|
|
2109
2290
|
|
|
2110
2291
|
Raises
|
|
2111
2292
|
------
|
|
2112
2293
|
AssertionError
|
|
2113
|
-
If the input stack does not have 4 dimensions, or if the length of the `percentiles` list does not match the number
|
|
2294
|
+
If the input stack does not have 4 dimensions, or if the length of the `percentiles` list does not match the number
|
|
2114
2295
|
of channels in the stack.
|
|
2115
2296
|
|
|
2116
2297
|
Notes
|
|
2117
2298
|
-----
|
|
2118
|
-
- The function assumes the input stack is in TYXC format, where T is the time dimension, Y and X are spatial dimensions,
|
|
2299
|
+
- The function assumes the input stack is in TYXC format, where T is the time dimension, Y and X are spatial dimensions,
|
|
2119
2300
|
and C is the channel dimension.
|
|
2120
|
-
- Memory management via `gc.collect()` is employed after calculating normalization values for each channel to mitigate
|
|
2301
|
+
- Memory management via `gc.collect()` is employed after calculating normalization values for each channel to mitigate
|
|
2121
2302
|
potential memory issues with large datasets.
|
|
2122
2303
|
|
|
2123
2304
|
Examples
|
|
@@ -2125,23 +2306,24 @@ def get_stack_normalization_values(stack, percentiles=None, ignore_gray_value=0.
|
|
|
2125
2306
|
>>> stack = np.random.rand(5, 100, 100, 3) # Example 4D stack with 3 channels
|
|
2126
2307
|
>>> normalization_values = get_stack_normalization_values(stack, percentiles=((1, 99), (2, 98), (0, 100)))
|
|
2127
2308
|
# Calculates normalization ranges for each channel using the specified percentiles.
|
|
2128
|
-
|
|
2309
|
+
|
|
2129
2310
|
"""
|
|
2130
2311
|
|
|
2131
|
-
assert stack.ndim==4,f'Wrong number of dimensions for the stack, expect TYXC (4) got {stack.ndim}.'
|
|
2312
|
+
assert stack.ndim == 4, f'Wrong number of dimensions for the stack, expect TYXC (4) got {stack.ndim}.'
|
|
2132
2313
|
if percentiles is None:
|
|
2133
|
-
percentiles = [(0.,99.99)]*stack.shape[-1]
|
|
2134
|
-
elif isinstance(percentiles,tuple):
|
|
2135
|
-
percentiles = [percentiles]*stack.shape[-1]
|
|
2136
|
-
elif isinstance(percentiles,list):
|
|
2137
|
-
assert len(percentiles)==
|
|
2314
|
+
percentiles = [(0., 99.99)] * stack.shape[-1]
|
|
2315
|
+
elif isinstance(percentiles, tuple):
|
|
2316
|
+
percentiles = [percentiles] * stack.shape[-1]
|
|
2317
|
+
elif isinstance(percentiles, list):
|
|
2318
|
+
assert len(percentiles) == stack.shape[
|
|
2319
|
+
-1], f'Mismatch between the provided percentiles and the number of channels {stack.shape[-1]}. If you meant to apply the same percentiles to all channels, please provide a single tuple.'
|
|
2138
2320
|
|
|
2139
2321
|
values = []
|
|
2140
2322
|
for c in range(stack.shape[-1]):
|
|
2141
2323
|
perc = percentiles[c]
|
|
2142
|
-
mi = np.nanpercentile(stack[
|
|
2143
|
-
ma = np.nanpercentile(stack[
|
|
2144
|
-
values.append(tuple((mi,ma)))
|
|
2324
|
+
mi = np.nanpercentile(stack[:, :, :, c].flatten(), perc[0], keepdims=True)[0]
|
|
2325
|
+
ma = np.nanpercentile(stack[:, :, :, c].flatten(), perc[1], keepdims=True)[0]
|
|
2326
|
+
values.append(tuple((mi, ma)))
|
|
2145
2327
|
gc.collect()
|
|
2146
2328
|
|
|
2147
2329
|
return values
|
|
@@ -2150,15 +2332,15 @@ def get_stack_normalization_values(stack, percentiles=None, ignore_gray_value=0.
|
|
|
2150
2332
|
def get_positions_in_well(well):
|
|
2151
2333
|
|
|
2152
2334
|
"""
|
|
2153
|
-
Retrieves the list of position directories within a specified well directory,
|
|
2335
|
+
Retrieves the list of position directories within a specified well directory,
|
|
2154
2336
|
formatted as a NumPy array of strings.
|
|
2155
2337
|
|
|
2156
|
-
This function identifies position directories based on their naming convention,
|
|
2157
|
-
which must include a numeric identifier following the well's name. The well's name
|
|
2158
|
-
is expected to start with 'W' (e.g., 'W1'), followed by a numeric identifier. Position
|
|
2159
|
-
directories are assumed to be named with this numeric identifier directly after the well
|
|
2160
|
-
identifier, without the 'W'. For example, positions within well 'W1' might be named
|
|
2161
|
-
'101', '102', etc. This function will glob these directories and return their full
|
|
2338
|
+
This function identifies position directories based on their naming convention,
|
|
2339
|
+
which must include a numeric identifier following the well's name. The well's name
|
|
2340
|
+
is expected to start with 'W' (e.g., 'W1'), followed by a numeric identifier. Position
|
|
2341
|
+
directories are assumed to be named with this numeric identifier directly after the well
|
|
2342
|
+
identifier, without the 'W'. For example, positions within well 'W1' might be named
|
|
2343
|
+
'101', '102', etc. This function will glob these directories and return their full
|
|
2162
2344
|
paths as a NumPy array.
|
|
2163
2345
|
|
|
2164
2346
|
Parameters
|
|
@@ -2169,13 +2351,13 @@ def get_positions_in_well(well):
|
|
|
2169
2351
|
Returns
|
|
2170
2352
|
-------
|
|
2171
2353
|
np.ndarray
|
|
2172
|
-
An array of strings, each representing the full path to a position directory within
|
|
2354
|
+
An array of strings, each representing the full path to a position directory within
|
|
2173
2355
|
the specified well. The array is empty if no position directories are found.
|
|
2174
2356
|
|
|
2175
2357
|
Notes
|
|
2176
2358
|
-----
|
|
2177
2359
|
- This function relies on a specific naming convention for wells and positions. It assumes
|
|
2178
|
-
that each well directory is prefixed with 'W' followed by a numeric identifier, and
|
|
2360
|
+
that each well directory is prefixed with 'W' followed by a numeric identifier, and
|
|
2179
2361
|
position directories are named starting with this numeric identifier directly.
|
|
2180
2362
|
|
|
2181
2363
|
Examples
|
|
@@ -2183,54 +2365,54 @@ def get_positions_in_well(well):
|
|
|
2183
2365
|
>>> get_positions_in_well('/path/to/experiment/W1')
|
|
2184
2366
|
# This might return an array like array(['/path/to/experiment/W1/101', '/path/to/experiment/W1/102'])
|
|
2185
2367
|
if position directories '101' and '102' exist within the well 'W1' directory.
|
|
2186
|
-
|
|
2368
|
+
|
|
2187
2369
|
"""
|
|
2188
2370
|
|
|
2189
2371
|
if well.endswith(os.sep):
|
|
2190
2372
|
well = well[:-1]
|
|
2191
2373
|
|
|
2192
|
-
w_numeric = os.path.split(well)[-1].replace('W','')
|
|
2193
|
-
positions = natsorted(glob(os.sep.join([well,f'{w_numeric}*{os.sep}'])))
|
|
2374
|
+
w_numeric = os.path.split(well)[-1].replace('W', '')
|
|
2375
|
+
positions = natsorted(glob(os.sep.join([well, f'{w_numeric}*{os.sep}'])))
|
|
2194
2376
|
|
|
2195
|
-
return np.array(positions,dtype=str)
|
|
2377
|
+
return np.array(positions, dtype=str)
|
|
2196
2378
|
|
|
2197
2379
|
|
|
2198
2380
|
def extract_experiment_folder_output(experiment_folder, destination_folder):
|
|
2199
2381
|
|
|
2200
2382
|
"""
|
|
2201
|
-
Copies the output subfolder and associated tables from an experiment folder to a new location,
|
|
2383
|
+
Copies the output subfolder and associated tables from an experiment folder to a new location,
|
|
2202
2384
|
making the experiment folder much lighter by only keeping essential data.
|
|
2203
|
-
|
|
2204
|
-
This function takes the path to an experiment folder and a destination folder as input.
|
|
2205
|
-
It creates a copy of the experiment folder at the destination, but only includes the output subfolders
|
|
2206
|
-
and their associated tables for each well and position within the experiment.
|
|
2207
|
-
This operation significantly reduces the size of the experiment data by excluding non-essential files.
|
|
2208
|
-
|
|
2209
|
-
The structure of the copied experiment folder is preserved, including the configuration file,
|
|
2210
|
-
well directories, and position directories within each well.
|
|
2385
|
+
|
|
2386
|
+
This function takes the path to an experiment folder and a destination folder as input.
|
|
2387
|
+
It creates a copy of the experiment folder at the destination, but only includes the output subfolders
|
|
2388
|
+
and their associated tables for each well and position within the experiment.
|
|
2389
|
+
This operation significantly reduces the size of the experiment data by excluding non-essential files.
|
|
2390
|
+
|
|
2391
|
+
The structure of the copied experiment folder is preserved, including the configuration file,
|
|
2392
|
+
well directories, and position directories within each well.
|
|
2211
2393
|
Only the 'output' subfolder and its 'tables' subdirectory are copied for each position.
|
|
2212
|
-
|
|
2394
|
+
|
|
2213
2395
|
Parameters
|
|
2214
2396
|
----------
|
|
2215
2397
|
experiment_folder : str
|
|
2216
|
-
The path to the source experiment folder from which to extract data.
|
|
2398
|
+
The path to the source experiment folder from which to extract data.
|
|
2217
2399
|
destination_folder : str
|
|
2218
|
-
The path to the destination folder where the reduced copy of the experiment
|
|
2400
|
+
The path to the destination folder where the reduced copy of the experiment
|
|
2219
2401
|
will be created.
|
|
2220
|
-
|
|
2402
|
+
|
|
2221
2403
|
Notes
|
|
2222
2404
|
-----
|
|
2223
|
-
- This function assumes that the structure of the experiment folder is consistent,
|
|
2224
|
-
with wells organized in subdirectories and each containing a position subdirectory.
|
|
2405
|
+
- This function assumes that the structure of the experiment folder is consistent,
|
|
2406
|
+
with wells organized in subdirectories and each containing a position subdirectory.
|
|
2225
2407
|
Each position subdirectory should have an 'output' folder and a 'tables' subfolder within it.
|
|
2226
|
-
|
|
2227
|
-
- The function also assumes the existence of a configuration file in the root of the
|
|
2408
|
+
|
|
2409
|
+
- The function also assumes the existence of a configuration file in the root of the
|
|
2228
2410
|
experiment folder, which is copied to the root of the destination experiment folder.
|
|
2229
|
-
|
|
2411
|
+
|
|
2230
2412
|
Examples
|
|
2231
2413
|
--------
|
|
2232
2414
|
>>> extract_experiment_folder_output('/path/to/experiment_folder', '/path/to/destination_folder')
|
|
2233
|
-
# This will copy the 'experiment_folder' to 'destination_folder', including only
|
|
2415
|
+
# This will copy the 'experiment_folder' to 'destination_folder', including only
|
|
2234
2416
|
# the output subfolders and their tables for each well and position.
|
|
2235
2417
|
|
|
2236
2418
|
"""
|
|
@@ -2240,21 +2422,21 @@ def extract_experiment_folder_output(experiment_folder, destination_folder):
|
|
|
2240
2422
|
experiment_folder = experiment_folder[:-1]
|
|
2241
2423
|
if destination_folder.endswith(os.sep):
|
|
2242
2424
|
destination_folder = destination_folder[:-1]
|
|
2243
|
-
|
|
2425
|
+
|
|
2244
2426
|
exp_name = experiment_folder.split(os.sep)[-1]
|
|
2245
2427
|
output_path = os.sep.join([destination_folder, exp_name])
|
|
2246
2428
|
if not os.path.exists(output_path):
|
|
2247
2429
|
os.mkdir(output_path)
|
|
2248
2430
|
|
|
2249
2431
|
config = get_config(experiment_folder)
|
|
2250
|
-
copyfile(config,os.sep.join([output_path,os.path.split(config)[-1]]))
|
|
2251
|
-
|
|
2432
|
+
copyfile(config, os.sep.join([output_path, os.path.split(config)[-1]]))
|
|
2433
|
+
|
|
2252
2434
|
wells_src = get_experiment_wells(experiment_folder)
|
|
2253
2435
|
wells = [w.split(os.sep)[-2] for w in wells_src]
|
|
2254
2436
|
|
|
2255
|
-
for k,w in enumerate(wells):
|
|
2256
|
-
|
|
2257
|
-
well_output_path = os.sep.join([output_path,w])
|
|
2437
|
+
for k, w in enumerate(wells):
|
|
2438
|
+
|
|
2439
|
+
well_output_path = os.sep.join([output_path, w])
|
|
2258
2440
|
if not os.path.exists(well_output_path):
|
|
2259
2441
|
os.mkdir(well_output_path)
|
|
2260
2442
|
|
|
@@ -2270,16 +2452,16 @@ def extract_experiment_folder_output(experiment_folder, destination_folder):
|
|
|
2270
2452
|
|
|
2271
2453
|
if not os.path.exists(output_folder):
|
|
2272
2454
|
os.mkdir(output_folder)
|
|
2273
|
-
|
|
2455
|
+
|
|
2274
2456
|
if not os.path.exists(output_tables_folder):
|
|
2275
|
-
os.mkdir(output_tables_folder)
|
|
2457
|
+
os.mkdir(output_tables_folder)
|
|
2276
2458
|
|
|
2277
|
-
tab_path = glob(pos+os.sep.join(['output','tables',f'*']))
|
|
2278
|
-
|
|
2279
|
-
for t in tab_path:
|
|
2280
|
-
copyfile(t,os.sep.join([output_tables_folder,os.path.split(t)[-1]]))
|
|
2459
|
+
tab_path = glob(pos + os.sep.join(['output', 'tables', f'*']))
|
|
2281
2460
|
|
|
2461
|
+
for t in tab_path:
|
|
2462
|
+
copyfile(t, os.sep.join([output_tables_folder, os.path.split(t)[-1]]))
|
|
2282
2463
|
|
|
2283
2464
|
|
|
2284
2465
|
if __name__ == '__main__':
|
|
2285
|
-
control_segmentation_napari("/home/limozin/Documents/Experiments/MinimumJan/W4/401/", prefix='Aligned',
|
|
2466
|
+
control_segmentation_napari("/home/limozin/Documents/Experiments/MinimumJan/W4/401/", prefix='Aligned',
|
|
2467
|
+
population="target", flush_memory=False)
|