celldetective 1.4.0__py3-none-any.whl → 1.4.1__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/_version.py +1 -1
- celldetective/exceptions.py +11 -0
- celldetective/filters.py +7 -1
- celldetective/gui/InitWindow.py +4 -1
- celldetective/gui/__init__.py +2 -9
- celldetective/gui/about.py +2 -2
- celldetective/gui/base_annotator.py +786 -0
- celldetective/gui/classifier_widget.py +18 -13
- celldetective/gui/configure_new_exp.py +51 -30
- celldetective/gui/control_panel.py +10 -7
- celldetective/gui/{signal_annotator.py → event_annotator.py} +473 -1437
- celldetective/gui/generic_signal_plot.py +2 -1
- celldetective/gui/gui_utils.py +5 -2
- celldetective/gui/help/neighborhood.json +2 -2
- celldetective/gui/layouts.py +21 -11
- celldetective/gui/{signal_annotator2.py → pair_event_annotator.py} +3 -1
- celldetective/gui/process_block.py +129 -91
- celldetective/gui/processes/downloader.py +37 -34
- celldetective/gui/processes/measure_cells.py +14 -8
- celldetective/gui/processes/segment_cells.py +21 -6
- celldetective/gui/processes/track_cells.py +12 -13
- celldetective/gui/settings/__init__.py +7 -0
- celldetective/gui/settings/_settings_base.py +70 -0
- celldetective/gui/{retrain_signal_model_options.py → settings/_settings_event_model_training.py} +35 -91
- celldetective/gui/{measurement_options.py → settings/_settings_measurements.py} +28 -81
- celldetective/gui/{neighborhood_options.py → settings/_settings_neighborhood.py} +1 -1
- celldetective/gui/settings/_settings_segmentation.py +49 -0
- celldetective/gui/{retrain_segmentation_model_options.py → settings/_settings_segmentation_model_training.py} +33 -79
- celldetective/gui/{signal_annotator_options.py → settings/_settings_signal_annotator.py} +73 -95
- celldetective/gui/{btrack_options.py → settings/_settings_tracking.py} +64 -87
- celldetective/gui/styles.py +2 -1
- celldetective/gui/survival_ui.py +1 -1
- celldetective/gui/tableUI.py +25 -0
- celldetective/gui/table_ops/__init__.py +0 -0
- celldetective/gui/table_ops/merge_groups.py +118 -0
- celldetective/gui/viewers.py +3 -5
- celldetective/gui/workers.py +0 -2
- celldetective/io.py +98 -55
- celldetective/links/zenodo.json +145 -144
- celldetective/measure.py +31 -26
- celldetective/preprocessing.py +34 -21
- celldetective/regionprops/_regionprops.py +16 -5
- celldetective/scripts/measure_cells.py +5 -5
- celldetective/scripts/measure_relative.py +16 -11
- celldetective/scripts/segment_cells.py +4 -4
- celldetective/scripts/segment_cells_thresholds.py +3 -3
- celldetective/scripts/track_cells.py +7 -7
- celldetective/scripts/train_segmentation_model.py +10 -1
- celldetective/tracking.py +10 -4
- celldetective/utils.py +59 -58
- {celldetective-1.4.0.dist-info → celldetective-1.4.1.dist-info}/METADATA +1 -1
- celldetective-1.4.1.dist-info/RECORD +123 -0
- tests/gui/__init__.py +0 -0
- tests/gui/test_new_project.py +228 -0
- tests/{test_qt.py → gui/test_project.py} +22 -26
- tests/test_preprocessing.py +2 -2
- celldetective/models/segmentation_effectors/ricm_bf_all_last/config_input.json +0 -79
- celldetective/models/segmentation_effectors/ricm_bf_all_last/ricm_bf_all_last +0 -0
- celldetective/models/segmentation_effectors/ricm_bf_all_last/training_instructions.json +0 -37
- celldetective/models/segmentation_effectors/test-transfer/config_input.json +0 -39
- celldetective/models/segmentation_effectors/test-transfer/test-transfer +0 -0
- celldetective/models/signal_detection/NucCond/classification_loss.png +0 -0
- celldetective/models/signal_detection/NucCond/classifier.h5 +0 -0
- celldetective/models/signal_detection/NucCond/config_input.json +0 -1
- celldetective/models/signal_detection/NucCond/log_classifier.csv +0 -126
- celldetective/models/signal_detection/NucCond/log_regressor.csv +0 -282
- celldetective/models/signal_detection/NucCond/regression_loss.png +0 -0
- celldetective/models/signal_detection/NucCond/regressor.h5 +0 -0
- celldetective/models/signal_detection/NucCond/scores.npy +0 -0
- celldetective/models/signal_detection/NucCond/test_confusion_matrix.png +0 -0
- celldetective/models/signal_detection/NucCond/test_regression.png +0 -0
- celldetective/models/signal_detection/NucCond/validation_confusion_matrix.png +0 -0
- celldetective/models/signal_detection/NucCond/validation_regression.png +0 -0
- celldetective-1.4.0.dist-info/RECORD +0 -131
- {celldetective-1.4.0.dist-info → celldetective-1.4.1.dist-info}/WHEEL +0 -0
- {celldetective-1.4.0.dist-info → celldetective-1.4.1.dist-info}/entry_points.txt +0 -0
- {celldetective-1.4.0.dist-info → celldetective-1.4.1.dist-info}/licenses/LICENSE +0 -0
- {celldetective-1.4.0.dist-info → celldetective-1.4.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from PyQt5.QtCore import QSize, Qt
|
|
5
|
+
from PyQt5.QtWidgets import QComboBox, QHBoxLayout, QLabel, QLineEdit, QPushButton, QVBoxLayout
|
|
6
|
+
from fonticon_mdi6 import MDI6
|
|
7
|
+
from superqt.fonticon import icon
|
|
8
|
+
|
|
9
|
+
from celldetective.gui import CelldetectiveWidget
|
|
10
|
+
from celldetective.gui.gui_utils import PandasModel, center_window
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MergeGroupWidget(CelldetectiveWidget):
|
|
14
|
+
def __init__(self, parent_window, columns: List[str] = [], n_cols_init: int = 3):
|
|
15
|
+
|
|
16
|
+
super().__init__()
|
|
17
|
+
self.parent_window = parent_window
|
|
18
|
+
|
|
19
|
+
self.setWindowTitle("Merge classifications")
|
|
20
|
+
self.group_cols = [c for c in list(self.parent_window.data.columns) if c.startswith("group_") or c.startswith("status_")]
|
|
21
|
+
self.group_cols.insert(0, "--")
|
|
22
|
+
if len(columns) > n_cols_init:
|
|
23
|
+
n_cols_init = len(columns)
|
|
24
|
+
|
|
25
|
+
center_window(self)
|
|
26
|
+
|
|
27
|
+
layout = QVBoxLayout(self)
|
|
28
|
+
layout.setContentsMargins(30, 10, 30, 30)
|
|
29
|
+
|
|
30
|
+
label = QLabel(
|
|
31
|
+
"Merge several binary or multi-label classification features into a multi-label classification feature where each state is one of the possible combinations.\n")
|
|
32
|
+
|
|
33
|
+
label.setWordWrap(True) # enables automatic line breaking
|
|
34
|
+
label.setTextInteractionFlags(Qt.TextSelectableByMouse) # optional, to allow copy
|
|
35
|
+
label.setStyleSheet("color: gray;") # optional style
|
|
36
|
+
|
|
37
|
+
layout.addWidget(label)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
self.name_le = QLineEdit("group_multilabel")
|
|
41
|
+
name_layout = QHBoxLayout()
|
|
42
|
+
name_layout.addWidget(QLabel("name: "), 25)
|
|
43
|
+
name_layout.addWidget(self.name_le, 75)
|
|
44
|
+
layout.addLayout(name_layout)
|
|
45
|
+
|
|
46
|
+
self.cbs_layout = QVBoxLayout()
|
|
47
|
+
self.cbs_layout.setContentsMargins(0,10,0,0)
|
|
48
|
+
|
|
49
|
+
self.cbs = []
|
|
50
|
+
for i in range(n_cols_init):
|
|
51
|
+
cb_i = QComboBox()
|
|
52
|
+
cb_i.addItems(self.group_cols)
|
|
53
|
+
if i < len(columns):
|
|
54
|
+
selection = columns[i]
|
|
55
|
+
idx = cb_i.findText(selection)
|
|
56
|
+
if idx>=0:
|
|
57
|
+
cb_i.setCurrentIndex(idx)
|
|
58
|
+
else:
|
|
59
|
+
cb_i.setCurrentIndex(0)
|
|
60
|
+
self.cbs.append(cb_i)
|
|
61
|
+
|
|
62
|
+
col_layout = QHBoxLayout()
|
|
63
|
+
col_layout.addWidget(QLabel(f'state {i}: '), 25)
|
|
64
|
+
col_layout.addWidget(cb_i, 75)
|
|
65
|
+
self.cbs_layout.addLayout(col_layout)
|
|
66
|
+
|
|
67
|
+
layout.addLayout(self.cbs_layout)
|
|
68
|
+
|
|
69
|
+
self.add_feature_btn = QPushButton()
|
|
70
|
+
self.add_feature_btn.setIcon(icon(MDI6.plus, color=self.help_color))
|
|
71
|
+
self.add_feature_btn.setIconSize(QSize(20, 20))
|
|
72
|
+
self.add_feature_btn.clicked.connect(self.add_col)
|
|
73
|
+
self.add_feature_btn.setStyleSheet(self.button_select_all)
|
|
74
|
+
layout.addWidget(self.add_feature_btn, alignment=Qt.AlignRight)
|
|
75
|
+
|
|
76
|
+
self.submit_btn = QPushButton('Compute')
|
|
77
|
+
self.submit_btn.setStyleSheet(self.button_style_sheet)
|
|
78
|
+
self.submit_btn.clicked.connect(self.compute)
|
|
79
|
+
layout.addWidget(self.submit_btn, 30)
|
|
80
|
+
|
|
81
|
+
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
82
|
+
|
|
83
|
+
def add_col(self):
|
|
84
|
+
cb_i = QComboBox()
|
|
85
|
+
cb_i.addItems(self.group_cols)
|
|
86
|
+
self.cbs.append(cb_i)
|
|
87
|
+
|
|
88
|
+
col_layout = QHBoxLayout()
|
|
89
|
+
col_layout.addWidget(QLabel(f'state {len(self.cbs)}: '), 25)
|
|
90
|
+
col_layout.addWidget(cb_i, 75)
|
|
91
|
+
|
|
92
|
+
self.cbs_layout.addLayout(col_layout)
|
|
93
|
+
|
|
94
|
+
def compute(self):
|
|
95
|
+
|
|
96
|
+
cols_to_merge = [cb_i.currentText() for cb_i in self.cbs if cb_i.currentText() != "--"]
|
|
97
|
+
name = self.name_le.text()
|
|
98
|
+
if " " in name:
|
|
99
|
+
name.replace(" ","_")
|
|
100
|
+
if name == '':
|
|
101
|
+
name = "multilabel"
|
|
102
|
+
if not name.startswith("group_"):
|
|
103
|
+
name = "group_" + name
|
|
104
|
+
|
|
105
|
+
if len(cols_to_merge) > 1:
|
|
106
|
+
print("Computing a multi-label classification from the classification feature sources...")
|
|
107
|
+
bases = [int(self.parent_window.data[c].max()) + 1 for c in cols_to_merge]
|
|
108
|
+
multipliers = np.concatenate(([1], np.cumprod(bases[:-1])))
|
|
109
|
+
self.parent_window.data[name] = (self.parent_window.data[cols_to_merge] * multipliers).sum(axis=1)
|
|
110
|
+
self.parent_window.data.loc[self.parent_window.data[cols_to_merge].isna().any(axis=1), name] = np.nan
|
|
111
|
+
|
|
112
|
+
self.parent_window.model = PandasModel(self.parent_window.data)
|
|
113
|
+
self.parent_window.table_view.setModel(self.parent_window.model)
|
|
114
|
+
self.close()
|
|
115
|
+
elif len(cols_to_merge) == 1:
|
|
116
|
+
print("Only one classification feature was selected, nothing to merge...")
|
|
117
|
+
else:
|
|
118
|
+
print("No classification feature was selected...")
|
celldetective/gui/viewers.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import numpy as np
|
|
2
1
|
from celldetective.io import auto_load_number_of_frames, load_frames
|
|
3
2
|
from celldetective.filters import *
|
|
4
3
|
from celldetective.segmentation import filter_image, threshold_image
|
|
@@ -11,17 +10,16 @@ from natsort import natsorted
|
|
|
11
10
|
from glob import glob
|
|
12
11
|
import os
|
|
13
12
|
|
|
14
|
-
from PyQt5.QtWidgets import QHBoxLayout, QPushButton, QLabel, QComboBox, QLineEdit, QListWidget, QShortcut
|
|
13
|
+
from PyQt5.QtWidgets import QHBoxLayout, QMessageBox, QPushButton, QLabel, QComboBox, QLineEdit, QListWidget, QShortcut
|
|
15
14
|
from PyQt5.QtCore import Qt, QSize
|
|
16
15
|
from PyQt5.QtGui import QKeySequence, QDoubleValidator
|
|
17
|
-
from celldetective.gui.gui_utils import FigureCanvas,
|
|
18
|
-
from celldetective.gui import
|
|
16
|
+
from celldetective.gui.gui_utils import FigureCanvas, QuickSliderLayout, QHSeperationLine, ThresholdLineEdit, PreprocessingLayout2
|
|
17
|
+
from celldetective.gui import CelldetectiveWidget
|
|
19
18
|
from superqt import QLabeledDoubleSlider, QLabeledSlider, QLabeledDoubleRangeSlider
|
|
20
19
|
from superqt.fonticon import icon
|
|
21
20
|
from fonticon_mdi6 import MDI6
|
|
22
21
|
from matplotlib_scalebar.scalebar import ScaleBar
|
|
23
22
|
import gc
|
|
24
|
-
from celldetective.utils import mask_edges
|
|
25
23
|
from scipy.ndimage import shift
|
|
26
24
|
|
|
27
25
|
class StackVisualizer(CelldetectiveWidget):
|
celldetective/gui/workers.py
CHANGED
|
@@ -4,8 +4,6 @@ from PyQt5.QtCore import QRunnable, QObject, pyqtSignal, QThreadPool, QSize, Qt
|
|
|
4
4
|
|
|
5
5
|
from celldetective.gui.base_components import CelldetectiveDialog
|
|
6
6
|
from celldetective.gui.gui_utils import center_window
|
|
7
|
-
from celldetective.gui import Styles
|
|
8
|
-
import time
|
|
9
7
|
import math
|
|
10
8
|
|
|
11
9
|
class ProgressWindow(CelldetectiveDialog):
|
celldetective/io.py
CHANGED
|
@@ -24,13 +24,18 @@ from magicgui import magicgui
|
|
|
24
24
|
from pathlib import Path, PurePath
|
|
25
25
|
from shutil import copyfile, rmtree
|
|
26
26
|
|
|
27
|
-
from celldetective.utils import _rearrange_multichannel_frame, _fix_no_contrast, zoom_multiframes,
|
|
28
|
-
|
|
27
|
+
from celldetective.utils import _rearrange_multichannel_frame, _fix_no_contrast, zoom_multiframes, \
|
|
28
|
+
config_section_to_dict, extract_experiment_channels, _extract_labels_from_config, get_zenodo_files, \
|
|
29
|
+
download_zenodo_file
|
|
30
|
+
from celldetective.utils import interpolate_nan_multichannel, get_config
|
|
29
31
|
|
|
30
32
|
from stardist import fill_label_holes
|
|
31
33
|
from skimage.transform import resize
|
|
32
34
|
import re
|
|
33
35
|
|
|
36
|
+
from typing import List, Tuple, Union
|
|
37
|
+
import numbers
|
|
38
|
+
|
|
34
39
|
def extract_experiment_from_well(well_path):
|
|
35
40
|
|
|
36
41
|
"""
|
|
@@ -202,6 +207,9 @@ def collect_experiment_metadata(pos_path=None, well_path=None):
|
|
|
202
207
|
if not well_path.endswith(os.sep):
|
|
203
208
|
well_path += os.sep
|
|
204
209
|
experiment = extract_experiment_from_well(well_path)
|
|
210
|
+
else:
|
|
211
|
+
print("Please provide a position or well path...")
|
|
212
|
+
return None
|
|
205
213
|
|
|
206
214
|
wells = list(get_experiment_wells(experiment))
|
|
207
215
|
idx = wells.index(well_path)
|
|
@@ -211,7 +219,14 @@ def collect_experiment_metadata(pos_path=None, well_path=None):
|
|
|
211
219
|
else:
|
|
212
220
|
pos_name = 0
|
|
213
221
|
|
|
214
|
-
dico = {"pos_path": pos_path,
|
|
222
|
+
dico = {"pos_path": pos_path,
|
|
223
|
+
"position": pos_path,
|
|
224
|
+
"pos_name": pos_name,
|
|
225
|
+
"well_path": well_path,
|
|
226
|
+
"well_name": well_name,
|
|
227
|
+
"well_nbr": well_nbr,
|
|
228
|
+
"experiment": experiment,
|
|
229
|
+
}
|
|
215
230
|
|
|
216
231
|
meta = get_experiment_metadata(experiment) # None or dict of metadata
|
|
217
232
|
if meta is not None:
|
|
@@ -306,9 +321,9 @@ def get_spatial_calibration(experiment):
|
|
|
306
321
|
"""
|
|
307
322
|
|
|
308
323
|
config = get_config(experiment)
|
|
309
|
-
|
|
324
|
+
px_to_um = float(config_section_to_dict(config, "MovieSettings")["pxtoum"])
|
|
310
325
|
|
|
311
|
-
return
|
|
326
|
+
return px_to_um
|
|
312
327
|
|
|
313
328
|
|
|
314
329
|
def get_temporal_calibration(experiment):
|
|
@@ -351,14 +366,14 @@ def get_temporal_calibration(experiment):
|
|
|
351
366
|
"""
|
|
352
367
|
|
|
353
368
|
config = get_config(experiment)
|
|
354
|
-
|
|
369
|
+
frame_to_min = float(config_section_to_dict(config, "MovieSettings")["frametomin"])
|
|
355
370
|
|
|
356
|
-
return
|
|
371
|
+
return frame_to_min
|
|
357
372
|
|
|
358
373
|
def get_experiment_metadata(experiment):
|
|
359
374
|
|
|
360
375
|
config = get_config(experiment)
|
|
361
|
-
metadata =
|
|
376
|
+
metadata = config_section_to_dict(config, "Metadata")
|
|
362
377
|
return metadata
|
|
363
378
|
|
|
364
379
|
def get_experiment_labels(experiment):
|
|
@@ -367,12 +382,12 @@ def get_experiment_labels(experiment):
|
|
|
367
382
|
wells = get_experiment_wells(experiment)
|
|
368
383
|
nbr_of_wells = len(wells)
|
|
369
384
|
|
|
370
|
-
labels =
|
|
385
|
+
labels = config_section_to_dict(config, "Labels")
|
|
371
386
|
for k in list(labels.keys()):
|
|
372
387
|
values = labels[k].split(',')
|
|
373
388
|
if nbr_of_wells != len(values):
|
|
374
389
|
values = [str(s) for s in np.linspace(0, nbr_of_wells - 1, nbr_of_wells)]
|
|
375
|
-
if np.all([s.isnumeric() for s in values]):
|
|
390
|
+
if np.all(np.array([s.isnumeric() for s in values])):
|
|
376
391
|
values = [float(s) for s in values]
|
|
377
392
|
labels.update({k: values})
|
|
378
393
|
|
|
@@ -427,7 +442,7 @@ def get_experiment_concentrations(experiment, dtype=str):
|
|
|
427
442
|
wells = get_experiment_wells(experiment)
|
|
428
443
|
nbr_of_wells = len(wells)
|
|
429
444
|
|
|
430
|
-
concentrations =
|
|
445
|
+
concentrations = config_section_to_dict(config, "Labels")["concentrations"].split(",")
|
|
431
446
|
if nbr_of_wells != len(concentrations):
|
|
432
447
|
concentrations = [str(s) for s in np.linspace(0, nbr_of_wells - 1, nbr_of_wells)]
|
|
433
448
|
|
|
@@ -482,7 +497,7 @@ def get_experiment_cell_types(experiment, dtype=str):
|
|
|
482
497
|
wells = get_experiment_wells(experiment)
|
|
483
498
|
nbr_of_wells = len(wells)
|
|
484
499
|
|
|
485
|
-
cell_types =
|
|
500
|
+
cell_types = config_section_to_dict(config, "Labels")["cell_types"].split(",")
|
|
486
501
|
if nbr_of_wells != len(cell_types):
|
|
487
502
|
cell_types = [str(s) for s in np.linspace(0, nbr_of_wells - 1, nbr_of_wells)]
|
|
488
503
|
|
|
@@ -534,7 +549,7 @@ def get_experiment_antibodies(experiment, dtype=str):
|
|
|
534
549
|
wells = get_experiment_wells(experiment)
|
|
535
550
|
nbr_of_wells = len(wells)
|
|
536
551
|
|
|
537
|
-
antibodies =
|
|
552
|
+
antibodies = config_section_to_dict(config, "Labels")["antibodies"].split(",")
|
|
538
553
|
if nbr_of_wells != len(antibodies):
|
|
539
554
|
antibodies = [str(s) for s in np.linspace(0, nbr_of_wells - 1, nbr_of_wells)]
|
|
540
555
|
|
|
@@ -589,7 +604,7 @@ def get_experiment_pharmaceutical_agents(experiment, dtype=str):
|
|
|
589
604
|
wells = get_experiment_wells(experiment)
|
|
590
605
|
nbr_of_wells = len(wells)
|
|
591
606
|
|
|
592
|
-
pharmaceutical_agents =
|
|
607
|
+
pharmaceutical_agents = config_section_to_dict(config, "Labels")["pharmaceutical_agents"].split(",")
|
|
593
608
|
if nbr_of_wells != len(pharmaceutical_agents):
|
|
594
609
|
pharmaceutical_agents = [str(s) for s in np.linspace(0, nbr_of_wells - 1, nbr_of_wells)]
|
|
595
610
|
|
|
@@ -599,7 +614,7 @@ def get_experiment_pharmaceutical_agents(experiment, dtype=str):
|
|
|
599
614
|
def get_experiment_populations(experiment, dtype=str):
|
|
600
615
|
|
|
601
616
|
config = get_config(experiment)
|
|
602
|
-
populations_str =
|
|
617
|
+
populations_str = config_section_to_dict(config, "Populations")
|
|
603
618
|
if populations_str is not None:
|
|
604
619
|
populations = populations_str['populations'].split(',')
|
|
605
620
|
else:
|
|
@@ -607,7 +622,7 @@ def get_experiment_populations(experiment, dtype=str):
|
|
|
607
622
|
return list([dtype(c) for c in populations])
|
|
608
623
|
|
|
609
624
|
|
|
610
|
-
def interpret_wells_and_positions(experiment, well_option, position_option):
|
|
625
|
+
def interpret_wells_and_positions(experiment: str, well_option: Union[str,int,List[int]], position_option: Union[str,int,List[int]]) -> Union[Tuple[List[int], List[int]], None]:
|
|
611
626
|
"""
|
|
612
627
|
Interpret well and position options for a given experiment.
|
|
613
628
|
|
|
@@ -617,8 +632,8 @@ def interpret_wells_and_positions(experiment, well_option, position_option):
|
|
|
617
632
|
|
|
618
633
|
Parameters
|
|
619
634
|
----------
|
|
620
|
-
experiment :
|
|
621
|
-
The experiment
|
|
635
|
+
experiment : str
|
|
636
|
+
The experiment path containing well information.
|
|
622
637
|
well_option : str, int, or list of int
|
|
623
638
|
The well selection option:
|
|
624
639
|
- '*' : Select all wells.
|
|
@@ -660,6 +675,9 @@ def interpret_wells_and_positions(experiment, well_option, position_option):
|
|
|
660
675
|
well_indices = [int(well_option)]
|
|
661
676
|
elif isinstance(well_option, list):
|
|
662
677
|
well_indices = well_option
|
|
678
|
+
else:
|
|
679
|
+
print("Well indices could not be interpreted...")
|
|
680
|
+
return None
|
|
663
681
|
|
|
664
682
|
if position_option == '*':
|
|
665
683
|
position_indices = None
|
|
@@ -667,6 +685,9 @@ def interpret_wells_and_positions(experiment, well_option, position_option):
|
|
|
667
685
|
position_indices = np.array([position_option], dtype=int)
|
|
668
686
|
elif isinstance(position_option, list):
|
|
669
687
|
position_indices = position_option
|
|
688
|
+
else:
|
|
689
|
+
print("Position indices could not be interpreted...")
|
|
690
|
+
return None
|
|
670
691
|
|
|
671
692
|
return well_indices, position_indices
|
|
672
693
|
|
|
@@ -795,7 +816,11 @@ def get_position_table(pos, population, return_path=False):
|
|
|
795
816
|
table = pos + os.sep.join(['output', 'tables', f'trajectories_{population}.csv'])
|
|
796
817
|
|
|
797
818
|
if os.path.exists(table):
|
|
798
|
-
|
|
819
|
+
try:
|
|
820
|
+
df_pos = pd.read_csv(table, low_memory=False)
|
|
821
|
+
except Exception as e:
|
|
822
|
+
print(e)
|
|
823
|
+
df_pos = None
|
|
799
824
|
else:
|
|
800
825
|
df_pos = None
|
|
801
826
|
|
|
@@ -970,7 +995,7 @@ def load_experiment_tables(experiment, population='targets', well_option='*', po
|
|
|
970
995
|
config = get_config(experiment)
|
|
971
996
|
wells = get_experiment_wells(experiment)
|
|
972
997
|
|
|
973
|
-
movie_prefix =
|
|
998
|
+
movie_prefix = config_section_to_dict(config, "MovieSettings")["movie_prefix"]
|
|
974
999
|
|
|
975
1000
|
labels = get_experiment_labels(experiment)
|
|
976
1001
|
metadata = get_experiment_metadata(experiment) # None or dict of metadata
|
|
@@ -1027,15 +1052,23 @@ def load_experiment_tables(experiment, population='targets', well_option='*', po
|
|
|
1027
1052
|
|
|
1028
1053
|
if metadata is not None:
|
|
1029
1054
|
keys = list(metadata.keys())
|
|
1030
|
-
for
|
|
1031
|
-
df_pos[
|
|
1055
|
+
for key in keys:
|
|
1056
|
+
df_pos[key] = metadata[key]
|
|
1032
1057
|
|
|
1033
1058
|
df.append(df_pos)
|
|
1034
1059
|
any_table = True
|
|
1035
1060
|
|
|
1036
|
-
pos_dict = {'pos_path': pos_path,
|
|
1037
|
-
|
|
1038
|
-
|
|
1061
|
+
pos_dict = {'pos_path': pos_path,
|
|
1062
|
+
'pos_index': real_pos_index,
|
|
1063
|
+
'pos_name': pos_name,
|
|
1064
|
+
'table_path': table,
|
|
1065
|
+
'stack_path': stack_path,
|
|
1066
|
+
'well_path': well_path,
|
|
1067
|
+
'well_index': real_well_index,
|
|
1068
|
+
'well_name': well_name,
|
|
1069
|
+
'well_number': well_number,
|
|
1070
|
+
'well_alias': well_alias,
|
|
1071
|
+
}
|
|
1039
1072
|
|
|
1040
1073
|
df_pos_info.append(pos_dict)
|
|
1041
1074
|
|
|
@@ -1098,7 +1131,9 @@ def locate_stack(position, prefix='Aligned'):
|
|
|
1098
1131
|
position += os.sep
|
|
1099
1132
|
|
|
1100
1133
|
stack_path = glob(position + os.sep.join(['movie', f'{prefix}*.tif']))
|
|
1101
|
-
|
|
1134
|
+
if not stack_path:
|
|
1135
|
+
raise FileNotFoundError(f"No movie with prefix {prefix} found...")
|
|
1136
|
+
|
|
1102
1137
|
stack = imread(stack_path[0].replace('\\', '/'))
|
|
1103
1138
|
stack_length = auto_load_number_of_frames(stack_path[0])
|
|
1104
1139
|
|
|
@@ -1417,7 +1452,7 @@ def auto_load_number_of_frames(stack_path):
|
|
|
1417
1452
|
|
|
1418
1453
|
Handle invalid or missing paths gracefully:
|
|
1419
1454
|
|
|
1420
|
-
>>> frames = auto_load_number_of_frames(
|
|
1455
|
+
>>> frames = auto_load_number_of_frames("stack.tif")
|
|
1421
1456
|
>>> print(frames)
|
|
1422
1457
|
None
|
|
1423
1458
|
|
|
@@ -1520,7 +1555,7 @@ def parse_isotropic_radii(string):
|
|
|
1520
1555
|
|
|
1521
1556
|
"""
|
|
1522
1557
|
|
|
1523
|
-
sections = re.split(
|
|
1558
|
+
sections = re.split(r"[ ,]", string)
|
|
1524
1559
|
radii = []
|
|
1525
1560
|
for k, s in enumerate(sections):
|
|
1526
1561
|
if s.isdigit():
|
|
@@ -2344,7 +2379,7 @@ def load_napari_data(position, prefix="Aligned", population="target", return_sta
|
|
|
2344
2379
|
position : str
|
|
2345
2380
|
The path to the position or experiment directory.
|
|
2346
2381
|
prefix : str, optional
|
|
2347
|
-
The prefix used to identify the
|
|
2382
|
+
The prefix used to identify the movie file. The default is "Aligned".
|
|
2348
2383
|
population : str, optional
|
|
2349
2384
|
The population type to load, either "target" or "effector". The default is "target".
|
|
2350
2385
|
|
|
@@ -2396,7 +2431,7 @@ def load_napari_data(position, prefix="Aligned", population="target", return_sta
|
|
|
2396
2431
|
return data, properties, graph, labels, stack
|
|
2397
2432
|
|
|
2398
2433
|
|
|
2399
|
-
def auto_correct_masks(masks, bbox_factor = 1.75, min_area=9, fill_labels=False):
|
|
2434
|
+
def auto_correct_masks(masks, bbox_factor: float = 1.75, min_area: int = 9, fill_labels: bool = False):
|
|
2400
2435
|
|
|
2401
2436
|
"""
|
|
2402
2437
|
Correct segmentation masks to ensure consistency and remove anomalies.
|
|
@@ -2416,6 +2451,13 @@ def auto_correct_masks(masks, bbox_factor = 1.75, min_area=9, fill_labels=False)
|
|
|
2416
2451
|
masks : np.ndarray
|
|
2417
2452
|
A 2D array representing the segmented mask image with labeled regions. Each unique value
|
|
2418
2453
|
in the array represents a different object or cell.
|
|
2454
|
+
bbox_factor : float, optional
|
|
2455
|
+
A factor on cell area that is compared directly to the bounding box area of the cell, to detect remote cells
|
|
2456
|
+
sharing a same label value. The default is `1.75`.
|
|
2457
|
+
min_area : int, optional
|
|
2458
|
+
Discard cells that have an area smaller than this minimum area (px²). The default is `9` (3x3 pixels).
|
|
2459
|
+
fill_labels : bool, optional
|
|
2460
|
+
Fill holes within cell masks automatically. The default is `False`.
|
|
2419
2461
|
|
|
2420
2462
|
Returns
|
|
2421
2463
|
-------
|
|
@@ -2439,6 +2481,8 @@ def auto_correct_masks(masks, bbox_factor = 1.75, min_area=9, fill_labels=False)
|
|
|
2439
2481
|
[0, 2, 2, 1],
|
|
2440
2482
|
[0, 2, 0, 0]])
|
|
2441
2483
|
"""
|
|
2484
|
+
|
|
2485
|
+
assert masks.ndim==2,"`masks` should be a 2D numpy array..."
|
|
2442
2486
|
|
|
2443
2487
|
# Avoid negative mask values
|
|
2444
2488
|
masks[masks<0] = np.abs(masks[masks<0])
|
|
@@ -2503,6 +2547,8 @@ def control_segmentation_napari(position, prefix='Aligned', population="target",
|
|
|
2503
2547
|
The prefix used to identify the stack. The default is 'Aligned'.
|
|
2504
2548
|
population : str, optional
|
|
2505
2549
|
The population type for which the segmentation is performed. The default is 'target'.
|
|
2550
|
+
flush_memory : bool, optional
|
|
2551
|
+
Pop napari layers upon closing the viewer to empty the memory footprint. The default is `False`.
|
|
2506
2552
|
|
|
2507
2553
|
Notes
|
|
2508
2554
|
-----
|
|
@@ -2559,7 +2605,7 @@ def control_segmentation_napari(position, prefix='Aligned', population="target",
|
|
|
2559
2605
|
for k in keys:
|
|
2560
2606
|
info.update({k: metadata_info[k]})
|
|
2561
2607
|
|
|
2562
|
-
spatial_calibration = float(
|
|
2608
|
+
spatial_calibration = float(config_section_to_dict(config, "MovieSettings")["pxtoum"])
|
|
2563
2609
|
channel_names, channel_indices = extract_experiment_channels(expfolder)
|
|
2564
2610
|
|
|
2565
2611
|
annotation_folder = expfolder + os.sep + f'annotations_{population}' + os.sep
|
|
@@ -2727,6 +2773,12 @@ def correct_annotation(filename):
|
|
|
2727
2773
|
def save_widget():
|
|
2728
2774
|
return export_labels()
|
|
2729
2775
|
|
|
2776
|
+
if filename.endswith("_labelled.tif"):
|
|
2777
|
+
filename = filename.replace("_labelled.tif",".tif")
|
|
2778
|
+
if filename.endswith(".json"):
|
|
2779
|
+
filename = filename.replace('.json',".tif")
|
|
2780
|
+
assert os.path.exists(filename),f"Image {filename} does not seem to exist..."
|
|
2781
|
+
|
|
2730
2782
|
img = imread(filename.replace('\\','/'))
|
|
2731
2783
|
if img.ndim==3:
|
|
2732
2784
|
img = np.moveaxis(img, 0, -1)
|
|
@@ -2791,7 +2843,7 @@ def _view_on_napari(tracks=None, stack=None, labels=None):
|
|
|
2791
2843
|
... 'x': [10, 20, 30], 'y': [15, 25, 35]})
|
|
2792
2844
|
>>> stack = np.random.rand(100, 100, 3)
|
|
2793
2845
|
>>> labels = np.random.randint(0, 2, (100, 100))
|
|
2794
|
-
>>>
|
|
2846
|
+
>>> _view_on_napari(tracks, stack=stack, labels=labels)
|
|
2795
2847
|
# Visualize tracks, stack, and labels using Napari.
|
|
2796
2848
|
|
|
2797
2849
|
"""
|
|
@@ -2864,22 +2916,6 @@ def get_segmentation_models_list(mode='targets', return_path=False):
|
|
|
2864
2916
|
else:
|
|
2865
2917
|
repository_models = get_zenodo_files(cat=os.sep.join(["models", f"segmentation_{mode}"]))
|
|
2866
2918
|
|
|
2867
|
-
# if mode == 'targets':
|
|
2868
|
-
# modelpath = os.sep.join(
|
|
2869
|
-
# [os.path.split(os.path.dirname(os.path.realpath(__file__)))[0], "celldetective", "models",
|
|
2870
|
-
# "segmentation_targets", os.sep])
|
|
2871
|
-
# repository_models = get_zenodo_files(cat=os.sep.join(["models", "segmentation_targets"]))
|
|
2872
|
-
# elif mode == 'effectors':
|
|
2873
|
-
# modelpath = os.sep.join(
|
|
2874
|
-
# [os.path.split(os.path.dirname(os.path.realpath(__file__)))[0], "celldetective", "models",
|
|
2875
|
-
# "segmentation_effectors", os.sep])
|
|
2876
|
-
# repository_models = get_zenodo_files(cat=os.sep.join(["models", "segmentation_effectors"]))
|
|
2877
|
-
# elif mode == 'generic':
|
|
2878
|
-
# modelpath = os.sep.join(
|
|
2879
|
-
# [os.path.split(os.path.dirname(os.path.realpath(__file__)))[0], "celldetective", "models",
|
|
2880
|
-
# "segmentation_generic", os.sep])
|
|
2881
|
-
# repository_models = get_zenodo_files(cat=os.sep.join(["models", "segmentation_generic"]))
|
|
2882
|
-
|
|
2883
2919
|
available_models = natsorted(glob(modelpath + '*/'))
|
|
2884
2920
|
available_models = [m.replace('\\', '/').split('/')[-2] for m in available_models]
|
|
2885
2921
|
|
|
@@ -3199,7 +3235,7 @@ def normalize(frame, percentiles=(0.0,99.99), values=None, ignore_gray_value=0.,
|
|
|
3199
3235
|
subframe = frame.copy()
|
|
3200
3236
|
|
|
3201
3237
|
if values is not None:
|
|
3202
|
-
mi = values[0]
|
|
3238
|
+
mi = values[0]
|
|
3203
3239
|
ma = values[1]
|
|
3204
3240
|
else:
|
|
3205
3241
|
mi = np.nanpercentile(subframe.flatten(), percentiles[0], keepdims=True)
|
|
@@ -3220,9 +3256,14 @@ def normalize(frame, percentiles=(0.0,99.99), values=None, ignore_gray_value=0.,
|
|
|
3220
3256
|
return frame.copy().astype(dtype)
|
|
3221
3257
|
|
|
3222
3258
|
|
|
3223
|
-
def normalize_multichannel(multichannel_frame
|
|
3224
|
-
|
|
3225
|
-
|
|
3259
|
+
def normalize_multichannel(multichannel_frame: np.ndarray,
|
|
3260
|
+
percentiles=None,
|
|
3261
|
+
values=None,
|
|
3262
|
+
ignore_gray_value=0.,
|
|
3263
|
+
clip=False,
|
|
3264
|
+
amplification=None,
|
|
3265
|
+
dtype=float,
|
|
3266
|
+
):
|
|
3226
3267
|
|
|
3227
3268
|
"""
|
|
3228
3269
|
Normalizes a multichannel frame by adjusting the intensity values of each channel based on specified percentiles,
|
|
@@ -3271,13 +3312,11 @@ def normalize_multichannel(multichannel_frame, percentiles=None,
|
|
|
3271
3312
|
Examples
|
|
3272
3313
|
--------
|
|
3273
3314
|
>>> multichannel_frame = np.random.rand(100, 100, 3) # Example multichannel frame
|
|
3274
|
-
>>> normalized_frame = normalize_multichannel(multichannel_frame, percentiles=(
|
|
3315
|
+
>>> normalized_frame = normalize_multichannel(multichannel_frame, percentiles=[(1, 99), (2, 98), (0, 100)])
|
|
3275
3316
|
# Normalizes each channel of the frame using specified percentile ranges.
|
|
3276
3317
|
|
|
3277
3318
|
"""
|
|
3278
3319
|
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
3320
|
mf = multichannel_frame.copy().astype(float)
|
|
3282
3321
|
assert mf.ndim == 3, f'Wrong shape for the multichannel frame: {mf.shape}.'
|
|
3283
3322
|
if percentiles is None:
|
|
@@ -3371,6 +3410,10 @@ def load_frames(img_nums, stack_path, scale=None, normalize_input=True, dtype=np
|
|
|
3371
3410
|
print(
|
|
3372
3411
|
f'Error in loading the frame {img_nums} {e}. Please check that the experiment channel information is consistent with the movie being read.')
|
|
3373
3412
|
return None
|
|
3413
|
+
try:
|
|
3414
|
+
frames[np.isinf(frames)] = np.nan
|
|
3415
|
+
except Exception as e:
|
|
3416
|
+
print(e)
|
|
3374
3417
|
|
|
3375
3418
|
frames = _rearrange_multichannel_frame(frames)
|
|
3376
3419
|
|