celldetective 1.3.0.post1__tar.gz → 1.3.2__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.3.0.post1 → celldetective-1.3.2}/PKG-INFO +3 -3
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/README.md +2 -2
- celldetective-1.3.2/celldetective/_version.py +1 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/events.py +88 -11
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/extra_properties.py +5 -1
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/InitWindow.py +35 -9
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/classifier_widget.py +99 -23
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/control_panel.py +7 -1
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/generic_signal_plot.py +161 -2
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/gui_utils.py +90 -1
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/layouts.py +128 -7
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/measurement_options.py +3 -3
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/plot_signals_ui.py +8 -3
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/process_block.py +77 -32
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/retrain_segmentation_model_options.py +24 -10
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/signal_annotator.py +53 -26
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/signal_annotator2.py +17 -30
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/survival_ui.py +24 -3
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/tableUI.py +300 -183
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/viewers.py +263 -3
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/io.py +56 -3
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/links/zenodo.json +136 -123
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/measure.py +3 -0
- celldetective-1.3.2/celldetective/models/tracking_configs/biased_motion.json +68 -0
- celldetective-1.3.2/celldetective/models/tracking_configs/no_z_motion.json +202 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/neighborhood.py +154 -69
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/preprocessing.py +172 -3
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/relative_measurements.py +128 -4
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/scripts/measure_cells.py +3 -3
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/signals.py +212 -215
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/tracking.py +7 -3
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/utils.py +22 -6
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective.egg-info/PKG-INFO +3 -3
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective.egg-info/SOURCES.txt +2 -0
- celldetective-1.3.0.post1/celldetective/_version.py +0 -1
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/LICENSE +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/__init__.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/__main__.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/datasets/segmentation_annotations/blank +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/datasets/signal_annotations/blank +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/filters.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/__init__.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/about.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/analyze_block.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/btrack_options.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/configure_new_exp.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/help/DL-segmentation-strategy.json +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/help/Threshold-vs-DL.json +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/help/cell-populations.json +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/help/exp-structure.json +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/help/feature-btrack.json +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/help/neighborhood.json +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/help/prefilter-for-segmentation.json +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/help/preprocessing.json +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/help/propagate-classification.json +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/help/track-postprocessing.json +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/help/tracking.json +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/json_readers.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/neighborhood_options.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/plot_measurements.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/retrain_signal_model_options.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/seg_model_loader.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/signal_annotator_options.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/styles.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/gui/thresholds_gui.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/icons/logo-large.png +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/icons/logo.png +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/icons/signals_icon.png +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/icons/splash-test.png +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/icons/splash.png +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/icons/splash0.png +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/icons/survival2.png +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/icons/vignette_signals2.png +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/icons/vignette_signals2.svg +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/models/pair_signal_detection/blank +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/models/segmentation_effectors/blank +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/models/segmentation_generic/blank +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/models/segmentation_targets/blank +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/models/signal_detection/blank +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/models/tracking_configs/mcf7.json +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/models/tracking_configs/ricm.json +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/models/tracking_configs/ricm2.json +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/scripts/analyze_signals.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/scripts/measure_relative.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/scripts/segment_cells.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/scripts/segment_cells_thresholds.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/scripts/track_cells.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/scripts/train_segmentation_model.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/scripts/train_signal_model.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective/segmentation.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective.egg-info/dependency_links.txt +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective.egg-info/entry_points.txt +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective.egg-info/not-zip-safe +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective.egg-info/requires.txt +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/celldetective.egg-info/top_level.txt +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/setup.cfg +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/setup.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/tests/__init__.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/tests/test_events.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/tests/test_filters.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/tests/test_io.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/tests/test_measure.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/tests/test_neighborhood.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/tests/test_preprocessing.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/tests/test_qt.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/tests/test_segmentation.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/tests/test_signals.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/tests/test_tracking.py +0 -0
- {celldetective-1.3.0.post1 → celldetective-1.3.2}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: celldetective
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.2
|
|
4
4
|
Summary: description
|
|
5
5
|
Home-page: http://github.com/remyeltorro/celldetective
|
|
6
6
|
Author: Rémy Torro
|
|
@@ -69,7 +69,7 @@ analysis on multimodal time lapse microscopy images.
|
|
|
69
69
|
|
|
70
70
|
## Overview
|
|
71
71
|
|
|
72
|
-

