celldetective 1.3.0__py3-none-any.whl → 1.3.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/events.py +86 -11
- celldetective/extra_properties.py +5 -1
- celldetective/gui/InitWindow.py +35 -9
- celldetective/gui/classifier_widget.py +57 -24
- celldetective/gui/layouts.py +128 -7
- celldetective/gui/measurement_options.py +3 -3
- celldetective/gui/process_block.py +46 -12
- celldetective/gui/retrain_segmentation_model_options.py +24 -10
- celldetective/gui/survival_ui.py +19 -2
- celldetective/gui/viewers.py +263 -3
- celldetective/io.py +4 -1
- celldetective/links/zenodo.json +136 -123
- celldetective/measure.py +5 -5
- celldetective/models/tracking_configs/biased_motion.json +68 -0
- celldetective/models/tracking_configs/no_z_motion.json +202 -0
- celldetective/preprocessing.py +172 -3
- celldetective/signals.py +5 -2
- celldetective/tracking.py +7 -3
- celldetective/utils.py +6 -6
- {celldetective-1.3.0.dist-info → celldetective-1.3.1.dist-info}/METADATA +3 -3
- {celldetective-1.3.0.dist-info → celldetective-1.3.1.dist-info}/RECORD +26 -24
- {celldetective-1.3.0.dist-info → celldetective-1.3.1.dist-info}/WHEEL +1 -1
- {celldetective-1.3.0.dist-info → celldetective-1.3.1.dist-info}/LICENSE +0 -0
- {celldetective-1.3.0.dist-info → celldetective-1.3.1.dist-info}/entry_points.txt +0 -0
- {celldetective-1.3.0.dist-info → celldetective-1.3.1.dist-info}/top_level.txt +0 -0
|
@@ -30,10 +30,10 @@ import json
|
|
|
30
30
|
import psutil
|
|
31
31
|
from celldetective.neighborhood import compute_neighborhood_at_position, compute_contact_neighborhood_at_position
|
|
32
32
|
from celldetective.gui.gui_utils import FigureCanvas
|
|
33
|
-
from celldetective.preprocessing import correct_background_model_free, correct_background_model
|
|
33
|
+
from celldetective.preprocessing import correct_background_model_free, correct_background_model, correct_channel_offset
|
|
34
34
|
from celldetective.utils import _estimate_scale_factor, _extract_channel_indices_from_config, _extract_channel_indices, ConfigSectionMap, _extract_nbr_channels_from_config, _get_img_num_per_channel, normalize_per_channel
|
|
35
35
|
from celldetective.gui.gui_utils import ThresholdLineEdit, QuickSliderLayout, help_generic
|
|
36
|
-
from celldetective.gui.layouts import CellposeParamsWidget, StarDistParamsWidget, BackgroundModelFreeCorrectionLayout, ProtocolDesignerLayout, BackgroundFitCorrectionLayout
|
|
36
|
+
from celldetective.gui.layouts import CellposeParamsWidget, StarDistParamsWidget, BackgroundModelFreeCorrectionLayout, ProtocolDesignerLayout, BackgroundFitCorrectionLayout, ChannelOffsetOptionsLayout
|
|
37
37
|
from celldetective.gui import Styles
|
|
38
38
|
from celldetective.utils import get_software_location
|
|
39
39
|
|
|
@@ -166,10 +166,10 @@ class ProcessPanel(QFrame, Styles):
|
|
|
166
166
|
self.generate_signal_analysis_options()
|
|
167
167
|
|
|
168
168
|
self.grid_contents.addWidget(QHSeperationLine(), 9, 0, 1, 4)
|
|
169
|
-
self.view_tab_btn = QPushButton("
|
|
169
|
+
self.view_tab_btn = QPushButton("Explore table")
|
|
170
170
|
self.view_tab_btn.setStyleSheet(self.button_style_sheet_2)
|
|
171
171
|
self.view_tab_btn.clicked.connect(self.view_table_ui)
|
|
172
|
-
self.view_tab_btn.setToolTip('
|
|
172
|
+
self.view_tab_btn.setToolTip('Explore table')
|
|
173
173
|
self.view_tab_btn.setIcon(icon(MDI6.table,color="#1565c0"))
|
|
174
174
|
self.view_tab_btn.setIconSize(QSize(20, 20))
|
|
175
175
|
#self.view_tab_btn.setEnabled(False)
|
|
@@ -177,7 +177,7 @@ class ProcessPanel(QFrame, Styles):
|
|
|
177
177
|
|
|
178
178
|
self.grid_contents.addWidget(QHSeperationLine(), 9, 0, 1, 4)
|
|
179
179
|
self.submit_btn = QPushButton("Submit")
|
|
180
|
-
self.submit_btn.setStyleSheet(self.
|
|
180
|
+
self.submit_btn.setStyleSheet(self.button_style_sheet)
|
|
181
181
|
self.submit_btn.clicked.connect(self.process_population)
|
|
182
182
|
self.grid_contents.addWidget(self.submit_btn, 11, 0, 1, 4)
|
|
183
183
|
|
|
@@ -228,7 +228,7 @@ class ProcessPanel(QFrame, Styles):
|
|
|
228
228
|
|
|
229
229
|
signal_layout = QVBoxLayout()
|
|
230
230
|
signal_hlayout = QHBoxLayout()
|
|
231
|
-
self.signal_analysis_action = QCheckBox("
|
|
231
|
+
self.signal_analysis_action = QCheckBox("DETECT EVENTS")
|
|
232
232
|
self.signal_analysis_action.setStyleSheet("""
|
|
233
233
|
font-size: 10px;
|
|
234
234
|
padding-left: 10px;
|
|
@@ -1283,10 +1283,10 @@ class NeighPanel(QFrame, Styles):
|
|
|
1283
1283
|
self.grid_contents.addLayout(signal_layout, 7, 0, 1, 4)
|
|
1284
1284
|
self.grid_contents.addWidget(QHSeperationLine(), 11, 0, 1, 4)
|
|
1285
1285
|
|
|
1286
|
-
self.view_tab_btn = QPushButton("
|
|
1286
|
+
self.view_tab_btn = QPushButton("Explore table")
|
|
1287
1287
|
self.view_tab_btn.setStyleSheet(self.button_style_sheet_2)
|
|
1288
1288
|
self.view_tab_btn.clicked.connect(self.view_table_ui)
|
|
1289
|
-
self.view_tab_btn.setToolTip('
|
|
1289
|
+
self.view_tab_btn.setToolTip('Explore table')
|
|
1290
1290
|
self.view_tab_btn.setIcon(icon(MDI6.table,color="#1565c0"))
|
|
1291
1291
|
self.view_tab_btn.setIconSize(QSize(20, 20))
|
|
1292
1292
|
#self.view_tab_btn.setEnabled(False)
|
|
@@ -1295,7 +1295,7 @@ class NeighPanel(QFrame, Styles):
|
|
|
1295
1295
|
#self.grid_contents.addWidget(QLabel(''), 12, 0, 1, 4)
|
|
1296
1296
|
|
|
1297
1297
|
self.submit_btn = QPushButton("Submit")
|
|
1298
|
-
self.submit_btn.setStyleSheet(self.
|
|
1298
|
+
self.submit_btn.setStyleSheet(self.button_style_sheet)
|
|
1299
1299
|
self.submit_btn.setToolTip("Compute the neighborhoods of the selected positions.")
|
|
1300
1300
|
self.submit_btn.clicked.connect(self.process_neighborhood)
|
|
1301
1301
|
self.grid_contents.addWidget(self.submit_btn, 14, 0, 1, 4)
|
|
@@ -1657,12 +1657,33 @@ class PreprocessingPanel(QFrame, Styles):
|
|
|
1657
1657
|
|
|
1658
1658
|
self.protocol_layout.title_layout.addWidget(self.help_background_btn, 5, alignment=Qt.AlignRight)
|
|
1659
1659
|
|
|
1660
|
+
|
|
1661
|
+
self.channel_offset_correction_layout = QVBoxLayout()
|
|
1662
|
+
|
|
1663
|
+
self.channel_shift_lbl = QLabel("CHANNEL OFFSET CORRECTION")
|
|
1664
|
+
self.channel_shift_lbl.setStyleSheet("""
|
|
1665
|
+
font-weight: bold;
|
|
1666
|
+
padding: 0px;
|
|
1667
|
+
""")
|
|
1668
|
+
self.channel_offset_correction_layout.addWidget(self.channel_shift_lbl, alignment=Qt.AlignCenter)
|
|
1669
|
+
|
|
1670
|
+
self.channel_offset_options_layout = ChannelOffsetOptionsLayout(self)
|
|
1671
|
+
self.channel_offset_correction_layout.addLayout(self.channel_offset_options_layout)
|
|
1672
|
+
|
|
1673
|
+
self.protocol_layout.correction_layout.addWidget(QLabel(''))
|
|
1674
|
+
self.protocol_layout.correction_layout.addLayout(self.channel_offset_correction_layout)
|
|
1675
|
+
|
|
1660
1676
|
self.grid_contents.addLayout(self.protocol_layout,0,0,1,4)
|
|
1677
|
+
|
|
1661
1678
|
self.submit_preprocessing_btn = QPushButton("Submit")
|
|
1662
|
-
self.submit_preprocessing_btn.setStyleSheet(self.
|
|
1679
|
+
self.submit_preprocessing_btn.setStyleSheet(self.button_style_sheet)
|
|
1663
1680
|
self.submit_preprocessing_btn.clicked.connect(self.launch_preprocessing)
|
|
1681
|
+
|
|
1664
1682
|
self.grid_contents.addWidget(self.submit_preprocessing_btn, 1,0,1,4)
|
|
1665
1683
|
|
|
1684
|
+
def add_offset_instructions_to_parent_list(self):
|
|
1685
|
+
print('adding instructions')
|
|
1686
|
+
|
|
1666
1687
|
|
|
1667
1688
|
def launch_preprocessing(self):
|
|
1668
1689
|
|
|
@@ -1712,7 +1733,7 @@ class PreprocessingPanel(QFrame, Styles):
|
|
|
1712
1733
|
export_prefix = None
|
|
1713
1734
|
|
|
1714
1735
|
if correction_protocol['correction_type']=='model-free':
|
|
1715
|
-
|
|
1736
|
+
print(f'Model-free correction; {movie_prefix=} {export_prefix=}')
|
|
1716
1737
|
correct_background_model_free(self.exp_dir,
|
|
1717
1738
|
well_option=well_option,
|
|
1718
1739
|
position_option=pos_option,
|
|
@@ -1726,7 +1747,7 @@ class PreprocessingPanel(QFrame, Styles):
|
|
|
1726
1747
|
)
|
|
1727
1748
|
|
|
1728
1749
|
elif correction_protocol['correction_type']=='fit':
|
|
1729
|
-
|
|
1750
|
+
print(f'Fit correction; {movie_prefix=} {export_prefix=} {correction_protocol=}')
|
|
1730
1751
|
correct_background_model(self.exp_dir,
|
|
1731
1752
|
well_option=well_option,
|
|
1732
1753
|
position_option=pos_option,
|
|
@@ -1738,6 +1759,19 @@ class PreprocessingPanel(QFrame, Styles):
|
|
|
1738
1759
|
export_prefix = export_prefix,
|
|
1739
1760
|
**correction_protocol,
|
|
1740
1761
|
)
|
|
1762
|
+
elif correction_protocol['correction_type']=='offset':
|
|
1763
|
+
print(f'Offset correction; {movie_prefix=} {export_prefix=} {correction_protocol=}')
|
|
1764
|
+
correct_channel_offset(self.exp_dir,
|
|
1765
|
+
well_option=well_option,
|
|
1766
|
+
position_option=pos_option,
|
|
1767
|
+
export= True,
|
|
1768
|
+
return_stacks=False,
|
|
1769
|
+
show_progress_per_well = True,
|
|
1770
|
+
show_progress_per_pos = True,
|
|
1771
|
+
movie_prefix = movie_prefix,
|
|
1772
|
+
export_prefix = export_prefix,
|
|
1773
|
+
**correction_protocol,
|
|
1774
|
+
)
|
|
1741
1775
|
print('Done.')
|
|
1742
1776
|
|
|
1743
1777
|
|
|
@@ -8,7 +8,7 @@ from superqt import QLabeledDoubleSlider,QLabeledSlider
|
|
|
8
8
|
from superqt.fonticon import icon
|
|
9
9
|
from fonticon_mdi6 import MDI6
|
|
10
10
|
from celldetective.utils import get_software_location
|
|
11
|
-
from celldetective.io import get_segmentation_datasets_list, locate_segmentation_dataset
|
|
11
|
+
from celldetective.io import get_segmentation_datasets_list, locate_segmentation_dataset, get_segmentation_models_list
|
|
12
12
|
from celldetective.segmentation import train_segmentation_model
|
|
13
13
|
from celldetective.gui.layouts import CellposeParamsWidget
|
|
14
14
|
import numpy as np
|
|
@@ -85,8 +85,11 @@ class ConfigSegmentationModelTraining(QMainWindow, Styles):
|
|
|
85
85
|
self.submit_btn.clicked.connect(self.prep_model)
|
|
86
86
|
self.main_layout.addWidget(self.submit_btn)
|
|
87
87
|
self.submit_btn.setEnabled(False)
|
|
88
|
+
self.submit_warning = QLabel('')
|
|
89
|
+
self.main_layout.addWidget(self.submit_warning)
|
|
88
90
|
|
|
89
91
|
self.spatial_calib_le.textChanged.connect(self.activate_train_btn)
|
|
92
|
+
self.modelname_le.setText(f"Untitled_model_{datetime.today().strftime('%Y-%m-%d')}")
|
|
90
93
|
|
|
91
94
|
#self.populate_left_panel()
|
|
92
95
|
#grid.addLayout(self.left_side, 0, 0, 1, 1)
|
|
@@ -273,6 +276,7 @@ class ConfigSegmentationModelTraining(QMainWindow, Styles):
|
|
|
273
276
|
modelname_layout.addWidget(QLabel('Model name: '), 30)
|
|
274
277
|
self.modelname_le = QLineEdit()
|
|
275
278
|
self.modelname_le.setText(f"Untitled_model_{datetime.today().strftime('%Y-%m-%d')}")
|
|
279
|
+
self.modelname_le.textChanged.connect(self.activate_train_btn)
|
|
276
280
|
modelname_layout.addWidget(self.modelname_le, 70)
|
|
277
281
|
layout.addLayout(modelname_layout)
|
|
278
282
|
|
|
@@ -317,13 +321,21 @@ class ConfigSegmentationModelTraining(QMainWindow, Styles):
|
|
|
317
321
|
spatial_calib_layout.addWidget(self.spatial_calib_le, 70)
|
|
318
322
|
layout.addLayout(spatial_calib_layout)
|
|
319
323
|
|
|
320
|
-
|
|
321
|
-
|
|
322
324
|
def activate_train_btn(self):
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
325
|
+
|
|
326
|
+
current_name = self.modelname_le.text()
|
|
327
|
+
models = get_segmentation_models_list(mode=self.mode, return_path=False)
|
|
328
|
+
if not current_name in models and not self.spatial_calib_le.text()=='' and not np.all([cb.currentText()=='--' for cb in self.ch_norm.channel_cbs]):
|
|
326
329
|
self.submit_btn.setEnabled(True)
|
|
330
|
+
self.submit_warning.setText('')
|
|
331
|
+
else:
|
|
332
|
+
self.submit_btn.setEnabled(False)
|
|
333
|
+
if current_name in models:
|
|
334
|
+
self.submit_warning.setText('A model with this name already exists... Please pick another.')
|
|
335
|
+
elif self.spatial_calib_le.text()=='':
|
|
336
|
+
self.submit_warning.setText('Please provide a valid spatial calibration...')
|
|
337
|
+
elif np.all([cb.currentText()=='--' for cb in self.ch_norm.channel_cbs]):
|
|
338
|
+
self.submit_warning.setText('Please provide valid channels...')
|
|
327
339
|
|
|
328
340
|
def rescale_slider(self):
|
|
329
341
|
if self.stardist_model.isChecked():
|
|
@@ -336,11 +348,13 @@ class ConfigSegmentationModelTraining(QMainWindow, Styles):
|
|
|
336
348
|
|
|
337
349
|
def showDialog_pretrained(self):
|
|
338
350
|
|
|
339
|
-
try:
|
|
340
|
-
|
|
341
|
-
except:
|
|
342
|
-
|
|
351
|
+
# try:
|
|
352
|
+
# self.cancel_pretrained.click()
|
|
353
|
+
# except Exception as e:
|
|
354
|
+
# print(e)
|
|
355
|
+
# pass
|
|
343
356
|
|
|
357
|
+
self.clear_pretrained()
|
|
344
358
|
self.pretrained_model = None
|
|
345
359
|
self.pretrained_model = QFileDialog.getExistingDirectory(
|
|
346
360
|
self, "Open Directory",
|
celldetective/gui/survival_ui.py
CHANGED
|
@@ -125,6 +125,13 @@ class ConfigSurvival(QWidget, Styles):
|
|
|
125
125
|
select_layout.addWidget(self.query_le, 66)
|
|
126
126
|
main_layout.addLayout(select_layout)
|
|
127
127
|
|
|
128
|
+
time_cut_layout = QHBoxLayout()
|
|
129
|
+
time_cut_layout.addWidget(QLabel('cut observation time [min]: '), 33)
|
|
130
|
+
self.query_time_cut = QLineEdit()
|
|
131
|
+
self.query_time_cut.setValidator(self.float_validator)
|
|
132
|
+
time_cut_layout.addWidget(self.query_time_cut, 66)
|
|
133
|
+
main_layout.addLayout(time_cut_layout)
|
|
134
|
+
|
|
128
135
|
self.cbs[0].setCurrentIndex(0)
|
|
129
136
|
self.cbs[1].setCurrentText('t_firstdetection')
|
|
130
137
|
|
|
@@ -259,17 +266,27 @@ class ConfigSurvival(QWidget, Styles):
|
|
|
259
266
|
|
|
260
267
|
def compute_survival_functions(self):
|
|
261
268
|
|
|
269
|
+
cut_observation_time = None
|
|
270
|
+
try:
|
|
271
|
+
cut_observation_time = float(self.query_time_cut.text()) / self.FrameToMin
|
|
272
|
+
if not 0<cut_observation_time<=(self.df['FRAME'].max()):
|
|
273
|
+
print('Invalid cut time (larger than movie length)... Not applied.')
|
|
274
|
+
cut_observation_time = None
|
|
275
|
+
except Exception as e:
|
|
276
|
+
pass
|
|
277
|
+
print(f"{cut_observation_time=}")
|
|
278
|
+
|
|
262
279
|
# Per position survival
|
|
263
280
|
for block,movie_group in self.df.groupby(['well','position']):
|
|
264
281
|
|
|
265
|
-
ks = compute_survival(movie_group, self.class_of_interest, self.cbs[2].currentText(), t_reference=self.cbs[1].currentText(), FrameToMin=self.FrameToMin)
|
|
282
|
+
ks = compute_survival(movie_group, self.class_of_interest, self.cbs[2].currentText(), t_reference=self.cbs[1].currentText(), FrameToMin=self.FrameToMin, cut_observation_time=cut_observation_time)
|
|
266
283
|
if ks is not None:
|
|
267
284
|
self.df_pos_info.loc[self.df_pos_info['pos_path']==block[1],'survival_fit'] = ks
|
|
268
285
|
|
|
269
286
|
# Per well survival
|
|
270
287
|
for well,well_group in self.df.groupby('well'):
|
|
271
288
|
|
|
272
|
-
ks = compute_survival(well_group, self.class_of_interest, self.cbs[2].currentText(), t_reference=self.cbs[1].currentText(), FrameToMin=self.FrameToMin)
|
|
289
|
+
ks = compute_survival(well_group, self.class_of_interest, self.cbs[2].currentText(), t_reference=self.cbs[1].currentText(), FrameToMin=self.FrameToMin, cut_observation_time=cut_observation_time)
|
|
273
290
|
if ks is not None:
|
|
274
291
|
self.df_well_info.loc[self.df_well_info['well_path']==well,'survival_fit'] = ks
|
|
275
292
|
|
celldetective/gui/viewers.py
CHANGED
|
@@ -11,9 +11,10 @@ from natsort import natsorted
|
|
|
11
11
|
from glob import glob
|
|
12
12
|
import os
|
|
13
13
|
|
|
14
|
-
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QPushButton, QLabel, QComboBox, QLineEdit, QListWidget
|
|
14
|
+
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QPushButton, QLabel, QComboBox, QLineEdit, QListWidget, QShortcut
|
|
15
15
|
from PyQt5.QtCore import Qt, QSize
|
|
16
|
-
from
|
|
16
|
+
from PyQt5.QtGui import QKeySequence
|
|
17
|
+
from celldetective.gui.gui_utils import FigureCanvas, center_window, QuickSliderLayout, QHSeperationLine, ThresholdLineEdit
|
|
17
18
|
from celldetective.gui import Styles
|
|
18
19
|
from superqt import QLabeledDoubleSlider, QLabeledSlider, QLabeledDoubleRangeSlider
|
|
19
20
|
from superqt.fonticon import icon
|
|
@@ -21,6 +22,7 @@ from fonticon_mdi6 import MDI6
|
|
|
21
22
|
from matplotlib_scalebar.scalebar import ScaleBar
|
|
22
23
|
import gc
|
|
23
24
|
from celldetective.utils import mask_edges
|
|
25
|
+
from scipy.ndimage import shift
|
|
24
26
|
|
|
25
27
|
class StackVisualizer(QWidget, Styles):
|
|
26
28
|
|
|
@@ -739,4 +741,262 @@ class CellSizeViewer(StackVisualizer):
|
|
|
739
741
|
|
|
740
742
|
self.diameter = value
|
|
741
743
|
self.circ.set_radius(self.diameter//2)
|
|
742
|
-
self.canvas.canvas.draw_idle()
|
|
744
|
+
self.canvas.canvas.draw_idle()
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
class ChannelOffsetViewer(StackVisualizer):
|
|
748
|
+
|
|
749
|
+
def __init__(self, parent_window=None, *args, **kwargs):
|
|
750
|
+
|
|
751
|
+
self.parent_window = parent_window
|
|
752
|
+
self.overlay_target_channel = -1
|
|
753
|
+
self.shift_vertical = 0
|
|
754
|
+
self.shift_horizontal = 0
|
|
755
|
+
super().__init__(*args, **kwargs)
|
|
756
|
+
|
|
757
|
+
self.load_stack()
|
|
758
|
+
self.canvas.layout.addWidget(QHSeperationLine())
|
|
759
|
+
|
|
760
|
+
self.generate_overlay_channel_cb()
|
|
761
|
+
self.generate_overlay_imshow()
|
|
762
|
+
|
|
763
|
+
self.generate_overlay_alpha_slider()
|
|
764
|
+
self.generate_overlay_contrast_slider()
|
|
765
|
+
|
|
766
|
+
self.generate_overlay_shift()
|
|
767
|
+
self.generate_add_to_parent_btn()
|
|
768
|
+
|
|
769
|
+
if self.overlay_target_channel==-1:
|
|
770
|
+
index = len(self.channel_names) -1
|
|
771
|
+
else:
|
|
772
|
+
index = self.overlay_target_channel
|
|
773
|
+
self.channels_overlay_cb.setCurrentIndex(index)
|
|
774
|
+
self.frame_slider.valueChanged.connect(self.change_overlay_frame)
|
|
775
|
+
|
|
776
|
+
self.define_keyboard_shortcuts()
|
|
777
|
+
|
|
778
|
+
self.channels_overlay_cb.setCurrentIndex(self.parent_window.channels_cb.currentIndex())
|
|
779
|
+
self.set_channel_index(0)
|
|
780
|
+
|
|
781
|
+
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
782
|
+
|
|
783
|
+
def generate_overlay_imshow(self):
|
|
784
|
+
self.im_overlay = self.ax.imshow(self.overlay_init_frame, cmap='Blues', interpolation='none',alpha=0.5, **self.imshow_kwargs)
|
|
785
|
+
|
|
786
|
+
def generate_overlay_alpha_slider(self):
|
|
787
|
+
# Generate the contrast slider if enabled
|
|
788
|
+
|
|
789
|
+
self.overlay_alpha_slider = QLabeledDoubleSlider()
|
|
790
|
+
alpha_layout = QuickSliderLayout(
|
|
791
|
+
label='Overlay\ntransparency: ',
|
|
792
|
+
slider=self.overlay_alpha_slider,
|
|
793
|
+
slider_initial_value=0.5,
|
|
794
|
+
slider_range=(0,1.0),
|
|
795
|
+
decimal_option=True,
|
|
796
|
+
precision=1.0E-05,
|
|
797
|
+
)
|
|
798
|
+
alpha_layout.setContentsMargins(15,0,15,0)
|
|
799
|
+
self.overlay_alpha_slider.valueChanged.connect(self.change_alpha_overlay)
|
|
800
|
+
self.canvas.layout.addLayout(alpha_layout)
|
|
801
|
+
|
|
802
|
+
|
|
803
|
+
def generate_overlay_contrast_slider(self):
|
|
804
|
+
# Generate the contrast slider if enabled
|
|
805
|
+
|
|
806
|
+
self.overlay_contrast_slider = QLabeledDoubleRangeSlider()
|
|
807
|
+
contrast_layout = QuickSliderLayout(
|
|
808
|
+
label='Overlay contrast: ',
|
|
809
|
+
slider=self.overlay_contrast_slider,
|
|
810
|
+
slider_initial_value=[np.nanpercentile(self.overlay_init_frame, 0.1),np.nanpercentile(self.overlay_init_frame, 99.99)],
|
|
811
|
+
slider_range=(np.nanmin(self.overlay_init_frame),np.nanmax(self.overlay_init_frame)),
|
|
812
|
+
decimal_option=True,
|
|
813
|
+
precision=1.0E-05,
|
|
814
|
+
)
|
|
815
|
+
contrast_layout.setContentsMargins(15,0,15,0)
|
|
816
|
+
self.im_overlay.set_clim(vmin=np.nanpercentile(self.overlay_init_frame, 0.1),vmax=np.nanpercentile(self.overlay_init_frame, 99.99))
|
|
817
|
+
self.overlay_contrast_slider.valueChanged.connect(self.change_contrast_overlay)
|
|
818
|
+
self.canvas.layout.addLayout(contrast_layout)
|
|
819
|
+
|
|
820
|
+
def set_overlay_channel_index(self, value):
|
|
821
|
+
# Set the channel index based on dropdown value
|
|
822
|
+
|
|
823
|
+
self.overlay_target_channel = value
|
|
824
|
+
self.overlay_init_contrast = True
|
|
825
|
+
if self.mode == 'direct':
|
|
826
|
+
self.overlay_last_frame = self.stack[-1,:,:,self.overlay_target_channel]
|
|
827
|
+
elif self.mode == 'virtual':
|
|
828
|
+
self.overlay_last_frame = load_frames(self.img_num_per_channel[self.overlay_target_channel, self.stack_length-1],
|
|
829
|
+
self.stack_path,
|
|
830
|
+
normalize_input=False).astype(float)[:,:,0]
|
|
831
|
+
self.change_overlay_frame(self.frame_slider.value())
|
|
832
|
+
self.overlay_init_contrast = False
|
|
833
|
+
|
|
834
|
+
def generate_overlay_channel_cb(self):
|
|
835
|
+
|
|
836
|
+
assert self.channel_names is not None
|
|
837
|
+
assert len(self.channel_names)==self.n_channels
|
|
838
|
+
|
|
839
|
+
channel_layout = QHBoxLayout()
|
|
840
|
+
channel_layout.setContentsMargins(15,0,15,0)
|
|
841
|
+
channel_layout.addWidget(QLabel('Overlay channel: '), 25)
|
|
842
|
+
|
|
843
|
+
self.channels_overlay_cb = QComboBox()
|
|
844
|
+
self.channels_overlay_cb.addItems(self.channel_names)
|
|
845
|
+
self.channels_overlay_cb.currentIndexChanged.connect(self.set_overlay_channel_index)
|
|
846
|
+
channel_layout.addWidget(self.channels_overlay_cb, 75)
|
|
847
|
+
self.canvas.layout.addLayout(channel_layout)
|
|
848
|
+
|
|
849
|
+
def generate_overlay_shift(self):
|
|
850
|
+
|
|
851
|
+
shift_layout = QHBoxLayout()
|
|
852
|
+
shift_layout.setContentsMargins(15,0,15,0)
|
|
853
|
+
shift_layout.addWidget(QLabel('shift (h): '), 20, alignment=Qt.AlignRight)
|
|
854
|
+
|
|
855
|
+
self.apply_shift_btn = QPushButton('Apply')
|
|
856
|
+
self.apply_shift_btn.setStyleSheet(self.button_style_sheet_2)
|
|
857
|
+
self.apply_shift_btn.setToolTip('Apply the shift to the overlay channel.')
|
|
858
|
+
self.apply_shift_btn.clicked.connect(self.shift_generic)
|
|
859
|
+
|
|
860
|
+
self.set_shift_btn = QPushButton('Set')
|
|
861
|
+
|
|
862
|
+
self.horizontal_shift_le = ThresholdLineEdit(init_value=self.shift_horizontal, connected_buttons=[self.apply_shift_btn, self.set_shift_btn],placeholder='horizontal shift [pixels]', value_type='float')
|
|
863
|
+
shift_layout.addWidget(self.horizontal_shift_le, 20)
|
|
864
|
+
|
|
865
|
+
shift_layout.addWidget(QLabel('shift (v): '), 20, alignment=Qt.AlignRight)
|
|
866
|
+
|
|
867
|
+
self.vertical_shift_le = ThresholdLineEdit(init_value=self.shift_vertical, connected_buttons=[self.apply_shift_btn, self.set_shift_btn],placeholder='vertical shift [pixels]', value_type='float')
|
|
868
|
+
shift_layout.addWidget(self.vertical_shift_le, 20)
|
|
869
|
+
|
|
870
|
+
shift_layout.addWidget(self.apply_shift_btn, 20)
|
|
871
|
+
|
|
872
|
+
self.canvas.layout.addLayout(shift_layout)
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
def change_overlay_frame(self, value):
|
|
876
|
+
# Change the displayed frame based on slider value
|
|
877
|
+
|
|
878
|
+
if self.mode=='virtual':
|
|
879
|
+
|
|
880
|
+
self.overlay_init_frame = load_frames(self.img_num_per_channel[self.overlay_target_channel, value],
|
|
881
|
+
self.stack_path,
|
|
882
|
+
normalize_input=False
|
|
883
|
+
).astype(float)[:,:,0]
|
|
884
|
+
elif self.mode=='direct':
|
|
885
|
+
self.overlay_init_frame = self.stack[value,:,:,self.overlay_target_channel].copy()
|
|
886
|
+
|
|
887
|
+
self.im_overlay.set_data(self.overlay_init_frame)
|
|
888
|
+
|
|
889
|
+
if self.overlay_init_contrast:
|
|
890
|
+
self.im_overlay.autoscale()
|
|
891
|
+
I_min, I_max = self.im_overlay.get_clim()
|
|
892
|
+
self.overlay_contrast_slider.setRange(np.nanmin([self.overlay_init_frame,self.overlay_last_frame]),np.nanmax([self.overlay_init_frame,self.overlay_last_frame]))
|
|
893
|
+
self.overlay_contrast_slider.setValue((I_min,I_max))
|
|
894
|
+
|
|
895
|
+
if self.create_contrast_slider:
|
|
896
|
+
self.change_contrast_overlay(self.overlay_contrast_slider.value())
|
|
897
|
+
|
|
898
|
+
def locate_image_virtual(self):
|
|
899
|
+
# Locate the stack of images if provided as a file
|
|
900
|
+
self.stack_length = auto_load_number_of_frames(self.stack_path)
|
|
901
|
+
if self.stack_length is None:
|
|
902
|
+
stack = imread(self.stack_path)
|
|
903
|
+
self.stack_length = len(stack)
|
|
904
|
+
del stack
|
|
905
|
+
gc.collect()
|
|
906
|
+
|
|
907
|
+
self.mid_time = self.stack_length // 2
|
|
908
|
+
self.img_num_per_channel = _get_img_num_per_channel(np.arange(self.n_channels), self.stack_length, self.n_channels)
|
|
909
|
+
|
|
910
|
+
self.init_frame = load_frames(self.img_num_per_channel[self.target_channel, self.mid_time],
|
|
911
|
+
self.stack_path,
|
|
912
|
+
normalize_input=False).astype(float)[:,:,0]
|
|
913
|
+
self.last_frame = load_frames(self.img_num_per_channel[self.target_channel, self.stack_length-1],
|
|
914
|
+
self.stack_path,
|
|
915
|
+
normalize_input=False).astype(float)[:,:,0]
|
|
916
|
+
self.overlay_init_frame = load_frames(self.img_num_per_channel[self.overlay_target_channel, self.mid_time],
|
|
917
|
+
self.stack_path,
|
|
918
|
+
normalize_input=False).astype(float)[:,:,0]
|
|
919
|
+
self.overlay_last_frame = load_frames(self.img_num_per_channel[self.overlay_target_channel, self.stack_length-1],
|
|
920
|
+
self.stack_path,
|
|
921
|
+
normalize_input=False).astype(float)[:,:,0]
|
|
922
|
+
|
|
923
|
+
def change_contrast_overlay(self, value):
|
|
924
|
+
# Change contrast based on slider value
|
|
925
|
+
|
|
926
|
+
vmin = value[0]
|
|
927
|
+
vmax = value[1]
|
|
928
|
+
self.im_overlay.set_clim(vmin=vmin, vmax=vmax)
|
|
929
|
+
self.fig.canvas.draw_idle()
|
|
930
|
+
|
|
931
|
+
def change_alpha_overlay(self, value):
|
|
932
|
+
# Change contrast based on slider value
|
|
933
|
+
|
|
934
|
+
alpha = value
|
|
935
|
+
self.im_overlay.set_alpha(alpha)
|
|
936
|
+
self.fig.canvas.draw_idle()
|
|
937
|
+
|
|
938
|
+
|
|
939
|
+
def define_keyboard_shortcuts(self):
|
|
940
|
+
|
|
941
|
+
self.shift_up_shortcut = QShortcut(QKeySequence(Qt.Key_Up), self.canvas)
|
|
942
|
+
self.shift_up_shortcut.activated.connect(self.shift_overlay_up)
|
|
943
|
+
|
|
944
|
+
self.shift_down_shortcut = QShortcut(QKeySequence(Qt.Key_Down), self.canvas)
|
|
945
|
+
self.shift_down_shortcut.activated.connect(self.shift_overlay_down)
|
|
946
|
+
|
|
947
|
+
self.shift_left_shortcut = QShortcut(QKeySequence(Qt.Key_Left), self.canvas)
|
|
948
|
+
self.shift_left_shortcut.activated.connect(self.shift_overlay_left)
|
|
949
|
+
|
|
950
|
+
self.shift_right_shortcut = QShortcut(QKeySequence(Qt.Key_Right), self.canvas)
|
|
951
|
+
self.shift_right_shortcut.activated.connect(self.shift_overlay_right)
|
|
952
|
+
|
|
953
|
+
|
|
954
|
+
def shift_overlay_up(self):
|
|
955
|
+
self.shift_vertical -= 2
|
|
956
|
+
self.vertical_shift_le.set_threshold(self.shift_vertical)
|
|
957
|
+
#self.shift_generic()
|
|
958
|
+
self.apply_shift_btn.click()
|
|
959
|
+
|
|
960
|
+
def shift_overlay_down(self):
|
|
961
|
+
self.shift_vertical += 2
|
|
962
|
+
self.vertical_shift_le.set_threshold(self.shift_vertical)
|
|
963
|
+
#self.shift_generic()
|
|
964
|
+
self.apply_shift_btn.click()
|
|
965
|
+
|
|
966
|
+
def shift_overlay_left(self):
|
|
967
|
+
self.shift_horizontal -= 2
|
|
968
|
+
self.horizontal_shift_le.set_threshold(self.shift_horizontal)
|
|
969
|
+
#self.shift_generic()
|
|
970
|
+
self.apply_shift_btn.click()
|
|
971
|
+
|
|
972
|
+
def shift_overlay_right(self):
|
|
973
|
+
self.shift_horizontal += 2
|
|
974
|
+
self.horizontal_shift_le.set_threshold(self.shift_horizontal)
|
|
975
|
+
#self.shift_generic()
|
|
976
|
+
self.apply_shift_btn.click()
|
|
977
|
+
|
|
978
|
+
def shift_generic(self):
|
|
979
|
+
self.shift_vertical = self.vertical_shift_le.get_threshold()
|
|
980
|
+
self.shift_horizontal = self.horizontal_shift_le.get_threshold()
|
|
981
|
+
self.shifted_frame = shift(self.overlay_init_frame, [self.shift_vertical, self.shift_horizontal],prefilter=False)
|
|
982
|
+
self.im_overlay.set_data(self.shifted_frame)
|
|
983
|
+
self.fig.canvas.draw_idle()
|
|
984
|
+
|
|
985
|
+
def generate_add_to_parent_btn(self):
|
|
986
|
+
|
|
987
|
+
add_hbox = QHBoxLayout()
|
|
988
|
+
add_hbox.setContentsMargins(0,5,0,5)
|
|
989
|
+
self.set_shift_btn.clicked.connect(self.set_parent_attributes)
|
|
990
|
+
self.set_shift_btn.setStyleSheet(self.button_style_sheet)
|
|
991
|
+
add_hbox.addWidget(QLabel(''),33)
|
|
992
|
+
add_hbox.addWidget(self.set_shift_btn, 33)
|
|
993
|
+
add_hbox.addWidget(QLabel(''),33)
|
|
994
|
+
self.canvas.layout.addLayout(add_hbox)
|
|
995
|
+
|
|
996
|
+
def set_parent_attributes(self):
|
|
997
|
+
|
|
998
|
+
idx = self.channels_overlay_cb.currentIndex()
|
|
999
|
+
self.parent_window.channels_cb.setCurrentIndex(idx)
|
|
1000
|
+
self.parent_window.vertical_shift_le.set_threshold(self.vertical_shift_le.get_threshold())
|
|
1001
|
+
self.parent_window.horizontal_shift_le.set_threshold(self.horizontal_shift_le.get_threshold())
|
|
1002
|
+
self.close()
|
celldetective/io.py
CHANGED
|
@@ -766,6 +766,9 @@ def locate_stack_and_labels(position, prefix='Aligned', population="target"):
|
|
|
766
766
|
position = position.replace('\\', '/')
|
|
767
767
|
labels = locate_labels(position, population=population)
|
|
768
768
|
stack = locate_stack(position, prefix=prefix)
|
|
769
|
+
if len(labels) < len(stack):
|
|
770
|
+
fix_missing_labels(position, population=population, prefix=prefix)
|
|
771
|
+
labels = locate_labels(position, population=population)
|
|
769
772
|
assert len(stack) == len(
|
|
770
773
|
labels), f"The shape of the stack {stack.shape} does not match with the shape of the labels {labels.shape}"
|
|
771
774
|
|
|
@@ -1272,7 +1275,7 @@ def view_on_napari_btrack(data, properties, graph, stack=None, labels=None, rela
|
|
|
1272
1275
|
if stack is not None:
|
|
1273
1276
|
viewer.add_image(stack, channel_axis=-1, colormap=["gray"] * stack.shape[-1])
|
|
1274
1277
|
if labels is not None:
|
|
1275
|
-
viewer.add_labels(labels, name='segmentation', opacity=0.4)
|
|
1278
|
+
viewer.add_labels(labels.astype(int), name='segmentation', opacity=0.4)
|
|
1276
1279
|
viewer.add_points(vertices, size=4, name='points', opacity=0.3)
|
|
1277
1280
|
if data.shape[1]==4:
|
|
1278
1281
|
viewer.add_tracks(data, properties=properties, graph=graph, name='tracks')
|