celldetective 1.1.1.post3__py3-none-any.whl → 1.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- celldetective/__init__.py +2 -1
- celldetective/__main__.py +17 -0
- celldetective/extra_properties.py +62 -34
- celldetective/gui/__init__.py +1 -0
- celldetective/gui/analyze_block.py +2 -1
- celldetective/gui/classifier_widget.py +18 -10
- celldetective/gui/control_panel.py +57 -6
- celldetective/gui/layouts.py +14 -11
- celldetective/gui/neighborhood_options.py +21 -13
- celldetective/gui/plot_signals_ui.py +39 -11
- celldetective/gui/process_block.py +413 -95
- celldetective/gui/retrain_segmentation_model_options.py +17 -4
- celldetective/gui/retrain_signal_model_options.py +106 -6
- celldetective/gui/signal_annotator.py +110 -30
- celldetective/gui/signal_annotator2.py +2708 -0
- celldetective/gui/signal_annotator_options.py +3 -1
- celldetective/gui/survival_ui.py +15 -6
- celldetective/gui/tableUI.py +248 -43
- celldetective/io.py +598 -416
- celldetective/measure.py +919 -969
- celldetective/models/pair_signal_detection/blank +0 -0
- celldetective/neighborhood.py +482 -340
- celldetective/preprocessing.py +81 -61
- celldetective/relative_measurements.py +648 -0
- celldetective/scripts/analyze_signals.py +1 -1
- celldetective/scripts/measure_cells.py +28 -8
- celldetective/scripts/measure_relative.py +103 -0
- celldetective/scripts/segment_cells.py +5 -5
- celldetective/scripts/track_cells.py +4 -1
- celldetective/scripts/train_segmentation_model.py +23 -18
- celldetective/scripts/train_signal_model.py +33 -0
- celldetective/segmentation.py +67 -29
- celldetective/signals.py +402 -8
- celldetective/tracking.py +8 -2
- celldetective/utils.py +144 -12
- {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/METADATA +8 -8
- {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/RECORD +42 -38
- {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/WHEEL +1 -1
- tests/test_segmentation.py +1 -1
- {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/LICENSE +0 -0
- {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/entry_points.txt +0 -0
- {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/top_level.txt +0 -0
|
@@ -423,7 +423,7 @@ class ConfigSegmentationModelTraining(QMainWindow, Styles):
|
|
|
423
423
|
)
|
|
424
424
|
if self.dataset_folder is not None:
|
|
425
425
|
|
|
426
|
-
subfiles = glob(self.dataset_folder+"
|
|
426
|
+
subfiles = glob(self.dataset_folder+os.sep+"*.tif")
|
|
427
427
|
if len(subfiles)>0:
|
|
428
428
|
print(f'found {len(subfiles)} files in folder')
|
|
429
429
|
self.data_folder_label.setText(self.dataset_folder[:16]+'...')
|
|
@@ -459,14 +459,26 @@ class ConfigSegmentationModelTraining(QMainWindow, Styles):
|
|
|
459
459
|
self.data_folder_label.setToolTip('')
|
|
460
460
|
self.cancel_dataset.setVisible(False)
|
|
461
461
|
|
|
462
|
+
def load_stardist_train_config(self):
|
|
463
|
+
|
|
464
|
+
config = os.sep.join([self.pretrained_model,"config.json"])
|
|
465
|
+
if os.path.exists(config):
|
|
466
|
+
with open(config, 'r') as f:
|
|
467
|
+
config = json.load(f)
|
|
468
|
+
if 'train_batch_size' in config:
|
|
469
|
+
bs = config['train_batch_size']
|
|
470
|
+
self.bs_le.setText(str(bs).replace('.',','))
|
|
471
|
+
if 'train_learning_rate' in config:
|
|
472
|
+
lr = config['train_learning_rate']
|
|
473
|
+
self.lr_le.setText(str(lr).replace('.',','))
|
|
462
474
|
|
|
463
475
|
def load_pretrained_config(self):
|
|
464
476
|
|
|
465
477
|
f = open(os.sep.join([self.pretrained_model,"config_input.json"]))
|
|
466
478
|
data = json.load(f)
|
|
467
479
|
channels = data["channels"]
|
|
468
|
-
self.seg_folder = self.pretrained_model.split(
|
|
469
|
-
self.model_name = self.pretrained_model.split(
|
|
480
|
+
self.seg_folder = self.pretrained_model.split(os.sep)[-2]
|
|
481
|
+
self.model_name = self.pretrained_model.split(os.sep)[-1]
|
|
470
482
|
if self.model_name.startswith('CP') and self.seg_folder=='segmentation_generic':
|
|
471
483
|
channels = ['brightfield_channel', 'live_nuclei_channel']
|
|
472
484
|
if self.model_name=="CP_nuclei":
|
|
@@ -484,6 +496,7 @@ class ConfigSegmentationModelTraining(QMainWindow, Styles):
|
|
|
484
496
|
if model_type=='stardist':
|
|
485
497
|
self.stardist_model.setChecked(True)
|
|
486
498
|
self.cellpose_model.setChecked(False)
|
|
499
|
+
self.load_stardist_train_config()
|
|
487
500
|
else:
|
|
488
501
|
self.stardist_model.setChecked(False)
|
|
489
502
|
self.cellpose_model.setChecked(True)
|
|
@@ -593,7 +606,7 @@ class ConfigSegmentationModelTraining(QMainWindow, Styles):
|
|
|
593
606
|
|
|
594
607
|
print(training_instructions)
|
|
595
608
|
|
|
596
|
-
model_folder =
|
|
609
|
+
model_folder = os.sep.join([self.software_models_dir,model_name, ''])
|
|
597
610
|
print(model_folder)
|
|
598
611
|
if not os.path.exists(model_folder):
|
|
599
612
|
os.mkdir(model_folder)
|
|
@@ -3,11 +3,11 @@ from PyQt5.QtCore import Qt, QSize
|
|
|
3
3
|
from PyQt5.QtGui import QDoubleValidator, QIntValidator, QIcon
|
|
4
4
|
from celldetective.gui.gui_utils import center_window, FeatureChoice, ListWidget, QHSeperationLine, FigureCanvas, GeometryChoice, OperationChoice
|
|
5
5
|
from celldetective.gui.layouts import ChannelNormGenerator
|
|
6
|
-
from superqt import QLabeledDoubleRangeSlider, QLabeledDoubleSlider,QLabeledSlider
|
|
6
|
+
from superqt import QLabeledDoubleRangeSlider, QLabeledDoubleSlider, QLabeledSlider, QSearchableComboBox
|
|
7
7
|
from superqt.fonticon import icon
|
|
8
8
|
from fonticon_mdi6 import MDI6
|
|
9
9
|
from celldetective.utils import extract_experiment_channels, get_software_location
|
|
10
|
-
from celldetective.io import interpret_tracking_configuration, load_frames, locate_signal_dataset, get_signal_datasets_list
|
|
10
|
+
from celldetective.io import interpret_tracking_configuration, load_frames, locate_signal_dataset, get_signal_datasets_list, load_experiment_tables
|
|
11
11
|
from celldetective.measure import compute_haralick_features, contour_of_instance_segmentation
|
|
12
12
|
from celldetective.signals import train_signal_model
|
|
13
13
|
import numpy as np
|
|
@@ -24,6 +24,7 @@ from datetime import datetime
|
|
|
24
24
|
import pandas as pd
|
|
25
25
|
from functools import partial
|
|
26
26
|
from celldetective.gui import Styles
|
|
27
|
+
from pandas.api.types import is_numeric_dtype
|
|
27
28
|
|
|
28
29
|
class ConfigSignalModelTraining(QMainWindow, Styles):
|
|
29
30
|
|
|
@@ -32,7 +33,7 @@ class ConfigSignalModelTraining(QMainWindow, Styles):
|
|
|
32
33
|
|
|
33
34
|
"""
|
|
34
35
|
|
|
35
|
-
def __init__(self, parent_window=None):
|
|
36
|
+
def __init__(self, parent_window=None, signal_mode='single-cells'):
|
|
36
37
|
|
|
37
38
|
super().__init__()
|
|
38
39
|
self.parent_window = parent_window
|
|
@@ -43,7 +44,16 @@ class ConfigSignalModelTraining(QMainWindow, Styles):
|
|
|
43
44
|
self.soft_path = get_software_location()
|
|
44
45
|
self.pretrained_model = None
|
|
45
46
|
self.dataset_folder = None
|
|
46
|
-
self.
|
|
47
|
+
self.current_neighborhood = None
|
|
48
|
+
self.reference_population = None
|
|
49
|
+
self.neighbor_population = None
|
|
50
|
+
self.signal_mode = signal_mode
|
|
51
|
+
|
|
52
|
+
if self.signal_mode=='single-cells':
|
|
53
|
+
self.signal_models_dir = self.soft_path+os.sep+os.sep.join(['celldetective','models','signal_detection'])
|
|
54
|
+
elif self.signal_mode=='pairs':
|
|
55
|
+
self.signal_models_dir = self.soft_path+os.sep+os.sep.join(['celldetective','models','pair_signal_detection'])
|
|
56
|
+
self.mode = 'pairs'
|
|
47
57
|
|
|
48
58
|
self.onlyFloat = QDoubleValidator()
|
|
49
59
|
self.onlyInt = QIntValidator()
|
|
@@ -272,6 +282,14 @@ class ConfigSignalModelTraining(QMainWindow, Styles):
|
|
|
272
282
|
modelname_layout.addWidget(self.modelname_le, 70)
|
|
273
283
|
layout.addLayout(modelname_layout)
|
|
274
284
|
|
|
285
|
+
if self.signal_mode=='pairs':
|
|
286
|
+
neighborhood_layout = QHBoxLayout()
|
|
287
|
+
neighborhood_layout.addWidget(QLabel('neighborhood of interest: '), 30)
|
|
288
|
+
self.neighborhood_choice_cb = QSearchableComboBox()
|
|
289
|
+
self.fill_available_neighborhoods()
|
|
290
|
+
neighborhood_layout.addWidget(self.neighborhood_choice_cb, 70)
|
|
291
|
+
layout.addLayout(neighborhood_layout)
|
|
292
|
+
|
|
275
293
|
classname_layout = QHBoxLayout()
|
|
276
294
|
classname_layout.addWidget(QLabel('event name: '), 30)
|
|
277
295
|
self.class_name_le = QLineEdit()
|
|
@@ -311,6 +329,10 @@ class ConfigSignalModelTraining(QMainWindow, Styles):
|
|
|
311
329
|
self.ch_norm = ChannelNormGenerator(self, mode='signals')
|
|
312
330
|
layout.addLayout(self.ch_norm)
|
|
313
331
|
|
|
332
|
+
if self.signal_mode=='pairs':
|
|
333
|
+
self.neighborhood_choice_cb.currentIndexChanged.connect(self.neighborhood_changed)
|
|
334
|
+
self.neighborhood_changed()
|
|
335
|
+
|
|
314
336
|
model_length_layout = QHBoxLayout()
|
|
315
337
|
model_length_layout.addWidget(QLabel('Max signal length: '), 30)
|
|
316
338
|
self.model_length_slider = QLabeledSlider()
|
|
@@ -323,6 +345,84 @@ class ConfigSignalModelTraining(QMainWindow, Styles):
|
|
|
323
345
|
model_length_layout.addWidget(self.model_length_slider, 70)
|
|
324
346
|
layout.addLayout(model_length_layout)
|
|
325
347
|
|
|
348
|
+
def neighborhood_changed(self):
|
|
349
|
+
|
|
350
|
+
neigh = self.neighborhood_choice_cb.currentText()
|
|
351
|
+
self.current_neighborhood = neigh.replace('target_ref_','').replace('effector_ref_','')
|
|
352
|
+
self.reference_population = ['targets' if 'target' in neigh else 'effectors'][0]
|
|
353
|
+
if 'target' in neigh:
|
|
354
|
+
if 'self' in neigh:
|
|
355
|
+
self.neighbor_population = 'targets'
|
|
356
|
+
else:
|
|
357
|
+
self.neighbor_population = 'effectors'
|
|
358
|
+
else:
|
|
359
|
+
if 'self' in neigh:
|
|
360
|
+
self.neighbor_population = 'effectors'
|
|
361
|
+
else:
|
|
362
|
+
self.neighbor_population = 'targets'
|
|
363
|
+
|
|
364
|
+
print(f'Current neighborhood: {self.current_neighborhood}')
|
|
365
|
+
print(f'New reference population: {self.reference_population}')
|
|
366
|
+
print(f'New neighbor population: {self.neighbor_population}')
|
|
367
|
+
|
|
368
|
+
# reload reference signals / neighbor signals / pair signals
|
|
369
|
+
# fill the channel cbs
|
|
370
|
+
self.df_reference = self.dataframes[self.reference_population]
|
|
371
|
+
self.df_neighbor = self.dataframes[self.neighbor_population]
|
|
372
|
+
self.df_pairs = load_experiment_tables(self.parent_window.exp_dir, population='pairs', load_pickle=False)
|
|
373
|
+
|
|
374
|
+
self.df_reference = self.df_reference.rename(columns=lambda x: 'reference_' + x)
|
|
375
|
+
num_cols_reference = [c for c in list(self.df_reference.columns) if is_numeric_dtype(self.df_reference[c])]
|
|
376
|
+
self.df_neighbor = self.df_neighbor.rename(columns=lambda x: 'neighbor_' + x)
|
|
377
|
+
num_cols_neighbor = [c for c in list(self.df_neighbor.columns) if is_numeric_dtype(self.df_neighbor[c])]
|
|
378
|
+
self.df_pairs = self.df_pairs.rename(columns=lambda x: 'pair_' + x)
|
|
379
|
+
num_cols_pairs = [c for c in list(self.df_pairs.columns) if is_numeric_dtype(self.df_pairs[c])]
|
|
380
|
+
|
|
381
|
+
self.signals = ['--'] + num_cols_pairs + num_cols_reference + num_cols_neighbor
|
|
382
|
+
|
|
383
|
+
for cb in self.ch_norm.channel_cbs:
|
|
384
|
+
# try:
|
|
385
|
+
# cb.disconnect()
|
|
386
|
+
# except:
|
|
387
|
+
# pass
|
|
388
|
+
cb.clear()
|
|
389
|
+
cb.addItems(self.signals)
|
|
390
|
+
|
|
391
|
+
def fill_available_neighborhoods(self):
|
|
392
|
+
|
|
393
|
+
df_targets = load_experiment_tables(self.parent_window.exp_dir, population='targets', load_pickle=True)
|
|
394
|
+
df_effectors = load_experiment_tables(self.parent_window.exp_dir, population='effectors', load_pickle=True)
|
|
395
|
+
|
|
396
|
+
self.dataframes = {
|
|
397
|
+
'targets': df_targets,
|
|
398
|
+
'effectors': df_effectors,
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
self.neighborhood_cols = []
|
|
402
|
+
self.reference_populations = []
|
|
403
|
+
self.neighbor_populations = []
|
|
404
|
+
if df_targets is not None:
|
|
405
|
+
self.neighborhood_cols.extend(['target_ref_'+c for c in list(df_targets.columns) if c.startswith('neighborhood')])
|
|
406
|
+
self.reference_populations.extend(['targets' for c in list(df_targets.columns) if c.startswith('neighborhood')])
|
|
407
|
+
for c in list(df_targets.columns):
|
|
408
|
+
if c.startswith('neighborhood') and '_2_' in c:
|
|
409
|
+
self.neighbor_populations.append('effectors')
|
|
410
|
+
elif c.startswith('neighborhood') and 'self' in c:
|
|
411
|
+
self.neighbor_populations.append('targets')
|
|
412
|
+
|
|
413
|
+
if df_effectors is not None:
|
|
414
|
+
self.neighborhood_cols.extend(['effector_ref_'+c for c in list(df_effectors.columns) if c.startswith('neighborhood')])
|
|
415
|
+
self.reference_populations.extend(['effectors' for c in list(df_effectors.columns) if c.startswith('neighborhood')])
|
|
416
|
+
for c in list(df_effectors.columns):
|
|
417
|
+
if c.startswith('neighborhood') and '_2_' in c:
|
|
418
|
+
self.neighbor_populations.append('targets')
|
|
419
|
+
elif c.startswith('neighborhood') and 'self' in c:
|
|
420
|
+
self.neighbor_populations.append('effectors')
|
|
421
|
+
|
|
422
|
+
print(f"The following neighborhoods were detected: {self.neighborhood_cols=} {self.reference_populations=} {self.neighbor_populations=}")
|
|
423
|
+
|
|
424
|
+
self.neighborhood_choice_cb.addItems(self.neighborhood_cols)
|
|
425
|
+
|
|
326
426
|
def showDialog_pretrained(self):
|
|
327
427
|
|
|
328
428
|
self.pretrained_model = QFileDialog.getExistingDirectory(
|
|
@@ -481,9 +581,9 @@ class ConfigSignalModelTraining(QMainWindow, Styles):
|
|
|
481
581
|
training_instructions = {'model_name': model_name,'pretrained': pretrained_model, 'channel_option': channels, 'normalization_percentile': normalization_mode,
|
|
482
582
|
'normalization_clip': clip_values,'normalization_values': norm_values, 'model_signal_length': signal_length,
|
|
483
583
|
'recompile_pretrained': recompile_op, 'ds': data_folders, 'augmentation_factor': aug_factor, 'validation_split': val_split,
|
|
484
|
-
'learning_rate': lr, 'batch_size': bs, 'epochs': epochs, 'label': self.class_name_le.text()}
|
|
584
|
+
'learning_rate': lr, 'batch_size': bs, 'epochs': epochs, 'label': self.class_name_le.text(), 'neighborhood_of_interest': self.current_neighborhood, 'reference_population': self.reference_population, 'neighbor_population': self.neighbor_population}
|
|
485
585
|
|
|
486
|
-
model_folder = self.signal_models_dir + model_name + os.sep
|
|
586
|
+
model_folder = self.signal_models_dir +os.sep+ model_name + os.sep
|
|
487
587
|
if not os.path.exists(model_folder):
|
|
488
588
|
os.mkdir(model_folder)
|
|
489
589
|
|
|
@@ -27,6 +27,7 @@ from matplotlib.cm import tab10
|
|
|
27
27
|
import pandas as pd
|
|
28
28
|
from sklearn.preprocessing import MinMaxScaler
|
|
29
29
|
from celldetective.gui import Styles
|
|
30
|
+
from celldetective.measure import contour_of_instance_segmentation
|
|
30
31
|
|
|
31
32
|
class SignalAnnotator(QMainWindow, Styles):
|
|
32
33
|
"""
|
|
@@ -56,6 +57,7 @@ class SignalAnnotator(QMainWindow, Styles):
|
|
|
56
57
|
|
|
57
58
|
self.screen_height = self.parent_window.parent_window.parent_window.screen_height
|
|
58
59
|
self.screen_width = self.parent_window.parent_window.parent_window.screen_width
|
|
60
|
+
self.value_magnitude = 1
|
|
59
61
|
|
|
60
62
|
# default params
|
|
61
63
|
self.class_name = 'class'
|
|
@@ -76,9 +78,9 @@ class SignalAnnotator(QMainWindow, Styles):
|
|
|
76
78
|
|
|
77
79
|
self.populate_widget()
|
|
78
80
|
|
|
79
|
-
self.setMinimumWidth(int(0.8 * self.screen_width))
|
|
81
|
+
#self.setMinimumWidth(int(0.8 * self.screen_width))
|
|
80
82
|
# self.setMaximumHeight(int(0.8*self.screen_height))
|
|
81
|
-
self.setMinimumHeight(int(0.8 * self.screen_height))
|
|
83
|
+
#self.setMinimumHeight(int(0.8 * self.screen_height))
|
|
82
84
|
# self.setMaximumHeight(int(0.8*self.screen_height))
|
|
83
85
|
|
|
84
86
|
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
@@ -119,7 +121,6 @@ class SignalAnnotator(QMainWindow, Styles):
|
|
|
119
121
|
|
|
120
122
|
self.class_choice_cb.addItems(self.class_cols)
|
|
121
123
|
self.class_choice_cb.currentIndexChanged.connect(self.compute_status_and_colors)
|
|
122
|
-
self.class_choice_cb.setCurrentIndex(0)
|
|
123
124
|
|
|
124
125
|
class_hbox.addWidget(self.class_choice_cb, 70)
|
|
125
126
|
|
|
@@ -346,6 +347,8 @@ class SignalAnnotator(QMainWindow, Styles):
|
|
|
346
347
|
main_layout.addLayout(self.right_panel, 65)
|
|
347
348
|
self.button_widget.adjustSize()
|
|
348
349
|
|
|
350
|
+
self.compute_status_and_colors(0)
|
|
351
|
+
|
|
349
352
|
self.setCentralWidget(self.button_widget)
|
|
350
353
|
self.show()
|
|
351
354
|
|
|
@@ -462,6 +465,7 @@ class SignalAnnotator(QMainWindow, Styles):
|
|
|
462
465
|
self.newClassWidget.close()
|
|
463
466
|
|
|
464
467
|
def compute_status_and_colors(self, i):
|
|
468
|
+
|
|
465
469
|
self.class_name = self.class_choice_cb.currentText()
|
|
466
470
|
self.expected_status = 'status'
|
|
467
471
|
suffix = self.class_name.replace('class', '').replace('_', '', 1)
|
|
@@ -474,11 +478,15 @@ class SignalAnnotator(QMainWindow, Styles):
|
|
|
474
478
|
self.status_name = self.expected_status
|
|
475
479
|
|
|
476
480
|
print('selection and expected names: ', self.class_name, self.expected_time, self.expected_status)
|
|
481
|
+
cols = list(self.df_tracks.columns)
|
|
477
482
|
|
|
478
|
-
if self.time_name in
|
|
483
|
+
if self.time_name in cols and self.class_name in cols and not self.status_name in cols:
|
|
479
484
|
# only create the status column if it does not exist to not erase static classification results
|
|
480
485
|
self.make_status_column()
|
|
481
|
-
elif self.time_name in
|
|
486
|
+
elif self.time_name in cols and self.class_name in cols and self.df_tracks[self.status_name].isnull().all():
|
|
487
|
+
print('this is the case!', )
|
|
488
|
+
self.make_status_column()
|
|
489
|
+
elif self.time_name in cols and self.class_name in cols:
|
|
482
490
|
# all good, do nothing
|
|
483
491
|
pass
|
|
484
492
|
else:
|
|
@@ -819,6 +827,8 @@ class SignalAnnotator(QMainWindow, Styles):
|
|
|
819
827
|
|
|
820
828
|
def plot_signals(self):
|
|
821
829
|
|
|
830
|
+
range_values = []
|
|
831
|
+
|
|
822
832
|
try:
|
|
823
833
|
yvalues = []
|
|
824
834
|
for i in range(len(self.signal_choice_cb)):
|
|
@@ -834,6 +844,8 @@ class SignalAnnotator(QMainWindow, Styles):
|
|
|
834
844
|
xdata = self.df_tracks.loc[self.df_tracks['TRACK_ID'] == self.track_of_interest, 'FRAME'].to_numpy()
|
|
835
845
|
ydata = self.df_tracks.loc[
|
|
836
846
|
self.df_tracks['TRACK_ID'] == self.track_of_interest, signal_choice].to_numpy()
|
|
847
|
+
|
|
848
|
+
range_values.extend(ydata)
|
|
837
849
|
|
|
838
850
|
xdata = xdata[ydata == ydata] # remove nan
|
|
839
851
|
ydata = ydata[ydata == ydata]
|
|
@@ -855,7 +867,21 @@ class SignalAnnotator(QMainWindow, Styles):
|
|
|
855
867
|
self.cell_ax.legend()
|
|
856
868
|
self.cell_fcanvas.canvas.draw()
|
|
857
869
|
except Exception as e:
|
|
858
|
-
print(f"{e=}")
|
|
870
|
+
print(f"Plot signals: {e=}")
|
|
871
|
+
|
|
872
|
+
if len(range_values)>0:
|
|
873
|
+
range_values = np.array(range_values)
|
|
874
|
+
if len(range_values[range_values==range_values])>0:
|
|
875
|
+
if len(range_values[range_values>0])>0:
|
|
876
|
+
self.value_magnitude = np.nanpercentile(range_values, 1)
|
|
877
|
+
else:
|
|
878
|
+
self.value_magnitude = 1
|
|
879
|
+
self.non_log_ymin = 0.98*np.nanmin(range_values)
|
|
880
|
+
self.non_log_ymax = np.nanmax(range_values)*1.02
|
|
881
|
+
if self.cell_ax.get_yscale()=='linear':
|
|
882
|
+
self.cell_ax.set_ylim(self.non_log_ymin, self.non_log_ymax)
|
|
883
|
+
else:
|
|
884
|
+
self.cell_ax.set_ylim(self.value_magnitude, self.non_log_ymax)
|
|
859
885
|
|
|
860
886
|
def extract_scatter_from_trajectories(self):
|
|
861
887
|
|
|
@@ -1109,7 +1135,7 @@ class SignalAnnotator(QMainWindow, Styles):
|
|
|
1109
1135
|
if len(min_values) > 0:
|
|
1110
1136
|
self.cell_ax.set_ylim(np.amin(min_values), np.amax(max_values))
|
|
1111
1137
|
except Exception as e:
|
|
1112
|
-
print(e)
|
|
1138
|
+
print('Ylim error:',e)
|
|
1113
1139
|
|
|
1114
1140
|
def draw_frame(self, framedata):
|
|
1115
1141
|
|
|
@@ -1287,21 +1313,24 @@ class SignalAnnotator(QMainWindow, Styles):
|
|
|
1287
1313
|
"""
|
|
1288
1314
|
|
|
1289
1315
|
try:
|
|
1290
|
-
if self.cell_ax.get_yscale()
|
|
1316
|
+
if self.cell_ax.get_yscale()=='linear':
|
|
1317
|
+
ymin,ymax = self.cell_ax.get_ylim()
|
|
1291
1318
|
self.cell_ax.set_yscale('log')
|
|
1292
|
-
self.log_btn.setIcon(icon(MDI6.math_log,
|
|
1319
|
+
self.log_btn.setIcon(icon(MDI6.math_log,color="#1565c0"))
|
|
1320
|
+
self.cell_ax.set_ylim(self.value_magnitude, ymax)
|
|
1293
1321
|
else:
|
|
1294
1322
|
self.cell_ax.set_yscale('linear')
|
|
1295
|
-
self.log_btn.setIcon(icon(MDI6.math_log,
|
|
1323
|
+
self.log_btn.setIcon(icon(MDI6.math_log,color="black"))
|
|
1296
1324
|
except Exception as e:
|
|
1297
1325
|
print(e)
|
|
1298
1326
|
|
|
1299
|
-
#
|
|
1327
|
+
#self.cell_ax.autoscale()
|
|
1300
1328
|
self.cell_fcanvas.canvas.draw_idle()
|
|
1301
1329
|
|
|
1302
|
-
|
|
1303
1330
|
class MeasureAnnotator(SignalAnnotator):
|
|
1331
|
+
|
|
1304
1332
|
def __init__(self, parent_window=None):
|
|
1333
|
+
|
|
1305
1334
|
QMainWindow.__init__(self)
|
|
1306
1335
|
self.parent_window = parent_window
|
|
1307
1336
|
self.setWindowTitle("Signal annotator")
|
|
@@ -1315,11 +1344,11 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
1315
1344
|
self.int_validator = QIntValidator()
|
|
1316
1345
|
self.current_alpha=0.5
|
|
1317
1346
|
if self.mode == "targets":
|
|
1318
|
-
self.instructions_path = self.exp_dir +
|
|
1319
|
-
self.trajectories_path = self.pos + 'output
|
|
1347
|
+
self.instructions_path = self.exp_dir + os.sep.join(['configs','signal_annotator_config_targets.json'])
|
|
1348
|
+
self.trajectories_path = self.pos + os.sep.join(['output','tables','trajectories_targets.csv'])
|
|
1320
1349
|
elif self.mode == "effectors":
|
|
1321
|
-
self.instructions_path = self.exp_dir +
|
|
1322
|
-
self.trajectories_path = self.pos + 'output
|
|
1350
|
+
self.instructions_path = self.exp_dir + os.sep.join(['configs','signal_annotator_config_effectors.json'])
|
|
1351
|
+
self.trajectories_path = self.pos + os.sep.join(['output','tables','trajectories_effectors.csv'])
|
|
1323
1352
|
|
|
1324
1353
|
self.screen_height = self.parent_window.parent_window.parent_window.screen_height
|
|
1325
1354
|
self.screen_width = self.parent_window.parent_window.parent_window.screen_width
|
|
@@ -1330,8 +1359,13 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
1330
1359
|
center_window(self)
|
|
1331
1360
|
|
|
1332
1361
|
self.locate_stack()
|
|
1362
|
+
|
|
1333
1363
|
data, properties, graph, labels, _ = load_napari_data(self.pos, prefix=None, population=self.mode,return_stack=False)
|
|
1334
|
-
|
|
1364
|
+
if data is not None:
|
|
1365
|
+
self.labels = relabel_segmentation(labels,data,properties)
|
|
1366
|
+
else:
|
|
1367
|
+
self.labels = labels
|
|
1368
|
+
|
|
1335
1369
|
self.current_channel = 0
|
|
1336
1370
|
|
|
1337
1371
|
self.locate_tracks()
|
|
@@ -1577,8 +1611,10 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
1577
1611
|
self.class_choice_cb = QComboBox()
|
|
1578
1612
|
|
|
1579
1613
|
cols = np.array(self.df_tracks.columns)
|
|
1580
|
-
self.class_cols = np.array([c.startswith('group') for c in list(self.df_tracks.columns)])
|
|
1614
|
+
self.class_cols = np.array([c.startswith('group') or c.startswith('status') for c in list(self.df_tracks.columns)])
|
|
1581
1615
|
self.class_cols = list(cols[self.class_cols])
|
|
1616
|
+
print(self.class_cols)
|
|
1617
|
+
|
|
1582
1618
|
try:
|
|
1583
1619
|
self.class_cols.remove('group_id')
|
|
1584
1620
|
except Exception:
|
|
@@ -1711,6 +1747,14 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
1711
1747
|
btn_hbox.addWidget(self.save_btn, 90)
|
|
1712
1748
|
self.left_panel.addLayout(btn_hbox)
|
|
1713
1749
|
|
|
1750
|
+
self.export_btn = QPushButton('')
|
|
1751
|
+
self.export_btn.setStyleSheet(self.button_select_all)
|
|
1752
|
+
self.export_btn.clicked.connect(self.export_measurements)
|
|
1753
|
+
self.export_btn.setIcon(icon(MDI6.export, color="black"))
|
|
1754
|
+
self.export_btn.setIconSize(QSize(25, 25))
|
|
1755
|
+
btn_hbox.addWidget(self.export_btn, 10)
|
|
1756
|
+
self.left_panel.addLayout(btn_hbox)
|
|
1757
|
+
|
|
1714
1758
|
# Animation
|
|
1715
1759
|
animation_buttons_box = QHBoxLayout()
|
|
1716
1760
|
|
|
@@ -1810,6 +1854,28 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
1810
1854
|
# del self.img
|
|
1811
1855
|
gc.collect()
|
|
1812
1856
|
|
|
1857
|
+
|
|
1858
|
+
def export_measurements(self):
|
|
1859
|
+
|
|
1860
|
+
auto_dataset_name = self.pos.split(os.sep)[-4] + '_' + self.pos.split(os.sep)[-2] + f'_{str(self.current_frame).zfill(3)}' + f'_{self.status_name}.npy'
|
|
1861
|
+
|
|
1862
|
+
if self.normalized_signals:
|
|
1863
|
+
self.normalize_features_btn.click()
|
|
1864
|
+
|
|
1865
|
+
subdf = self.df_tracks.loc[self.df_tracks['FRAME']==self.current_frame,:]
|
|
1866
|
+
subdf['class'] = subdf[self.status_name]
|
|
1867
|
+
dico = subdf.to_dict('records')
|
|
1868
|
+
|
|
1869
|
+
pathsave = QFileDialog.getSaveFileName(self, "Select file name", self.exp_dir + auto_dataset_name, ".npy")[0]
|
|
1870
|
+
if pathsave != '':
|
|
1871
|
+
if not pathsave.endswith(".npy"):
|
|
1872
|
+
pathsave += ".npy"
|
|
1873
|
+
try:
|
|
1874
|
+
np.save(pathsave, dico)
|
|
1875
|
+
print(f'File successfully written in {pathsave}.')
|
|
1876
|
+
except Exception as e:
|
|
1877
|
+
print(f"Error {e}...")
|
|
1878
|
+
|
|
1813
1879
|
def set_next_frame(self):
|
|
1814
1880
|
|
|
1815
1881
|
self.current_frame = self.current_frame + 1
|
|
@@ -1887,13 +1953,17 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
1887
1953
|
|
|
1888
1954
|
def give_cell_information(self):
|
|
1889
1955
|
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1956
|
+
try:
|
|
1957
|
+
cell_selected = f"cell: {self.track_of_interest}\n"
|
|
1958
|
+
if 'TRACK_ID' in self.df_tracks.columns:
|
|
1959
|
+
cell_status = f"phenotype: {self.df_tracks.loc[self.df_tracks['TRACK_ID'] == self.track_of_interest, self.status_name].to_numpy()[0]}\n"
|
|
1960
|
+
else:
|
|
1961
|
+
cell_status = f"phenotype: {self.df_tracks.loc[self.df_tracks['ID'] == self.track_of_interest, self.status_name].to_numpy()[0]}\n"
|
|
1962
|
+
self.cell_info.setText(cell_selected + cell_status)
|
|
1963
|
+
except Exception as e:
|
|
1964
|
+
print('Cell info:',e)
|
|
1965
|
+
print(self.track_of_interest, self.status_name)
|
|
1966
|
+
|
|
1897
1967
|
def create_new_event_class(self):
|
|
1898
1968
|
|
|
1899
1969
|
# display qwidget to name the event
|
|
@@ -1978,8 +2048,14 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
1978
2048
|
self.frame_lbl.setText(f'position: {self.framedata}')
|
|
1979
2049
|
self.im.set_array(self.img)
|
|
1980
2050
|
self.status_scatter.set_offsets(self.positions[self.framedata])
|
|
1981
|
-
|
|
2051
|
+
try:
|
|
2052
|
+
self.status_scatter.set_edgecolors(self.colors[self.framedata][:, 0])
|
|
2053
|
+
except Exception as e:
|
|
2054
|
+
print('L1993: ',e)
|
|
2055
|
+
|
|
1982
2056
|
self.current_label = self.labels[self.current_frame]
|
|
2057
|
+
self.current_label = contour_of_instance_segmentation(self.current_label, 5)
|
|
2058
|
+
|
|
1983
2059
|
self.im_mask.remove()
|
|
1984
2060
|
self.im_mask = self.ax.imshow(np.ma.masked_where(self.current_label == 0, self.current_label),
|
|
1985
2061
|
cmap='viridis', interpolation='none',alpha=self.current_alpha,vmin=0,vmax=np.nanmax(self.labels.flatten()))
|
|
@@ -2108,9 +2184,9 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
2108
2184
|
|
|
2109
2185
|
self.extract_scatter_from_trajectories()
|
|
2110
2186
|
if 'TRACK_ID' in self.df_tracks.columns:
|
|
2111
|
-
self.track_of_interest = self.df_tracks['TRACK_ID'].min()
|
|
2187
|
+
self.track_of_interest = self.df_tracks.dropna(subset='TRACK_ID')['TRACK_ID'].min()
|
|
2112
2188
|
else:
|
|
2113
|
-
self.track_of_interest = self.df_tracks['ID'].min()
|
|
2189
|
+
self.track_of_interest = self.df_tracks.dropna(subset='ID')['ID'].min()
|
|
2114
2190
|
|
|
2115
2191
|
self.loc_t = []
|
|
2116
2192
|
self.loc_idx = []
|
|
@@ -2172,9 +2248,13 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
2172
2248
|
"""
|
|
2173
2249
|
self.current_frame = self.frame_slider.value()
|
|
2174
2250
|
self.reload_frame()
|
|
2175
|
-
if '
|
|
2251
|
+
if 'TRACK_ID' in list(self.df_tracks.columns):
|
|
2252
|
+
pass
|
|
2253
|
+
elif 'ID' in list(self.df_tracks.columns):
|
|
2254
|
+
print('ID in cols... change class of interest... ')
|
|
2176
2255
|
self.track_of_interest = self.df_tracks[self.df_tracks['FRAME'] == self.current_frame]['ID'].min()
|
|
2177
2256
|
self.modify()
|
|
2257
|
+
|
|
2178
2258
|
self.draw_frame(self.current_frame)
|
|
2179
2259
|
self.fcanvas.canvas.draw()
|
|
2180
2260
|
self.plot_signals()
|
|
@@ -2302,7 +2382,7 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
2302
2382
|
try:
|
|
2303
2383
|
self.selection.pop(0)
|
|
2304
2384
|
except Exception as e:
|
|
2305
|
-
print(e)
|
|
2385
|
+
print('Cancel selection: ',e)
|
|
2306
2386
|
|
|
2307
2387
|
try:
|
|
2308
2388
|
for k, (t, idx) in enumerate(zip(self.loc_t, self.loc_idx)):
|