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.
Files changed (42) hide show
  1. celldetective/__init__.py +2 -1
  2. celldetective/__main__.py +17 -0
  3. celldetective/extra_properties.py +62 -34
  4. celldetective/gui/__init__.py +1 -0
  5. celldetective/gui/analyze_block.py +2 -1
  6. celldetective/gui/classifier_widget.py +18 -10
  7. celldetective/gui/control_panel.py +57 -6
  8. celldetective/gui/layouts.py +14 -11
  9. celldetective/gui/neighborhood_options.py +21 -13
  10. celldetective/gui/plot_signals_ui.py +39 -11
  11. celldetective/gui/process_block.py +413 -95
  12. celldetective/gui/retrain_segmentation_model_options.py +17 -4
  13. celldetective/gui/retrain_signal_model_options.py +106 -6
  14. celldetective/gui/signal_annotator.py +110 -30
  15. celldetective/gui/signal_annotator2.py +2708 -0
  16. celldetective/gui/signal_annotator_options.py +3 -1
  17. celldetective/gui/survival_ui.py +15 -6
  18. celldetective/gui/tableUI.py +248 -43
  19. celldetective/io.py +598 -416
  20. celldetective/measure.py +919 -969
  21. celldetective/models/pair_signal_detection/blank +0 -0
  22. celldetective/neighborhood.py +482 -340
  23. celldetective/preprocessing.py +81 -61
  24. celldetective/relative_measurements.py +648 -0
  25. celldetective/scripts/analyze_signals.py +1 -1
  26. celldetective/scripts/measure_cells.py +28 -8
  27. celldetective/scripts/measure_relative.py +103 -0
  28. celldetective/scripts/segment_cells.py +5 -5
  29. celldetective/scripts/track_cells.py +4 -1
  30. celldetective/scripts/train_segmentation_model.py +23 -18
  31. celldetective/scripts/train_signal_model.py +33 -0
  32. celldetective/segmentation.py +67 -29
  33. celldetective/signals.py +402 -8
  34. celldetective/tracking.py +8 -2
  35. celldetective/utils.py +144 -12
  36. {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/METADATA +8 -8
  37. {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/RECORD +42 -38
  38. {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/WHEEL +1 -1
  39. tests/test_segmentation.py +1 -1
  40. {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/LICENSE +0 -0
  41. {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/entry_points.txt +0 -0
  42. {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+"/*.tif")
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('/')[-2]
469
- self.model_name = self.pretrained_model.split('/')[-1]
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 = '/'.join([self.software_models_dir,model_name, ''])
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.signal_models_dir = self.soft_path+os.sep+os.sep.join(['celldetective','models','signal_detection'])
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 self.df_tracks.columns and self.class_name in self.df_tracks.columns and not self.status_name in self.df_tracks.columns:
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 self.df_tracks.columns and self.class_name in self.df_tracks.columns:
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() == 'linear':
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, color="#1565c0"))
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, color="black"))
1323
+ self.log_btn.setIcon(icon(MDI6.math_log,color="black"))
1296
1324
  except Exception as e:
1297
1325
  print(e)
1298
1326
 
1299
- # self.cell_ax.autoscale()
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 + "configs/signal_annotator_config_targets.json"
1319
- self.trajectories_path = self.pos + 'output/tables/trajectories_targets.csv'
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 + "configs/signal_annotator_config_effectors.json"
1322
- self.trajectories_path = self.pos + 'output/tables/trajectories_effectors.csv'
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
- self.labels = relabel_segmentation(labels,data,properties)
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
- cell_selected = f"cell: {self.track_of_interest}\n"
1891
- if 'TRACK_ID' in self.df_tracks.columns:
1892
- cell_status = f"phenotype: {self.df_tracks.loc[self.df_tracks['TRACK_ID'] == self.track_of_interest, self.status_name].to_numpy()[0]}\n"
1893
- else:
1894
- cell_status = f"phenotype: {self.df_tracks.loc[self.df_tracks['ID'] == self.track_of_interest, self.status_name].to_numpy()[0]}\n"
1895
- self.cell_info.setText(cell_selected + cell_status)
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
- self.status_scatter.set_edgecolors(self.colors[self.framedata][:, 0])
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 'ID' in self.df_tracks.columns:
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)):