|
|
73
73
|
|
|
74
74
|
|
|
75
75
|
Celldetective was designed to analyze time-lapse microscopy images in difficult situations: mixed cell populations that are only separable through multimodal information. This software provides a toolkit for the analysis of cell population interactions.
|
|
@@ -88,7 +88,7 @@ Instead of reinventing the wheel and out of respect for the amazing work done by
|
|
|
88
88
|
|
|
89
89
|
**Target Audience**: The software is targeted to scientists who are interested in quantifying dynamically (or not) cell populations from microscopy images. Experimental scientists who produce such images can also analyze their data, thanks to the graphical interface, that completely removes the need for coding, and the many helper functions that guide the user in the analysis steps. Finally, the modular structure of Celldetective welcomes users with a partial need.
|
|
90
90
|
|
|
91
|
-

|
|
92
92
|
|
|
93
93
|
|
|
94
94
|
# System requirements
|
|
@@ -25,7 +25,7 @@ analysis on multimodal time lapse microscopy images.
|
|
|
25
25
|
|
|
26
26
|
## Overview
|
|
27
27
|
|
|
28
|
-

|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
Celldetective was designed to analyze time-lapse microscopy images in difficult situations: mixed cell populations that are only separable through multimodal information. This software provides a toolkit for the analysis of cell population interactions.
|
|
@@ -44,7 +44,7 @@ Instead of reinventing the wheel and out of respect for the amazing work done by
|
|
|
44
44
|
|
|
45
45
|
**Target Audience**: The software is targeted to scientists who are interested in quantifying dynamically (or not) cell populations from microscopy images. Experimental scientists who produce such images can also analyze their data, thanks to the graphical interface, that completely removes the need for coding, and the many helper functions that guide the user in the analysis steps. Finally, the modular structure of Celldetective welcomes users with a partial need.
|
|
46
46
|
|
|
47
|
-

