celldetective 1.1.1.post3__tar.gz → 1.2.0__tar.gz
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-1.1.1.post3 → celldetective-1.2.0}/PKG-INFO +1 -1
- celldetective-1.2.0/celldetective/__init__.py +3 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/__main__.py +17 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/extra_properties.py +62 -34
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/gui/__init__.py +1 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/gui/analyze_block.py +2 -1
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/gui/classifier_widget.py +18 -10
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/gui/control_panel.py +57 -6
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/gui/layouts.py +14 -11
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/gui/neighborhood_options.py +21 -13
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/gui/plot_signals_ui.py +39 -11
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/gui/process_block.py +413 -95
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/gui/retrain_segmentation_model_options.py +17 -4
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/gui/retrain_signal_model_options.py +106 -6
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/gui/signal_annotator.py +110 -30
- celldetective-1.2.0/celldetective/gui/signal_annotator2.py +2708 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/gui/signal_annotator_options.py +3 -1
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/gui/survival_ui.py +15 -6
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/gui/tableUI.py +248 -43
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/io.py +598 -416
- celldetective-1.2.0/celldetective/measure.py +988 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/neighborhood.py +482 -340
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/preprocessing.py +81 -61
- celldetective-1.2.0/celldetective/relative_measurements.py +648 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/scripts/analyze_signals.py +1 -1
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/scripts/measure_cells.py +28 -8
- celldetective-1.2.0/celldetective/scripts/measure_relative.py +103 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/scripts/segment_cells.py +5 -5
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/scripts/track_cells.py +4 -1
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/scripts/train_segmentation_model.py +23 -18
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/scripts/train_signal_model.py +33 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/segmentation.py +67 -29
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/signals.py +402 -8
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/tracking.py +8 -2
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/utils.py +144 -12
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective.egg-info/PKG-INFO +1 -1
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective.egg-info/SOURCES.txt +4 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/setup.py +1 -1
- celldetective-1.2.0/tests/__init__.py +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/tests/test_segmentation.py +1 -1
- celldetective-1.1.1.post3/celldetective/__init__.py +0 -2
- celldetective-1.1.1.post3/celldetective/measure.py +0 -1038
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/LICENSE +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/README.md +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/datasets/segmentation_annotations/blank +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/datasets/signal_annotations/blank +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/events.py +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/filters.py +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/gui/about.py +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/gui/btrack_options.py +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/gui/configure_new_exp.py +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/gui/gui_utils.py +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/gui/json_readers.py +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/gui/measurement_options.py +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/gui/plot_measurements.py +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/gui/seg_model_loader.py +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/gui/styles.py +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/gui/thresholds_gui.py +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/gui/viewers.py +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/icons/logo-large.png +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/icons/logo.png +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/icons/signals_icon.png +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/icons/splash-test.png +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/icons/splash.png +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/icons/splash0.png +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/icons/survival2.png +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/icons/vignette_signals2.png +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/icons/vignette_signals2.svg +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/links/zenodo.json +0 -0
- {celldetective-1.1.1.post3/celldetective/models/segmentation_effectors → celldetective-1.2.0/celldetective/models/pair_signal_detection}/blank +0 -0
- {celldetective-1.1.1.post3/celldetective/models/segmentation_generic → celldetective-1.2.0/celldetective/models/segmentation_effectors}/blank +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/models/segmentation_effectors/primNK_cfse/config_input.json +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/models/segmentation_effectors/primNK_cfse/cp-cfse-transfer +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/models/segmentation_effectors/primNK_cfse/training_instructions.json +0 -0
- {celldetective-1.1.1.post3/celldetective/models/segmentation_targets → celldetective-1.2.0/celldetective/models/segmentation_generic}/blank +0 -0
- {celldetective-1.1.1.post3/celldetective/models/signal_detection → celldetective-1.2.0/celldetective/models/segmentation_targets}/blank +0 -0
- /celldetective-1.1.1.post3/tests/__init__.py → /celldetective-1.2.0/celldetective/models/signal_detection/blank +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/models/tracking_configs/mcf7.json +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/models/tracking_configs/ricm.json +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/models/tracking_configs/ricm2.json +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective/scripts/segment_cells_thresholds.py +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective.egg-info/dependency_links.txt +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective.egg-info/entry_points.txt +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective.egg-info/not-zip-safe +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective.egg-info/requires.txt +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/celldetective.egg-info/top_level.txt +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/setup.cfg +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/tests/test_events.py +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/tests/test_filters.py +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/tests/test_io.py +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/tests/test_measure.py +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/tests/test_neighborhood.py +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/tests/test_preprocessing.py +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/tests/test_signals.py +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/tests/test_tracking.py +0 -0
- {celldetective-1.1.1.post3 → celldetective-1.2.0}/tests/test_utils.py +0 -0
|
@@ -108,6 +108,10 @@ class AppInitWindow(QMainWindow):
|
|
|
108
108
|
OptionsMenu.addAction(self.MemoryAndThreadsAction)
|
|
109
109
|
menuBar.addMenu(OptionsMenu)
|
|
110
110
|
|
|
111
|
+
PluginsMenu = QMenu("Plugins", self)
|
|
112
|
+
PluginsMenu.addAction(self.CorrectAnnotationAction)
|
|
113
|
+
menuBar.addMenu(PluginsMenu)
|
|
114
|
+
|
|
111
115
|
helpMenu = QMenu("Help", self)
|
|
112
116
|
helpMenu.clear()
|
|
113
117
|
helpMenu.addAction(self.DocumentationAction)
|
|
@@ -130,6 +134,8 @@ class AppInitWindow(QMainWindow):
|
|
|
130
134
|
|
|
131
135
|
self.MemoryAndThreadsAction = QAction('Memory & Threads...')
|
|
132
136
|
|
|
137
|
+
self.CorrectAnnotationAction = QAction('Correct a segmentation annotation...')
|
|
138
|
+
|
|
133
139
|
self.newExpAction = QAction('New', self)
|
|
134
140
|
self.newExpAction.setShortcut("Ctrl+N")
|
|
135
141
|
self.newExpAction.setShortcutVisibleInContextMenu(True)
|
|
@@ -156,6 +162,7 @@ class AppInitWindow(QMainWindow):
|
|
|
156
162
|
self.openModels.triggered.connect(self.open_models_folder)
|
|
157
163
|
self.AboutAction.triggered.connect(self.open_about_window)
|
|
158
164
|
self.MemoryAndThreadsAction.triggered.connect(self.set_memory_and_threads)
|
|
165
|
+
self.CorrectAnnotationAction.triggered.connect(self.correct_seg_annotation)
|
|
159
166
|
|
|
160
167
|
self.DocumentationAction.triggered.connect(self.open_documentation)
|
|
161
168
|
|
|
@@ -186,6 +193,15 @@ class AppInitWindow(QMainWindow):
|
|
|
186
193
|
for r in self.recentFileActs:
|
|
187
194
|
r.triggered.connect(lambda checked, item=r: self.load_recent_exp(item.text()))
|
|
188
195
|
|
|
196
|
+
def correct_seg_annotation(self):
|
|
197
|
+
|
|
198
|
+
self.filename,_ = QFileDialog.getOpenFileName(self,"Open Image", "/home/", "TIF Files (*.tif)")
|
|
199
|
+
if self.filename!='':
|
|
200
|
+
print('Opening ',self.filename,' in napari...')
|
|
201
|
+
correct_annotation(self.filename)
|
|
202
|
+
else:
|
|
203
|
+
return None
|
|
204
|
+
|
|
189
205
|
def set_memory_and_threads(self):
|
|
190
206
|
|
|
191
207
|
print('setting memory and threads')
|
|
@@ -405,6 +421,7 @@ if __name__ == "__main__":
|
|
|
405
421
|
import subprocess
|
|
406
422
|
import os
|
|
407
423
|
from celldetective.gui.about import AboutWidget
|
|
424
|
+
from celldetective.io import correct_annotation
|
|
408
425
|
import psutil
|
|
409
426
|
import subprocess
|
|
410
427
|
import json
|
|
@@ -1,15 +1,31 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
Copyright © 2022 Laboratoire Adhesion et Inflammation, Authored by Remy Torro,
|
|
3
|
+
Ksenija Dervanova.
|
|
4
|
+
|
|
5
|
+
Add extra properties to measure using regionprops (measure_features).
|
|
6
|
+
The functions must take "regionmask" as the first argument, corresponding
|
|
7
|
+
to the binary mask of each cell of interest generated by regionprops
|
|
8
|
+
and an optional "intensity_image" second argument, i.e. the associated
|
|
9
|
+
multichannel image crop. The measurement will be named after the function
|
|
10
|
+
name. If a function function() has several outputs (e.g. due to a multichannel image),
|
|
11
|
+
they will be labelled with an index as function-0, function-1... A routine
|
|
12
|
+
automatically replacements keywords "intensity-0", "intensity-1" and so on,
|
|
13
|
+
with the actual name of the intensity channel (e.g. "intensity-0" ->
|
|
14
|
+
"brightfield_channel"). Due to this indexing behavior, avoid using digits
|
|
15
|
+
in the function names and prefer text instead: "intensity-99()" -->
|
|
16
|
+
"intensity-ninety-nine()".
|
|
17
|
+
|
|
18
|
+
New functions appear automatically in the ConfigMeasurements widget of the
|
|
19
|
+
GUI.
|
|
5
20
|
|
|
6
21
|
"""
|
|
22
|
+
|
|
7
23
|
import warnings
|
|
8
24
|
|
|
9
25
|
import numpy as np
|
|
10
26
|
from scipy.ndimage import distance_transform_edt, center_of_mass
|
|
11
27
|
from scipy.spatial.distance import euclidean
|
|
12
|
-
|
|
28
|
+
from celldetective.utils import interpolate_nan, contour_of_instance_segmentation
|
|
13
29
|
|
|
14
30
|
# Percentiles
|
|
15
31
|
|
|
@@ -44,6 +60,9 @@ def intensity_nanmean(regionmask, intensity_image):
|
|
|
44
60
|
return np.nanmean(intensity_image[regionmask])
|
|
45
61
|
|
|
46
62
|
def intensity_centre_of_mass_displacement(regionmask, intensity_image):
|
|
63
|
+
|
|
64
|
+
intensity_image = interpolate_nan(intensity_image.copy())
|
|
65
|
+
|
|
47
66
|
y, x = np.mgrid[:regionmask.shape[0], :regionmask.shape[1]]
|
|
48
67
|
xtemp = x.copy()
|
|
49
68
|
ytemp = y.copy()
|
|
@@ -51,60 +70,69 @@ def intensity_centre_of_mass_displacement(regionmask, intensity_image):
|
|
|
51
70
|
centroid_x = intensity_weighted_center[1]
|
|
52
71
|
centroid_y = intensity_weighted_center[0]
|
|
53
72
|
|
|
73
|
+
#centroid_x = np.sum(xtemp * intensity_image) / np.sum(intensity_image)
|
|
54
74
|
geometric_centroid_x = np.sum(xtemp * regionmask) / np.sum(regionmask)
|
|
55
75
|
geometric_centroid_y = np.sum(ytemp * regionmask) / np.sum(regionmask)
|
|
56
|
-
|
|
76
|
+
try:
|
|
77
|
+
distance = euclidean(np.array((geometric_centroid_y, geometric_centroid_x)), np.array((centroid_y, centroid_x)))
|
|
78
|
+
except:
|
|
79
|
+
distance = np.nan
|
|
80
|
+
|
|
57
81
|
delta_x = geometric_centroid_x - centroid_x
|
|
58
82
|
delta_y = geometric_centroid_y - centroid_y
|
|
59
83
|
direction_arctan = np.arctan2(delta_y, delta_x) * 180 / np.pi
|
|
60
84
|
if direction_arctan < 0:
|
|
61
85
|
direction_arctan += 360
|
|
62
|
-
|
|
86
|
+
|
|
87
|
+
return distance, direction_arctan, centroid_x - geometric_centroid_x, centroid_y - geometric_centroid_y
|
|
63
88
|
|
|
64
89
|
def intensity_radial_gradient(regionmask, intensity_image):
|
|
65
|
-
warnings.filterwarnings('ignore', message="Polyfit may be poorly conditioned")
|
|
66
|
-
cell_mask = regionmask.copy()
|
|
67
|
-
intensity = intensity_image.copy()
|
|
68
|
-
y = intensity[cell_mask].flatten()
|
|
69
|
-
x = distance_transform_edt(cell_mask)
|
|
70
|
-
x = x[cell_mask].flatten()
|
|
71
|
-
params = np.polyfit(x, y, 1)
|
|
72
|
-
line = np.poly1d(params)
|
|
73
90
|
|
|
74
|
-
|
|
91
|
+
try:
|
|
92
|
+
warnings.filterwarnings('ignore', message="Polyfit may be poorly conditioned")
|
|
93
|
+
cell_mask = regionmask.copy()
|
|
94
|
+
intensity = intensity_image.copy()
|
|
95
|
+
y = intensity[cell_mask].flatten()
|
|
96
|
+
x = distance_transform_edt(cell_mask)
|
|
97
|
+
x = x[cell_mask].flatten()
|
|
98
|
+
params = np.polyfit(x, y, 1)
|
|
99
|
+
line = np.poly1d(params)
|
|
100
|
+
|
|
101
|
+
return line.coefficients[0], line.coefficients[1]
|
|
102
|
+
except Exception as e:
|
|
103
|
+
print(e)
|
|
104
|
+
return np.nan, np.nan
|
|
75
105
|
|
|
76
106
|
|
|
77
107
|
def intensity_centre_of_mass_displacement_edge(regionmask, intensity_image):
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
thresholded = (edt <= max_distance) * (edt > min_distance)
|
|
82
|
-
edge_mask = np.copy(regionmask)
|
|
83
|
-
edge_mask[np.where(thresholded == 0)] = 0
|
|
84
|
-
y, x = np.mgrid[:edge_mask.shape[0], :edge_mask.shape[1]]
|
|
85
|
-
xtemp = x.copy()
|
|
86
|
-
ytemp = y.copy()
|
|
87
|
-
intensity_edge = intensity_image.copy()
|
|
88
|
-
intensity_edge[np.where(edge_mask == 0)] = 0.
|
|
89
|
-
sum_intensity_edge = np.sum(intensity_edge)
|
|
90
|
-
sum_regionmask = np.sum(regionmask)
|
|
108
|
+
|
|
109
|
+
intensity_image = interpolate_nan(intensity_image.copy())
|
|
110
|
+
edge_mask = contour_of_instance_segmentation(regionmask, 3)
|
|
91
111
|
|
|
92
|
-
if
|
|
93
|
-
|
|
112
|
+
if np.sum(edge_mask)>0:
|
|
113
|
+
|
|
114
|
+
y, x = np.mgrid[:edge_mask.shape[0], :edge_mask.shape[1]]
|
|
94
115
|
xtemp = x.copy()
|
|
95
116
|
ytemp = y.copy()
|
|
96
|
-
intensity_weighted_center = center_of_mass(intensity_image,
|
|
117
|
+
intensity_weighted_center = center_of_mass(intensity_image, edge_mask)
|
|
97
118
|
centroid_x = intensity_weighted_center[1]
|
|
98
119
|
centroid_y = intensity_weighted_center[0]
|
|
99
120
|
|
|
121
|
+
#centroid_x = np.sum(xtemp * intensity_image) / np.sum(intensity_image)
|
|
100
122
|
geometric_centroid_x = np.sum(xtemp * regionmask) / np.sum(regionmask)
|
|
101
123
|
geometric_centroid_y = np.sum(ytemp * regionmask) / np.sum(regionmask)
|
|
102
|
-
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
distance = euclidean(np.array((geometric_centroid_y, geometric_centroid_x)), np.array((centroid_y, centroid_x)))
|
|
127
|
+
except:
|
|
128
|
+
distance = np.nan
|
|
129
|
+
|
|
103
130
|
delta_x = geometric_centroid_x - centroid_x
|
|
104
131
|
delta_y = geometric_centroid_y - centroid_y
|
|
105
132
|
direction_arctan = np.arctan2(delta_y, delta_x) * 180 / np.pi
|
|
106
133
|
if direction_arctan < 0:
|
|
107
134
|
direction_arctan += 360
|
|
108
|
-
|
|
135
|
+
|
|
136
|
+
return distance, direction_arctan, centroid_x - geometric_centroid_x, centroid_y - geometric_centroid_y
|
|
109
137
|
else:
|
|
110
|
-
return np.nan, np.nan
|
|
138
|
+
return np.nan, np.nan, np.nan, np.nan
|
|
@@ -9,6 +9,7 @@ from .survival_ui import ConfigSurvival
|
|
|
9
9
|
from .plot_signals_ui import ConfigSignalPlot
|
|
10
10
|
from .signal_annotator_options import ConfigSignalAnnotator
|
|
11
11
|
from .signal_annotator import SignalAnnotator
|
|
12
|
+
from .signal_annotator2 import SignalAnnotator2
|
|
12
13
|
from .retrain_signal_model_options import ConfigSignalModelTraining
|
|
13
14
|
from .retrain_segmentation_model_options import ConfigSegmentationModelTraining
|
|
14
15
|
from .thresholds_gui import ThresholdConfigWizard
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
from PyQt5.QtWidgets import QFrame, QGridLayout, QComboBox,
|
|
1
|
+
from PyQt5.QtWidgets import QFrame, QGridLayout, QComboBox, QLabel, QPushButton, QVBoxLayout, QHBoxLayout, QCheckBox, \
|
|
2
|
+
QMessageBox, QSpacerItem, QSizePolicy
|
|
2
3
|
from PyQt5.QtCore import Qt, QSize
|
|
3
4
|
from PyQt5.QtGui import QIcon
|
|
4
5
|
|
|
@@ -12,6 +12,7 @@ from sklearn.metrics import r2_score
|
|
|
12
12
|
from scipy.optimize import curve_fit
|
|
13
13
|
from celldetective.gui import Styles
|
|
14
14
|
from math import ceil
|
|
15
|
+
from celldetective.utils import extract_cols_from_query
|
|
15
16
|
|
|
16
17
|
def step_function(t, t_shift, dt):
|
|
17
18
|
return 1/(1+np.exp(-(t-t_shift)/dt))
|
|
@@ -263,17 +264,23 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
263
264
|
self.propscanvas.canvas.draw_idle()
|
|
264
265
|
|
|
265
266
|
def apply_property_query(self):
|
|
267
|
+
|
|
266
268
|
query = self.property_query_le.text()
|
|
267
269
|
self.df[self.class_name] = 1
|
|
268
270
|
|
|
269
|
-
|
|
271
|
+
cols = extract_cols_from_query(query)
|
|
272
|
+
print(cols)
|
|
273
|
+
cols_in_df = np.all([c in list(self.df.columns) for c in cols], axis=0)
|
|
274
|
+
print(f'Testing if columns from query are in the dataframe: {cols_in_df}...')
|
|
270
275
|
|
|
271
276
|
if query=='':
|
|
272
277
|
print('empty query')
|
|
273
278
|
else:
|
|
274
279
|
try:
|
|
275
|
-
|
|
276
|
-
|
|
280
|
+
if cols_in_df:
|
|
281
|
+
self.selection = self.df.dropna(subset=cols).query(query).index
|
|
282
|
+
else:
|
|
283
|
+
self.selection = self.df.query(query).index
|
|
277
284
|
self.df.loc[self.selection, self.class_name] = 0
|
|
278
285
|
except Exception as e:
|
|
279
286
|
print(e)
|
|
@@ -368,13 +375,14 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
368
375
|
frames = track['FRAME'].to_numpy()
|
|
369
376
|
t_first = track['t_firstdetection'].to_numpy()[0]
|
|
370
377
|
median_status = np.nanmedian(status_values[frames>=t_first])
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
+
if median_status==median_status:
|
|
379
|
+
c = ceil(median_status)
|
|
380
|
+
if c==0:
|
|
381
|
+
self.df.loc[indices, self.class_name_user] = 1
|
|
382
|
+
self.df.loc[indices, self.class_name_user.replace('class','t')] = -1
|
|
383
|
+
elif c==1:
|
|
384
|
+
self.df.loc[indices, self.class_name_user] = 2
|
|
385
|
+
self.df.loc[indices, self.class_name_user.replace('class','t')] = -1
|
|
378
386
|
if self.irreversible_event_btn.isChecked():
|
|
379
387
|
self.estimate_time()
|
|
380
388
|
else:
|
|
@@ -3,7 +3,7 @@ from PyQt5.QtWidgets import QMainWindow, QComboBox, QPushButton, QHBoxLayout, QL
|
|
|
3
3
|
from PyQt5.QtCore import Qt, QSize
|
|
4
4
|
from PyQt5.QtGui import QIcon
|
|
5
5
|
from celldetective.gui.gui_utils import center_window, QHSeperationLine
|
|
6
|
-
from celldetective.utils import _extract_labels_from_config, ConfigSectionMap, extract_experiment_channels
|
|
6
|
+
from celldetective.utils import _extract_labels_from_config, ConfigSectionMap, extract_experiment_channels, extract_identity_col
|
|
7
7
|
from celldetective.gui import ConfigEditor, ProcessPanel, PreprocessingPanel, AnalysisPanel, NeighPanel
|
|
8
8
|
from natsort import natsorted
|
|
9
9
|
from glob import glob
|
|
@@ -16,6 +16,7 @@ import subprocess
|
|
|
16
16
|
from celldetective.gui.viewers import StackVisualizer
|
|
17
17
|
from celldetective.utils import extract_experiment_channels
|
|
18
18
|
from celldetective.gui import Styles
|
|
19
|
+
import pandas as pd
|
|
19
20
|
|
|
20
21
|
class ControlPanel(QMainWindow, Styles):
|
|
21
22
|
|
|
@@ -413,26 +414,46 @@ class ControlPanel(QMainWindow, Styles):
|
|
|
413
414
|
self.pos = self.position_list.currentText()
|
|
414
415
|
panels = [self.ProcessEffectors, self.ProcessTargets]
|
|
415
416
|
if self.position_list.currentText()=="*":
|
|
417
|
+
|
|
416
418
|
for p in panels:
|
|
417
419
|
p.check_seg_btn.setEnabled(False)
|
|
418
420
|
p.check_tracking_result_btn.setEnabled(False)
|
|
421
|
+
|
|
419
422
|
self.ProcessTargets.view_tab_btn.setEnabled(True)
|
|
420
423
|
self.ProcessEffectors.view_tab_btn.setEnabled(True)
|
|
424
|
+
self.NeighPanel.view_tab_btn.setEnabled(True)
|
|
425
|
+
self.ProcessEffectors.signal_analysis_action.setEnabled(True)
|
|
426
|
+
self.ProcessTargets.signal_analysis_action.setEnabled(True)
|
|
427
|
+
|
|
421
428
|
self.ProcessTargets.check_seg_btn.setEnabled(False)
|
|
422
429
|
self.ProcessEffectors.check_seg_btn.setEnabled(False)
|
|
430
|
+
|
|
423
431
|
self.ProcessTargets.check_tracking_result_btn.setEnabled(False)
|
|
424
432
|
self.ProcessEffectors.check_tracking_result_btn.setEnabled(False)
|
|
433
|
+
|
|
425
434
|
self.ProcessEffectors.check_measurements_btn.setEnabled(False)
|
|
426
435
|
self.ProcessTargets.check_measurements_btn.setEnabled(False)
|
|
427
436
|
#self.ProcessTargets.signal_analysis_action.setEnabled(False)
|
|
428
437
|
#self.ProcessEffectors.signal_analysis_action.setEnabled(False)
|
|
438
|
+
|
|
429
439
|
self.ProcessTargets.check_signals_btn.setEnabled(False)
|
|
430
440
|
self.ProcessEffectors.check_signals_btn.setEnabled(False)
|
|
441
|
+
self.NeighPanel.check_signals_btn.setEnabled(False)
|
|
442
|
+
self.ProcessTargets.delete_tracks_btn.hide()
|
|
443
|
+
self.ProcessEffectors.delete_tracks_btn.hide()
|
|
444
|
+
|
|
431
445
|
self.view_stack_btn.setEnabled(False)
|
|
432
446
|
elif self.well_list.currentText()=='*':
|
|
433
447
|
self.ProcessTargets.view_tab_btn.setEnabled(True)
|
|
434
448
|
self.ProcessEffectors.view_tab_btn.setEnabled(True)
|
|
435
|
-
self.
|
|
449
|
+
self.NeighPanel.view_tab_btn.setEnabled(True)
|
|
450
|
+
self.view_stack_btn.setEnabled(False)
|
|
451
|
+
self.ProcessEffectors.signal_analysis_action.setEnabled(True)
|
|
452
|
+
self.ProcessTargets.signal_analysis_action.setEnabled(True)
|
|
453
|
+
|
|
454
|
+
self.delete_tracks_btn.hide()
|
|
455
|
+
self.ProcessTargets.delete_tracks_btn.hide()
|
|
456
|
+
self.ProcessEffectors.delete_tracks_btn.hide()
|
|
436
457
|
else:
|
|
437
458
|
if not self.well_list.currentText()=="*":
|
|
438
459
|
self.locate_selected_position()
|
|
@@ -452,26 +473,56 @@ class ControlPanel(QMainWindow, Styles):
|
|
|
452
473
|
self.ProcessEffectors.check_tracking_result_btn.setEnabled(False)
|
|
453
474
|
|
|
454
475
|
if os.path.exists(os.sep.join([self.pos,'output','tables','trajectories_effectors.csv'])):
|
|
476
|
+
df = pd.read_csv(os.sep.join([self.pos,'output','tables','trajectories_effectors.csv']), nrows=1)
|
|
477
|
+
id_col = extract_identity_col(df)
|
|
455
478
|
self.ProcessEffectors.check_measurements_btn.setEnabled(True)
|
|
456
|
-
|
|
479
|
+
if id_col=='TRACK_ID':
|
|
480
|
+
self.ProcessEffectors.check_signals_btn.setEnabled(True)
|
|
481
|
+
self.ProcessEffectors.delete_tracks_btn.show()
|
|
482
|
+
self.ProcessEffectors.signal_analysis_action.setEnabled(True)
|
|
483
|
+
else:
|
|
484
|
+
self.ProcessEffectors.signal_analysis_action.setEnabled(False)
|
|
485
|
+
|
|
457
486
|
#self.ProcessEffectors.signal_analysis_action.setEnabled(True)
|
|
458
487
|
self.ProcessEffectors.view_tab_btn.setEnabled(True)
|
|
459
|
-
|
|
488
|
+
self.ProcessEffectors.classify_btn.setEnabled(True)
|
|
460
489
|
else:
|
|
461
490
|
self.ProcessEffectors.check_measurements_btn.setEnabled(False)
|
|
462
491
|
self.ProcessEffectors.check_signals_btn.setEnabled(False)
|
|
463
492
|
#self.ProcessEffectors.signal_analysis_action.setEnabled(False)
|
|
464
493
|
self.ProcessEffectors.view_tab_btn.setEnabled(False)
|
|
494
|
+
self.ProcessEffectors.classify_btn.setEnabled(False)
|
|
495
|
+
self.ProcessEffectors.delete_tracks_btn.hide()
|
|
496
|
+
self.ProcessEffectors.signal_analysis_action.setEnabled(False)
|
|
465
497
|
|
|
466
498
|
if os.path.exists(os.sep.join([self.pos,'output','tables','trajectories_targets.csv'])):
|
|
499
|
+
df = pd.read_csv(os.sep.join([self.pos,'output','tables','trajectories_targets.csv']), nrows=1)
|
|
500
|
+
id_col = extract_identity_col(df)
|
|
467
501
|
self.ProcessTargets.check_measurements_btn.setEnabled(True)
|
|
468
|
-
|
|
502
|
+
if id_col=='TRACK_ID':
|
|
503
|
+
self.ProcessTargets.check_signals_btn.setEnabled(True)
|
|
504
|
+
self.ProcessTargets.signal_analysis_action.setEnabled(True)
|
|
505
|
+
self.ProcessTargets.delete_tracks_btn.show()
|
|
506
|
+
else:
|
|
507
|
+
self.ProcessTargets.signal_analysis_action.setEnabled(False)
|
|
469
508
|
#self.ProcessTargets.signal_analysis_action.setEnabled(True)
|
|
470
509
|
self.ProcessTargets.view_tab_btn.setEnabled(True)
|
|
471
|
-
|
|
510
|
+
self.ProcessTargets.classify_btn.setEnabled(True)
|
|
472
511
|
else:
|
|
473
512
|
self.ProcessTargets.check_measurements_btn.setEnabled(False)
|
|
474
513
|
self.ProcessTargets.check_signals_btn.setEnabled(False)
|
|
475
514
|
#self.ProcessTargets.signal_analysis_action.setEnabled(False)
|
|
476
515
|
self.ProcessTargets.view_tab_btn.setEnabled(False)
|
|
516
|
+
self.ProcessTargets.classify_btn.setEnabled(False)
|
|
517
|
+
self.ProcessTargets.signal_analysis_action.setEnabled(False)
|
|
518
|
+
self.ProcessTargets.delete_tracks_btn.hide()
|
|
519
|
+
self.ProcessTargets.signal_analysis_action.setEnabled(False)
|
|
520
|
+
|
|
521
|
+
if os.path.exists(os.sep.join([self.pos,'output','tables','trajectories_pairs.csv'])):
|
|
522
|
+
self.NeighPanel.view_tab_btn.setEnabled(True)
|
|
523
|
+
self.NeighPanel.check_signals_btn.setEnabled(True)
|
|
524
|
+
else:
|
|
525
|
+
self.NeighPanel.view_tab_btn.setEnabled(False)
|
|
526
|
+
self.NeighPanel.check_signals_btn.setEnabled(False)
|
|
527
|
+
|
|
477
528
|
|
|
@@ -73,7 +73,7 @@ class ChannelNormGenerator(QVBoxLayout, Styles):
|
|
|
73
73
|
for tab in tables:
|
|
74
74
|
cols = pd.read_csv(tab, nrows=1).columns.tolist()
|
|
75
75
|
all_measurements.extend(cols)
|
|
76
|
-
all_measurements = np.unique(all_measurements)
|
|
76
|
+
all_measurements = np.unique(all_measurements)
|
|
77
77
|
|
|
78
78
|
if self.mode=='signals':
|
|
79
79
|
generic_measurements = ['brightfield_channel', 'live_nuclei_channel', 'dead_nuclei_channel',
|
|
@@ -245,13 +245,14 @@ class ChannelNormGenerator(QVBoxLayout, Styles):
|
|
|
245
245
|
|
|
246
246
|
def check_valid_channels(self):
|
|
247
247
|
|
|
248
|
-
if
|
|
249
|
-
|
|
248
|
+
if hasattr(self.parent_window, "submit_btn"):
|
|
249
|
+
if np.all([cb.currentText()=='--' for cb in self.channel_cbs]):
|
|
250
|
+
self.parent_window.submit_btn.setEnabled(False)
|
|
250
251
|
|
|
251
252
|
if hasattr(self.parent_window, "spatial_calib_le"):
|
|
252
253
|
if self.parent_window.spatial_calib_le.text()!='--':
|
|
253
254
|
self.parent_window.submit_btn.setEnabled(True)
|
|
254
|
-
|
|
255
|
+
elif hasattr(self.parent_window, "submit_btn"):
|
|
255
256
|
self.parent_window.submit_btn.setEnabled(True)
|
|
256
257
|
|
|
257
258
|
|
|
@@ -990,10 +991,12 @@ class BackgroundModelFreeCorrectionLayout(QGridLayout, Styles):
|
|
|
990
991
|
)
|
|
991
992
|
bg = bg[0]
|
|
992
993
|
bg = bg['bg']
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
994
|
+
print(bg)
|
|
995
|
+
if len(bg)>0:
|
|
996
|
+
|
|
997
|
+
self.viewer = StackVisualizer(
|
|
998
|
+
stack=[bg],
|
|
999
|
+
window_title='Reconstructed background',
|
|
1000
|
+
frame_slider = False,
|
|
1001
|
+
)
|
|
1002
|
+
self.viewer.show()
|
|
@@ -90,6 +90,8 @@ class ConfigNeighborhoods(QWidget, Styles):
|
|
|
90
90
|
|
|
91
91
|
self.clear_previous_btn = QCheckBox('clear previous neighborhoods')
|
|
92
92
|
self.clear_previous_btn.setToolTip('Clear all previous neighborhood measurements.')
|
|
93
|
+
self.clear_previous_btn.setIcon(icon(MDI6.broom, color='black'))
|
|
94
|
+
|
|
93
95
|
main_layout.addWidget(self.clear_previous_btn, alignment=Qt.AlignRight)
|
|
94
96
|
|
|
95
97
|
main_layout.addWidget(QLabel(''))
|
|
@@ -305,11 +307,13 @@ class ConfigNeighborhoods(QWidget, Styles):
|
|
|
305
307
|
|
|
306
308
|
self.cumulated_presence_btn = QCheckBox('cumulated presence')
|
|
307
309
|
self.cumulated_presence_btn.setToolTip("Compute the cumulated presence time of each neighbor around a reference cell.")
|
|
310
|
+
self.cumulated_presence_btn.setIcon(icon(MDI6.timer_outline, color='black'))
|
|
311
|
+
|
|
308
312
|
layout.addWidget(self.cumulated_presence_btn)
|
|
309
313
|
|
|
310
|
-
self.symmetrize_btn = QCheckBox('symmetrize')
|
|
311
|
-
self.symmetrize_btn.setToolTip("Write the neighborhood of the neighbor cells with respect to the reference cells.")
|
|
312
|
-
layout.addWidget(self.symmetrize_btn)
|
|
314
|
+
# self.symmetrize_btn = QCheckBox('symmetrize')
|
|
315
|
+
# self.symmetrize_btn.setToolTip("Write the neighborhood of the neighbor cells with respect to the reference cells.")
|
|
316
|
+
# layout.addWidget(self.symmetrize_btn)
|
|
313
317
|
|
|
314
318
|
self.fill_cbs_of_neighbor_population()
|
|
315
319
|
self.neighbor_population_cb.currentIndexChanged.connect(self.fill_cbs_of_neighbor_population)
|
|
@@ -319,16 +323,16 @@ class ConfigNeighborhoods(QWidget, Styles):
|
|
|
319
323
|
def fill_cbs_of_neighbor_population(self):
|
|
320
324
|
|
|
321
325
|
population = self.neighbor_population_cb.currentText()
|
|
322
|
-
class_cols, status_cols, time_cols = self.locate_population_specific_columns(population)
|
|
326
|
+
class_cols, status_cols, group_cols, time_cols = self.locate_population_specific_columns(population)
|
|
323
327
|
self.neighbor_population_status_cb.clear()
|
|
324
|
-
self.neighbor_population_status_cb.addItems(['--','class', 'status']+class_cols+status_cols)
|
|
328
|
+
self.neighbor_population_status_cb.addItems(['--','class', 'status']+class_cols+status_cols+group_cols)
|
|
325
329
|
|
|
326
330
|
def fill_cbs_of_reference_population(self):
|
|
327
331
|
|
|
328
332
|
population = self.reference_population_cb.currentText()
|
|
329
|
-
class_cols, status_cols, time_cols = self.locate_population_specific_columns(population)
|
|
333
|
+
class_cols, status_cols, group_cols, time_cols = self.locate_population_specific_columns(population)
|
|
330
334
|
self.reference_population_status_cb.clear()
|
|
331
|
-
self.reference_population_status_cb.addItems(['--','class', 'status']+class_cols+status_cols)
|
|
335
|
+
self.reference_population_status_cb.addItems(['--','class', 'status']+class_cols+status_cols+group_cols)
|
|
332
336
|
self.event_time_cb.addItems(['--', 't0']+time_cols)
|
|
333
337
|
|
|
334
338
|
def switch_not_reference(self):
|
|
@@ -364,6 +368,7 @@ class ConfigNeighborhoods(QWidget, Styles):
|
|
|
364
368
|
|
|
365
369
|
class_idx = np.array([s.startswith('class_') for s in self.all_columns])
|
|
366
370
|
status_idx = np.array([s.startswith('status_') for s in self.all_columns])
|
|
371
|
+
group_idx = np.array([s.startswith('group_') for s in self.all_columns])
|
|
367
372
|
time_idx = np.array([s.startswith('t_') for s in self.all_columns])
|
|
368
373
|
|
|
369
374
|
if len(class_idx)>0:
|
|
@@ -379,12 +384,17 @@ class ConfigNeighborhoods(QWidget, Styles):
|
|
|
379
384
|
else:
|
|
380
385
|
status_columns = []
|
|
381
386
|
|
|
387
|
+
if len(group_idx)>0:
|
|
388
|
+
group_columns = list(self.all_columns[group_idx])
|
|
389
|
+
else:
|
|
390
|
+
group_columns = []
|
|
391
|
+
|
|
382
392
|
if len(time_idx)>0:
|
|
383
393
|
time_columns = list(self.all_columns[time_idx])
|
|
384
394
|
else:
|
|
385
395
|
time_columns = []
|
|
386
396
|
|
|
387
|
-
return class_columns, status_columns, time_columns
|
|
397
|
+
return class_columns, status_columns, group_columns, time_columns
|
|
388
398
|
|
|
389
399
|
def write_instructions(self):
|
|
390
400
|
|
|
@@ -418,7 +428,7 @@ class ConfigNeighborhoods(QWidget, Styles):
|
|
|
418
428
|
neighborhood_options.update({'event_time_col': event_time_col})
|
|
419
429
|
|
|
420
430
|
neighborhood_kwargs = {'mode': mode, 'status': status_options, 'not_status_option': [self.not_status_reference, self.not_status_neighbor],
|
|
421
|
-
'compute_cum_sum': self.cumulated_presence_btn.isChecked(), 'attention_weight': True, 'symmetrize':
|
|
431
|
+
'compute_cum_sum': self.cumulated_presence_btn.isChecked(), 'attention_weight': True, 'symmetrize': False,
|
|
422
432
|
'include_dead_weight': True}
|
|
423
433
|
|
|
424
434
|
neighborhood_options.update({'neighborhood_kwargs': neighborhood_kwargs})
|
|
@@ -487,8 +497,6 @@ class ConfigNeighborhoods(QWidget, Styles):
|
|
|
487
497
|
neighborhood_kwargs = neigh_instructions['neighborhood_kwargs']
|
|
488
498
|
if 'compute_cum_sum' in neighborhood_kwargs:
|
|
489
499
|
self.cumulated_presence_btn.setChecked(neighborhood_kwargs['compute_cum_sum'])
|
|
490
|
-
if 'symmetrize' in neighborhood_kwargs:
|
|
491
|
-
self.symmetrize_btn.setChecked(neighborhood_kwargs['symmetrize'])
|
|
492
500
|
if 'status' in neighborhood_kwargs:
|
|
493
501
|
status_options = neighborhood_kwargs['status']
|
|
494
502
|
status_options = ['--' if s is None else s for s in status_options]
|
|
@@ -499,6 +507,6 @@ class ConfigNeighborhoods(QWidget, Styles):
|
|
|
499
507
|
if 'not_status_option' in neighborhood_kwargs:
|
|
500
508
|
not_status_option = neighborhood_kwargs['not_status_option']
|
|
501
509
|
if not_status_option[0]:
|
|
502
|
-
self.
|
|
510
|
+
self.reference_switch_status_btn.click()
|
|
503
511
|
if not_status_option[1]:
|
|
504
|
-
self.
|
|
512
|
+
self.neighbor_switch_status_btn.click()
|