|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
# System requirements
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.3.2"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
from lifelines import KaplanMeierFitter
|
|
3
3
|
|
|
4
|
-
def switch_to_events(classes, event_times, max_times, origin_times=None, left_censored=True, FrameToMin=None):
|
|
4
|
+
def switch_to_events(classes, event_times, max_times, origin_times=None, left_censored=True, FrameToMin=None, cut_observation_time=None):
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
"""
|
|
@@ -29,6 +29,8 @@ def switch_to_events(classes, event_times, max_times, origin_times=None, left_ce
|
|
|
29
29
|
FrameToMin : float, optional
|
|
30
30
|
A conversion factor to transform survival times from frames (or any other unit) to minutes. If None, no conversion
|
|
31
31
|
is applied (default is None).
|
|
32
|
+
cut_observation_time : float or None, optional
|
|
33
|
+
A cutoff time to artificially reduce the observation window and exclude late events. If None, uses all available data (default is None).
|
|
32
34
|
|
|
33
35
|
Returns
|
|
34
36
|
-------
|
|
@@ -70,15 +72,40 @@ def switch_to_events(classes, event_times, max_times, origin_times=None, left_ce
|
|
|
70
72
|
if ot>=0. and ot==ot:
|
|
71
73
|
# origin time is larger than zero, no censorship
|
|
72
74
|
if c==0 and t>0:
|
|
75
|
+
|
|
73
76
|
delta_t = t - ot
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
|
|
78
|
+
# Special case: observation cut at arbitrary time
|
|
79
|
+
if cut_observation_time is not None:
|
|
80
|
+
if t>=cut_observation_time:
|
|
81
|
+
# event time larger than cut, becomes no event
|
|
82
|
+
delta_t = cut_observation_time - ot # new time
|
|
83
|
+
if delta_t > 0:
|
|
84
|
+
events.append(0)
|
|
85
|
+
survival_times.append(delta_t)
|
|
86
|
+
else:
|
|
87
|
+
# negative delta t, invalid cell
|
|
88
|
+
pass
|
|
89
|
+
else:
|
|
90
|
+
# still event
|
|
91
|
+
if delta_t > 0:
|
|
92
|
+
events.append(1)
|
|
93
|
+
survival_times.append(delta_t)
|
|
94
|
+
else:
|
|
95
|
+
# negative delta t, invalid cell
|
|
96
|
+
pass
|
|
97
|
+
else:
|
|
98
|
+
# standard mode
|
|
99
|
+
if delta_t>0:
|
|
100
|
+
events.append(1)
|
|
101
|
+
survival_times.append(delta_t)
|
|
102
|
+
else:
|
|
103
|
+
# negative delta t, invalid cell
|
|
104
|
+
pass
|
|
80
105
|
elif c==1:
|
|
81
106
|
delta_t = mt - ot
|
|
107
|
+
if cut_observation_time is not None:
|
|
108
|
+
delta_t = cut_observation_time - ot
|
|
82
109
|
if delta_t>0:
|
|
83
110
|
events.append(0)
|
|
84
111
|
survival_times.append(delta_t)
|
|
@@ -93,10 +120,20 @@ def switch_to_events(classes, event_times, max_times, origin_times=None, left_ce
|
|
|
93
120
|
|
|
94
121
|
else:
|
|
95
122
|
if c==0 and t>0:
|
|
96
|
-
|
|
97
|
-
|
|
123
|
+
if cut_observation_time is not None:
|
|
124
|
+
if t>cut_observation_time:
|
|
125
|
+
events.append(0)
|
|
126
|
+
survival_times.append(cut_observation_time - ot)
|
|
127
|
+
else:
|
|
128
|
+
events.append(1)
|
|
129
|
+
survival_times.append(t - ot)
|
|
130
|
+
else:
|
|
131
|
+
events.append(1)
|
|
132
|
+
survival_times.append(t - ot)
|
|
98
133
|
elif c==1:
|
|
99
134
|
events.append(0)
|
|
135
|
+
if cut_observation_time is not None:
|
|
136
|
+
mt = cut_observation_time
|
|
100
137
|
survival_times.append(mt - ot)
|
|
101
138
|
else:
|
|
102
139
|
pass
|
|
@@ -106,7 +143,47 @@ def switch_to_events(classes, event_times, max_times, origin_times=None, left_ce
|
|
|
106
143
|
survival_times = [s*FrameToMin for s in survival_times]
|
|
107
144
|
return events, survival_times
|
|
108
145
|
|
|
109
|
-
def compute_survival(df, class_of_interest, t_event, t_reference=None, FrameToMin=1):
|
|
146
|
+
def compute_survival(df, class_of_interest, t_event, t_reference=None, FrameToMin=1, cut_observation_time=None):
|
|
147
|
+
|
|
148
|
+
"""
|
|
149
|
+
Computes survival analysis for a specific class of interest within a dataset, returning a fitted Kaplan-Meier
|
|
150
|
+
survival curve based on event and reference times.
|
|
151
|
+
|
|
152
|
+
Parameters
|
|
153
|
+
----------
|
|
154
|
+
df : pandas.DataFrame
|
|
155
|
+
The dataset containing tracking data, event times, and other relevant columns for survival analysis.
|
|
156
|
+
class_of_interest : str
|
|
157
|
+
The name of the column that specifies the class for which survival analysis is to be computed.
|
|
158
|
+
t_event : str
|
|
159
|
+
The column indicating the time of the event of interest (e.g., cell death or migration stop).
|
|
160
|
+
t_reference : str or None, optional
|
|
161
|
+
The reference column indicating the start or origin time for each track (e.g., detection time). If None,
|
|
162
|
+
events are not left-censored (default is None).
|
|
163
|
+
FrameToMin : float, optional
|
|
164
|
+
Conversion factor to scale the frame time to minutes (default is 1, assuming no scaling).
|
|
165
|
+
cut_observation_time : float or None, optional
|
|
166
|
+
A cutoff time to artificially reduce the observation window and exclude late events. If None, uses all available data (default is None).
|
|
167
|
+
Returns
|
|
168
|
+
-------
|
|
169
|
+
ks : lifelines.KaplanMeierFitter or None
|
|
170
|
+
A fitted Kaplan-Meier estimator object. If there are no events, returns None.
|
|
171
|
+
|
|
172
|
+
Notes
|
|
173
|
+
-----
|
|
174
|
+
- The function groups the data by 'position' and 'TRACK_ID', extracting the minimum `class_of_interest` and `t_event`
|
|
175
|
+
values for each track.
|
|
176
|
+
- If `t_reference` is provided, the analysis assumes left-censoring and will use `t_reference` as the origin time for
|
|
177
|
+
each track.
|
|
178
|
+
- The function calls `switch_to_events` to determine the event occurrences and their associated survival times.
|
|
179
|
+
- A Kaplan-Meier estimator (`KaplanMeierFitter`) is fitted to the data to compute the survival curve.
|
|
180
|
+
|
|
181
|
+
Example
|
|
182
|
+
-------
|
|
183
|
+
>>> ks = compute_survival(df, class_of_interest="class_custom", t_event="time_custom", t_reference="t_firstdetection")
|
|
184
|
+
>>> ks.plot_survival_function()
|
|
185
|
+
|
|
186
|
+
"""
|
|
110
187
|
|
|
111
188
|
cols = list(df.columns)
|
|
112
189
|
assert class_of_interest in cols,"The requested class cannot be found in the dataframe..."
|
|
@@ -127,7 +204,7 @@ def compute_survival(df, class_of_interest, t_event, t_reference=None, FrameToMi
|
|
|
127
204
|
assert t_reference in cols,"The reference time cannot be found in the dataframe..."
|
|
128
205
|
first_detections = df.groupby(['position','TRACK_ID'])[t_reference].max().values
|
|
129
206
|
|
|
130
|
-
events, survival_times = switch_to_events(classes, event_times, max_times, origin_times=first_detections, left_censored=left_censored, FrameToMin=FrameToMin)
|
|
207
|
+
events, survival_times = switch_to_events(classes, event_times, max_times, origin_times=first_detections, left_censored=left_censored, FrameToMin=FrameToMin, cut_observation_time=cut_observation_time)
|
|
131
208
|
ks = KaplanMeierFitter()
|
|
132
209
|
if len(events)>0:
|
|
133
210
|
ks.fit(survival_times, event_observed=events)
|
|
@@ -57,7 +57,11 @@ def intensity_median(regionmask, intensity_image):
|
|
|
57
57
|
return np.nanmedian(intensity_image[regionmask])
|
|
58
58
|
|
|
59
59
|
def intensity_nanmean(regionmask, intensity_image):
|
|
60
|
-
|
|
60
|
+
|
|
61
|
+
if np.all(intensity_image==0):
|
|
62
|
+
return np.nan
|
|
63
|
+
else:
|
|
64
|
+
return np.nanmean(intensity_image[regionmask])
|
|
61
65
|
|
|
62
66
|
def intensity_centre_of_mass_displacement(regionmask, intensity_image):
|
|
63
67
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from PyQt5.QtWidgets import QApplication, QMainWindow
|
|
2
|
-
from celldetective.utils import get_software_location
|
|
2
|
+
from celldetective.utils import get_software_location, download_zenodo_file
|
|
3
3
|
import os
|
|
4
4
|
from PyQt5.QtWidgets import QFileDialog, QWidget, QVBoxLayout, QCheckBox, QHBoxLayout, QLabel, QLineEdit, QPushButton, QMessageBox, QMenu, QAction
|
|
5
5
|
from PyQt5.QtCore import Qt, QUrl
|
|
@@ -108,6 +108,10 @@ class AppInitWindow(QMainWindow):
|
|
|
108
108
|
for i in range(len(self.recentFileActs)):
|
|
109
109
|
self.OpenRecentAction.addAction(self.recentFileActs[i])
|
|
110
110
|
|
|
111
|
+
fileMenu.addMenu(self.openDemo)
|
|
112
|
+
self.openDemo.addAction(self.openSpreadingAssayDemo)
|
|
113
|
+
self.openDemo.addAction(self.openCytotoxicityAssayDemo)
|
|
114
|
+
|
|
111
115
|
fileMenu.addAction(self.openModels)
|
|
112
116
|
fileMenu.addSeparator()
|
|
113
117
|
fileMenu.addAction(self.exitAction)
|
|
@@ -124,7 +128,7 @@ class AppInitWindow(QMainWindow):
|
|
|
124
128
|
helpMenu = QMenu("Help", self)
|
|
125
129
|
helpMenu.clear()
|
|
126
130
|
helpMenu.addAction(self.DocumentationAction)
|
|
127
|
-
helpMenu.addAction(self.SoftwareAction)
|
|
131
|
+
#helpMenu.addAction(self.SoftwareAction)
|
|
128
132
|
helpMenu.addSeparator()
|
|
129
133
|
helpMenu.addAction(self.AboutAction)
|
|
130
134
|
menuBar.addMenu(helpMenu)
|
|
@@ -137,13 +141,17 @@ class AppInitWindow(QMainWindow):
|
|
|
137
141
|
#self.newAction = QAction(self)
|
|
138
142
|
#self.newAction.setText("&New")
|
|
139
143
|
# Creating actions using the second constructor
|
|
140
|
-
self.openAction = QAction('Open
|
|
144
|
+
self.openAction = QAction('Open Project', self)
|
|
141
145
|
self.openAction.setShortcut("Ctrl+O")
|
|
142
146
|
self.openAction.setShortcutVisibleInContextMenu(True)
|
|
143
147
|
|
|
144
|
-
self.
|
|
148
|
+
self.openDemo = QMenu('Open Demo')
|
|
149
|
+
self.openSpreadingAssayDemo = QAction('Spreading Assay Demo', self)
|
|
150
|
+
self.openCytotoxicityAssayDemo = QAction('Cytotoxicity Assay Demo', self)
|
|
151
|
+
|
|
152
|
+
self.MemoryAndThreadsAction = QAction('Threads')
|
|
145
153
|
|
|
146
|
-
self.CorrectAnnotationAction = QAction('Correct a segmentation annotation
|
|
154
|
+
self.CorrectAnnotationAction = QAction('Correct a segmentation annotation')
|
|
147
155
|
|
|
148
156
|
self.newExpAction = QAction('New', self)
|
|
149
157
|
self.newExpAction.setShortcut("Ctrl+N")
|
|
@@ -154,14 +162,14 @@ class AppInitWindow(QMainWindow):
|
|
|
154
162
|
self.openModels.setShortcut("Ctrl+L")
|
|
155
163
|
self.openModels.setShortcutVisibleInContextMenu(True)
|
|
156
164
|
|
|
157
|
-
self.OpenRecentAction = QMenu('Open Recent')
|
|
165
|
+
self.OpenRecentAction = QMenu('Open Recent Project')
|
|
158
166
|
self.reload_previous_experiments()
|
|
159
167
|
|
|
160
168
|
self.DocumentationAction = QAction("Documentation", self)
|
|
161
169
|
self.DocumentationAction.setShortcut("Ctrl+D")
|
|
162
170
|
self.DocumentationAction.setShortcutVisibleInContextMenu(True)
|
|
163
171
|
|
|
164
|
-
self.SoftwareAction = QAction("Software", self) #1st arg icon(MDI6.information)
|
|
172
|
+
#self.SoftwareAction = QAction("Software", self) #1st arg icon(MDI6.information)
|
|
165
173
|
self.AboutAction = QAction("About celldetective", self)
|
|
166
174
|
|
|
167
175
|
#self.DocumentationAction.triggered.connect(self.load_previous_config)
|
|
@@ -172,9 +180,27 @@ class AppInitWindow(QMainWindow):
|
|
|
172
180
|
self.AboutAction.triggered.connect(self.open_about_window)
|
|
173
181
|
self.MemoryAndThreadsAction.triggered.connect(self.set_memory_and_threads)
|
|
174
182
|
self.CorrectAnnotationAction.triggered.connect(self.correct_seg_annotation)
|
|
175
|
-
|
|
176
183
|
self.DocumentationAction.triggered.connect(self.open_documentation)
|
|
177
184
|
|
|
185
|
+
self.openSpreadingAssayDemo.triggered.connect(self.download_spreading_assay_demo)
|
|
186
|
+
self.openCytotoxicityAssayDemo.triggered.connect(self.download_cytotoxicity_assay_demo)
|
|
187
|
+
|
|
188
|
+
def download_spreading_assay_demo(self):
|
|
189
|
+
|
|
190
|
+
self.target_dir = str(QFileDialog.getExistingDirectory(self, 'Select Folder for Download'))
|
|
191
|
+
if not os.path.exists(os.sep.join([self.target_dir,'demo_ricm'])):
|
|
192
|
+
download_zenodo_file('demo_ricm', self.target_dir)
|
|
193
|
+
self.experiment_path_selection.setText(os.sep.join([self.target_dir, 'demo_ricm']))
|
|
194
|
+
self.validate_button.click()
|
|
195
|
+
|
|
196
|
+
def download_cytotoxicity_assay_demo(self):
|
|
197
|
+
|
|
198
|
+
self.target_dir = str(QFileDialog.getExistingDirectory(self, 'Select Folder for Download'))
|
|
199
|
+
if not os.path.exists(os.sep.join([self.target_dir,'demo_adcc'])):
|
|
200
|
+
download_zenodo_file('demo_adcc', self.target_dir)
|
|
201
|
+
self.experiment_path_selection.setText(os.sep.join([self.target_dir, 'demo_adcc']))
|
|
202
|
+
self.validate_button.click()
|
|
203
|
+
|
|
178
204
|
def reload_previous_gpu_threads(self):
|
|
179
205
|
|
|
180
206
|
self.recentFileActs = []
|
|
@@ -256,7 +282,7 @@ class AppInitWindow(QMainWindow):
|
|
|
256
282
|
|
|
257
283
|
|
|
258
284
|
def open_experiment(self):
|
|
259
|
-
|
|
285
|
+
|
|
260
286
|
self.browse_experiment_folder()
|
|
261
287
|
if self.experiment_path_selection.text()!='':
|
|
262
288
|
self.open_directory()
|
|
@@ -120,9 +120,11 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
120
120
|
self.property_query_le = QLineEdit()
|
|
121
121
|
self.property_query_le.setPlaceholderText('classify points using a query such as: area > 100 or eccentricity > 0.95')
|
|
122
122
|
self.property_query_le.setToolTip('Classify points using a query on measurements.\nYou can use "and" and "or" conditions to combine\nmeasurements (e.g. "area > 100 or eccentricity > 0.95").')
|
|
123
|
+
self.property_query_le.textChanged.connect(self.activate_submit_btn)
|
|
123
124
|
hbox_classify.addWidget(self.property_query_le, 70)
|
|
124
125
|
self.submit_query_btn = QPushButton('Submit...')
|
|
125
126
|
self.submit_query_btn.clicked.connect(self.apply_property_query)
|
|
127
|
+
self.submit_query_btn.setEnabled(False)
|
|
126
128
|
hbox_classify.addWidget(self.submit_query_btn, 20)
|
|
127
129
|
layout.addLayout(hbox_classify)
|
|
128
130
|
|
|
@@ -182,15 +184,24 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
182
184
|
|
|
183
185
|
layout.addWidget(QLabel())
|
|
184
186
|
|
|
185
|
-
|
|
186
187
|
self.submit_btn = QPushButton('apply')
|
|
187
188
|
self.submit_btn.setStyleSheet(self.button_style_sheet)
|
|
188
189
|
self.submit_btn.clicked.connect(self.submit_classification)
|
|
190
|
+
self.submit_btn.setEnabled(False)
|
|
189
191
|
layout.addWidget(self.submit_btn, 30)
|
|
190
192
|
|
|
191
193
|
self.frame_slider.valueChanged.connect(self.set_frame)
|
|
192
194
|
self.alpha_slider.valueChanged.connect(self.set_transparency)
|
|
193
195
|
|
|
196
|
+
def activate_submit_btn(self):
|
|
197
|
+
|
|
198
|
+
if self.property_query_le.text()=='':
|
|
199
|
+
self.submit_query_btn.setEnabled(False)
|
|
200
|
+
self.submit_btn.setEnabled(False)
|
|
201
|
+
else:
|
|
202
|
+
self.submit_query_btn.setEnabled(True)
|
|
203
|
+
self.submit_btn.setEnabled(True)
|
|
204
|
+
|
|
194
205
|
def activate_r2(self):
|
|
195
206
|
if self.irreversible_event_btn.isChecked() and self.time_corr.isChecked():
|
|
196
207
|
for wg in [self.r2_slider, self.r2_label]:
|
|
@@ -242,6 +253,24 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
242
253
|
|
|
243
254
|
def update_props_scatter(self, feature_changed=True):
|
|
244
255
|
|
|
256
|
+
try:
|
|
257
|
+
if np.any(self.df[self.features_cb[0].currentText()].to_numpy() <= 0.):
|
|
258
|
+
if self.ax_props.get_yscale()=='log':
|
|
259
|
+
self.log_btns[0].click()
|
|
260
|
+
self.log_btns[0].setEnabled(False)
|
|
261
|
+
else:
|
|
262
|
+
self.log_btns[0].setEnabled(True)
|
|
263
|
+
|
|
264
|
+
if np.any(self.df[self.features_cb[1].currentText()].to_numpy() <= 0.):
|
|
265
|
+
if self.ax_props.get_xscale()=='log':
|
|
266
|
+
self.log_btns[1].click()
|
|
267
|
+
self.log_btns[1].setEnabled(False)
|
|
268
|
+
else:
|
|
269
|
+
self.log_btns[1].setEnabled(True)
|
|
270
|
+
except Exception as e:
|
|
271
|
+
#print(e)
|
|
272
|
+
pass
|
|
273
|
+
|
|
245
274
|
class_name = self.class_name
|
|
246
275
|
|
|
247
276
|
try:
|
|
@@ -250,32 +279,45 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
250
279
|
self.scat_props.set_offsets(self.df.loc[self.df['FRAME']==self.currentFrame,[self.features_cb[1].currentText(),self.features_cb[0].currentText()]].to_numpy())
|
|
251
280
|
colors = [color_from_status(c) for c in self.df.loc[self.df['FRAME']==self.currentFrame,class_name].to_numpy()]
|
|
252
281
|
self.scat_props.set_facecolor(colors)
|
|
253
|
-
self.scat_props.set_alpha(self.currentAlpha)
|
|
254
|
-
self.ax_props.set_xlabel(self.features_cb[1].currentText())
|
|
255
|
-
self.ax_props.set_ylabel(self.features_cb[0].currentText())
|
|
256
282
|
else:
|
|
257
283
|
self.scat_props.set_offsets(self.df[[self.features_cb[1].currentText(),self.features_cb[0].currentText()]].to_numpy())
|
|
258
284
|
colors = [color_from_status(c) for c in self.df[class_name].to_numpy()]
|
|
259
285
|
self.scat_props.set_facecolor(colors)
|
|
260
|
-
|
|
286
|
+
|
|
287
|
+
self.scat_props.set_alpha(self.currentAlpha)
|
|
288
|
+
|
|
289
|
+
if feature_changed:
|
|
290
|
+
|
|
261
291
|
self.ax_props.set_xlabel(self.features_cb[1].currentText())
|
|
262
292
|
self.ax_props.set_ylabel(self.features_cb[0].currentText())
|
|
263
293
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
294
|
+
feat_x = self.features_cb[1].currentText()
|
|
295
|
+
feat_y = self.features_cb[0].currentText()
|
|
296
|
+
min_x = self.df.dropna(subset=feat_x)[feat_x].min()
|
|
297
|
+
max_x = self.df.dropna(subset=feat_x)[feat_x].max()
|
|
298
|
+
min_y = self.df.dropna(subset=feat_y)[feat_y].min()
|
|
299
|
+
max_y = self.df.dropna(subset=feat_y)[feat_y].max()
|
|
300
|
+
|
|
301
|
+
x_padding = (max_x - min_x) * 0.05
|
|
302
|
+
y_padding = (max_y - min_y) * 0.05
|
|
303
|
+
if x_padding==0:
|
|
304
|
+
x_padding = 0.05
|
|
305
|
+
if y_padding==0:
|
|
306
|
+
y_padding = 0.05
|
|
307
|
+
|
|
308
|
+
if min_x==min_x and max_x==max_x:
|
|
309
|
+
if self.ax_props.get_xscale()=='linear':
|
|
310
|
+
self.ax_props.set_xlim(min_x - x_padding, max_x + x_padding)
|
|
311
|
+
else:
|
|
312
|
+
self.ax_props.set_xlim(min_x, max_x)
|
|
313
|
+
if min_y==min_y and max_y==max_y:
|
|
314
|
+
if self.ax_props.get_yscale()=='linear':
|
|
315
|
+
self.ax_props.set_ylim(min_y - y_padding, max_y + y_padding)
|
|
316
|
+
else:
|
|
317
|
+
self.ax_props.set_ylim(min_y, max_y)
|
|
276
318
|
|
|
277
|
-
if feature_changed:
|
|
278
319
|
self.propscanvas.canvas.toolbar.update()
|
|
320
|
+
|
|
279
321
|
self.propscanvas.canvas.draw_idle()
|
|
280
322
|
|
|
281
323
|
except Exception as e:
|
|
@@ -284,7 +326,20 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
284
326
|
def apply_property_query(self):
|
|
285
327
|
|
|
286
328
|
query = self.property_query_le.text()
|
|
287
|
-
|
|
329
|
+
|
|
330
|
+
try:
|
|
331
|
+
self.df = classify_cells_from_query(self.df, self.name_le.text(), query)
|
|
332
|
+
except Exception as e:
|
|
333
|
+
msgBox = QMessageBox()
|
|
334
|
+
msgBox.setIcon(QMessageBox.Warning)
|
|
335
|
+
msgBox.setText(f"The query could not be understood. No filtering was applied. {e}")
|
|
336
|
+
msgBox.setWindowTitle("Warning")
|
|
337
|
+
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
338
|
+
returnValue = msgBox.exec()
|
|
339
|
+
if returnValue == QMessageBox.Ok:
|
|
340
|
+
self.auto_close = False
|
|
341
|
+
return None
|
|
342
|
+
|
|
288
343
|
self.class_name = "status_"+self.name_le.text()
|
|
289
344
|
if self.df is None:
|
|
290
345
|
msgBox = QMessageBox()
|
|
@@ -294,8 +349,10 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
294
349
|
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
295
350
|
returnValue = msgBox.exec()
|
|
296
351
|
if returnValue == QMessageBox.Ok:
|
|
352
|
+
self.auto_close = False
|
|
297
353
|
return None
|
|
298
|
-
|
|
354
|
+
|
|
355
|
+
self.update_props_scatter(feature_changed=False)
|
|
299
356
|
|
|
300
357
|
def set_frame(self, value):
|
|
301
358
|
xlim=self.ax_props.get_xlim()
|
|
@@ -329,12 +386,14 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
329
386
|
self.project_times_btn.setIcon(icon(MDI6.math_integral_box,color="black"))
|
|
330
387
|
self.project_times_btn.setIconSize(QSize(20, 20))
|
|
331
388
|
self.frame_slider.setEnabled(False)
|
|
332
|
-
self.update_props_scatter()
|
|
389
|
+
self.update_props_scatter(feature_changed=False)
|
|
333
390
|
|
|
334
391
|
def submit_classification(self):
|
|
335
392
|
|
|
336
|
-
|
|
393
|
+
self.auto_close = True
|
|
337
394
|
self.apply_property_query()
|
|
395
|
+
if not self.auto_close:
|
|
396
|
+
return None
|
|
338
397
|
|
|
339
398
|
if self.time_corr.isChecked():
|
|
340
399
|
self.class_name_user = 'class_'+self.name_le.text()
|
|
@@ -424,22 +483,39 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
424
483
|
|
|
425
484
|
if i==1:
|
|
426
485
|
try:
|
|
486
|
+
feat_x = self.features_cb[1].currentText()
|
|
487
|
+
min_x = self.df.dropna(subset=feat_x)[feat_x].min()
|
|
488
|
+
max_x = self.df.dropna(subset=feat_x)[feat_x].max()
|
|
489
|
+
x_padding = (max_x - min_x) * 0.05
|
|
490
|
+
if x_padding==0:
|
|
491
|
+
x_padding = 0.05
|
|
492
|
+
|
|
427
493
|
if self.ax_props.get_xscale()=='linear':
|
|
494
|
+
self.ax_props.set_xlim(min_x, max_x)
|
|
428
495
|
self.ax_props.set_xscale('log')
|
|
429
496
|
self.log_btns[i].setIcon(icon(MDI6.math_log,color="#1565c0"))
|
|
430
497
|
else:
|
|
431
498
|
self.ax_props.set_xscale('linear')
|
|
499
|
+
self.ax_props.set_xlim(min_x - x_padding, max_x + x_padding)
|
|
432
500
|
self.log_btns[i].setIcon(icon(MDI6.math_log,color="black"))
|
|
433
501
|
except Exception as e:
|
|
434
502
|
print(e)
|
|
435
503
|
elif i==0:
|
|
436
504
|
try:
|
|
505
|
+
feat_y = self.features_cb[0].currentText()
|
|
506
|
+
min_y = self.df.dropna(subset=feat_y)[feat_y].min()
|
|
507
|
+
max_y = self.df.dropna(subset=feat_y)[feat_y].max()
|
|
508
|
+
y_padding = (max_y - min_y) * 0.05
|
|
509
|
+
if y_padding==0:
|
|
510
|
+
y_padding = 0.05
|
|
511
|
+
|
|
437
512
|
if self.ax_props.get_yscale()=='linear':
|
|
438
|
-
|
|
513
|
+
self.ax_props.set_ylim(min_y, max_y)
|
|
439
514
|
self.ax_props.set_yscale('log')
|
|
440
515
|
self.log_btns[i].setIcon(icon(MDI6.math_log,color="#1565c0"))
|
|
441
516
|
else:
|
|
442
517
|
self.ax_props.set_yscale('linear')
|
|
518
|
+
self.ax_props.set_ylim(min_y - y_padding, max_y + y_padding)
|
|
443
519
|
self.log_btns[i].setIcon(icon(MDI6.math_log,color="black"))
|
|
444
520
|
except Exception as e:
|
|
445
521
|
print(e)
|
|
@@ -181,7 +181,13 @@ class ControlPanel(QMainWindow, Styles):
|
|
|
181
181
|
exp_hbox = QHBoxLayout()
|
|
182
182
|
exp_hbox.addWidget(experiment_label, 25, alignment=Qt.AlignRight)
|
|
183
183
|
exp_subhbox = QHBoxLayout()
|
|
184
|
-
|
|
184
|
+
if len(name)>thresh:
|
|
185
|
+
name_cut = name[:thresh - 3]+'...'
|
|
186
|
+
else:
|
|
187
|
+
name_cut = name
|
|
188
|
+
exp_name_lbl = QLabel(name_cut)
|
|
189
|
+
exp_name_lbl.setToolTip(name)
|
|
190
|
+
exp_subhbox.addWidget(exp_name_lbl, 90, alignment=Qt.AlignLeft)
|
|
185
191
|
exp_subhbox.addWidget(self.folder_exp_btn, 5, alignment=Qt.AlignRight)
|
|
186
192
|
exp_subhbox.addWidget(self.edit_config_button, 5, alignment=Qt.AlignRight)
|
|
187
193
|
exp_hbox.addLayout(exp_subhbox, 75)
|