celldetective 1.4.0__py3-none-any.whl → 1.4.1.post1__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 (78) hide show
  1. celldetective/_version.py +1 -1
  2. celldetective/exceptions.py +11 -0
  3. celldetective/filters.py +7 -1
  4. celldetective/gui/InitWindow.py +4 -1
  5. celldetective/gui/__init__.py +2 -9
  6. celldetective/gui/about.py +2 -2
  7. celldetective/gui/base_annotator.py +786 -0
  8. celldetective/gui/classifier_widget.py +18 -13
  9. celldetective/gui/configure_new_exp.py +51 -30
  10. celldetective/gui/control_panel.py +10 -7
  11. celldetective/gui/{signal_annotator.py → event_annotator.py} +473 -1437
  12. celldetective/gui/generic_signal_plot.py +2 -1
  13. celldetective/gui/gui_utils.py +5 -2
  14. celldetective/gui/help/neighborhood.json +2 -2
  15. celldetective/gui/layouts.py +21 -11
  16. celldetective/gui/{signal_annotator2.py → pair_event_annotator.py} +3 -1
  17. celldetective/gui/process_block.py +129 -91
  18. celldetective/gui/processes/downloader.py +37 -34
  19. celldetective/gui/processes/measure_cells.py +14 -8
  20. celldetective/gui/processes/segment_cells.py +438 -314
  21. celldetective/gui/processes/track_cells.py +12 -13
  22. celldetective/gui/settings/__init__.py +7 -0
  23. celldetective/gui/settings/_settings_base.py +70 -0
  24. celldetective/gui/{retrain_signal_model_options.py → settings/_settings_event_model_training.py} +35 -91
  25. celldetective/gui/{measurement_options.py → settings/_settings_measurements.py} +28 -81
  26. celldetective/gui/{neighborhood_options.py → settings/_settings_neighborhood.py} +1 -1
  27. celldetective/gui/settings/_settings_segmentation.py +49 -0
  28. celldetective/gui/{retrain_segmentation_model_options.py → settings/_settings_segmentation_model_training.py} +33 -79
  29. celldetective/gui/{signal_annotator_options.py → settings/_settings_signal_annotator.py} +73 -95
  30. celldetective/gui/{btrack_options.py → settings/_settings_tracking.py} +64 -87
  31. celldetective/gui/styles.py +2 -1
  32. celldetective/gui/survival_ui.py +1 -1
  33. celldetective/gui/tableUI.py +25 -0
  34. celldetective/gui/table_ops/__init__.py +0 -0
  35. celldetective/gui/table_ops/merge_groups.py +118 -0
  36. celldetective/gui/viewers.py +3 -5
  37. celldetective/gui/workers.py +0 -2
  38. celldetective/io.py +98 -55
  39. celldetective/links/zenodo.json +145 -144
  40. celldetective/measure.py +31 -26
  41. celldetective/preprocessing.py +34 -21
  42. celldetective/regionprops/_regionprops.py +16 -5
  43. celldetective/scripts/measure_cells.py +5 -5
  44. celldetective/scripts/measure_relative.py +16 -11
  45. celldetective/scripts/segment_cells.py +4 -4
  46. celldetective/scripts/segment_cells_thresholds.py +3 -3
  47. celldetective/scripts/track_cells.py +7 -7
  48. celldetective/scripts/train_segmentation_model.py +10 -1
  49. celldetective/tracking.py +10 -4
  50. celldetective/utils.py +59 -58
  51. {celldetective-1.4.0.dist-info → celldetective-1.4.1.post1.dist-info}/METADATA +1 -1
  52. celldetective-1.4.1.post1.dist-info/RECORD +123 -0
  53. tests/gui/__init__.py +0 -0
  54. tests/gui/test_new_project.py +228 -0
  55. tests/{test_qt.py → gui/test_project.py} +22 -26
  56. tests/test_preprocessing.py +2 -2
  57. celldetective/models/segmentation_effectors/ricm_bf_all_last/config_input.json +0 -79
  58. celldetective/models/segmentation_effectors/ricm_bf_all_last/ricm_bf_all_last +0 -0
  59. celldetective/models/segmentation_effectors/ricm_bf_all_last/training_instructions.json +0 -37
  60. celldetective/models/segmentation_effectors/test-transfer/config_input.json +0 -39
  61. celldetective/models/segmentation_effectors/test-transfer/test-transfer +0 -0
  62. celldetective/models/signal_detection/NucCond/classification_loss.png +0 -0
  63. celldetective/models/signal_detection/NucCond/classifier.h5 +0 -0
  64. celldetective/models/signal_detection/NucCond/config_input.json +0 -1
  65. celldetective/models/signal_detection/NucCond/log_classifier.csv +0 -126
  66. celldetective/models/signal_detection/NucCond/log_regressor.csv +0 -282
  67. celldetective/models/signal_detection/NucCond/regression_loss.png +0 -0
  68. celldetective/models/signal_detection/NucCond/regressor.h5 +0 -0
  69. celldetective/models/signal_detection/NucCond/scores.npy +0 -0
  70. celldetective/models/signal_detection/NucCond/test_confusion_matrix.png +0 -0
  71. celldetective/models/signal_detection/NucCond/test_regression.png +0 -0
  72. celldetective/models/signal_detection/NucCond/validation_confusion_matrix.png +0 -0
  73. celldetective/models/signal_detection/NucCond/validation_regression.png +0 -0
  74. celldetective-1.4.0.dist-info/RECORD +0 -131
  75. {celldetective-1.4.0.dist-info → celldetective-1.4.1.post1.dist-info}/WHEEL +0 -0
  76. {celldetective-1.4.0.dist-info → celldetective-1.4.1.post1.dist-info}/entry_points.txt +0 -0
  77. {celldetective-1.4.0.dist-info → celldetective-1.4.1.post1.dist-info}/licenses/LICENSE +0 -0
  78. {celldetective-1.4.0.dist-info → celldetective-1.4.1.post1.dist-info}/top_level.txt +0 -0
@@ -10,6 +10,7 @@ import numpy as np
10
10
  import matplotlib.pyplot as plt
11
11
  import json
12
12
 
13
+ from celldetective.exceptions import EmptyQueryError, MissingColumnsError, QueryError
13
14
  from celldetective.gui.gui_utils import FigureCanvas, center_window, color_from_status, help_generic
14
15
  from celldetective.gui.base_components import CelldetectiveWidget
15
16
  from celldetective.utils import get_software_location
@@ -351,26 +352,30 @@ class ClassifierWidget(CelldetectiveWidget):
351
352
  self.propscanvas.canvas.draw_idle()
352
353
 
353
354
  except Exception as e:
354
- pass
355
+ print("Exception L355 ", e)
356
+
357
+ def show_warning(self, message: str):
358
+ msgBox = QMessageBox()
359
+ msgBox.setIcon(QMessageBox.Warning)
360
+ link = "https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.query.html"
361
+ msgBox.setText(f"{message}<br><br>See <a href='{link}'>documentation</a>.")
362
+ msgBox.setWindowTitle("Warning")
363
+ msgBox.setStandardButtons(QMessageBox.Ok)
364
+ msgBox.exec()
355
365
 
356
366
  def apply_property_query(self):
357
367
 
358
368
  query = self.property_query_le.text()
359
-
360
369
  try:
361
370
  self.df = classify_cells_from_query(self.df, self.name_le.text(), query)
371
+ except EmptyQueryError as e:
372
+ self.show_warning(str(e))
373
+ except MissingColumnsError as e:
374
+ self.show_warning(f"{e}. Please check your column names.")
375
+ except QueryError as e:
376
+ self.show_warning(f"{e}. Wrap features in backticks if needed.")
362
377
  except Exception as e:
363
- msgBox = QMessageBox()
364
- msgBox.setIcon(QMessageBox.Warning)
365
- link = "https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.query.html"
366
- msg = "The query could not be understood. Please write a valid query following <a href='%s'>the documentation</a>. Wrap features in backticks (e.g. `feature` > 1) to facilitate query interpretation. " % link
367
- msgBox.setText(msg)
368
- msgBox.setWindowTitle("Warning")
369
- msgBox.setStandardButtons(QMessageBox.Ok)
370
- returnValue = msgBox.exec()
371
- if returnValue == QMessageBox.Ok:
372
- self.auto_close = False
373
- return None
378
+ self.show_warning(f"Unexpected error: {e}")
374
379
 
375
380
  self.class_name = "status_"+self.name_le.text()
376
381
  if self.df is None:
@@ -1,4 +1,4 @@
1
- from PyQt5.QtWidgets import QApplication, QMessageBox, QHBoxLayout, QFileDialog, QVBoxLayout, QScrollArea, QCheckBox, QGridLayout, QLabel, QLineEdit, QPushButton
1
+ from PyQt5.QtWidgets import QApplication,QWidget, QMessageBox, QHBoxLayout, QFileDialog, QVBoxLayout, QScrollArea, QCheckBox, QGridLayout, QLabel, QLineEdit, QPushButton
2
2
  from PyQt5.QtGui import QIntValidator, QDoubleValidator
3
3
  from celldetective.gui.gui_utils import center_window, help_generic
4
4
  from celldetective.utils import get_software_location
@@ -169,7 +169,7 @@ class ConfigNewExperiment(CelldetectiveMainWindow):
169
169
  self.movie_length.setToolTip('Optional: depending on how the movies are encoded, the automatic extraction of the number of frames can be difficult.\nThe software will then rely on this value.')
170
170
  self.ms_grid.addWidget(self.movie_length,9, 0, 1, 3)
171
171
  self.MovieLengthSlider = QLabeledSlider(Qt.Horizontal, self)
172
- self.MovieLengthSlider.setMinimum(2)
172
+ self.MovieLengthSlider.setMinimum(1)
173
173
  #self.MovieLengthSlider.setMaximum(128)
174
174
  self.ms_grid.addWidget(self.MovieLengthSlider, 10, 0, 1, 3)
175
175
 
@@ -566,30 +566,69 @@ class SetupConditionLabels(CelldetectiveWidget):
566
566
  self.parent_window = parent_window
567
567
  self.n_wells = n_wells
568
568
  self.setWindowTitle("Well conditions")
569
- self.layout = QVBoxLayout()
570
- self.layout.setContentsMargins(30,30,30,30)
571
- self.setLayout(self.layout)
569
+
570
+ # --- Outer layout ---
571
+ self.outer_layout = QVBoxLayout(self)
572
+ self.outer_layout.setContentsMargins(10, 10, 10, 10)
573
+
574
+ # --- Scroll area ---
575
+ self.scroll = QScrollArea(self)
576
+ self.scroll.setWidgetResizable(True)
577
+ self.outer_layout.addWidget(self.scroll, stretch=1) # takes most space
578
+
579
+ # Container inside scroll
580
+ self.container = QWidget()
581
+ self.scroll.setWidget(self.container)
582
+
583
+ # Content layout (scrollable part)
584
+ self.layout = QVBoxLayout(self.container)
585
+ self.layout.setContentsMargins(30, 30, 30, 30)
586
+
572
587
  self.onlyFloat = QDoubleValidator()
573
588
  self.populate()
589
+
590
+ self.concentration_units_le = QLineEdit('pM')
591
+ self.concentration_units_le.setPlaceholderText('concentration units')
592
+
593
+ concentration_units_layout = QHBoxLayout()
594
+ concentration_units_layout.addWidget(QLabel('concentration\nunits: '), 5, alignment=Qt.AlignLeft)
595
+ concentration_units_layout.addWidget(self.concentration_units_le, 10)
596
+ concentration_units_layout.addWidget(QLabel(''), 85)
597
+ self.outer_layout.addLayout(concentration_units_layout)
598
+
599
+ # --- Fixed button row (not scrollable) ---
600
+ btn_hbox = QHBoxLayout()
601
+ btn_hbox.setContentsMargins(0, 15, 0, 0)
602
+
603
+ self.skip_btn = QPushButton('Skip')
604
+ self.skip_btn.setStyleSheet(self.button_style_sheet_2)
605
+ self.skip_btn.clicked.connect(self.set_default_values)
606
+ btn_hbox.addWidget(self.skip_btn)
607
+
608
+ self.submit_btn = QPushButton('Submit')
609
+ self.submit_btn.setStyleSheet(self.button_style_sheet)
610
+ self.submit_btn.clicked.connect(self.set_user_values)
611
+ btn_hbox.addWidget(self.submit_btn)
612
+
613
+ self.outer_layout.addLayout(btn_hbox) # outside scroll
614
+ self.setMinimumWidth(int(0.6 * self.parent_window.parent_window.screen_width))
615
+
574
616
  center_window(self)
575
617
 
576
618
  def populate(self):
577
-
578
619
  self.cell_type_cbs = [QLineEdit() for i in range(self.n_wells)]
579
620
  self.antibodies_cbs = [QLineEdit() for i in range(self.n_wells)]
580
621
  self.concentrations_cbs = [QLineEdit() for i in range(self.n_wells)]
581
622
  self.pharmaceutical_agents_cbs = [QLineEdit() for i in range(self.n_wells)]
582
- self.concentration_units_le = QLineEdit('pM')
583
- self.concentration_units_le.setPlaceholderText('concentration units')
584
623
 
585
624
  for i in range(self.n_wells):
586
625
  hbox = QHBoxLayout()
587
- hbox.setContentsMargins(15,5,15,5)
626
+ hbox.setContentsMargins(15, 5, 15, 5)
588
627
  hbox.addWidget(QLabel(f'well {i+1}'), 5, alignment=Qt.AlignLeft)
589
628
  hbox.addWidget(QLabel('cell type: '), 5)
590
629
  hbox.addWidget(self.cell_type_cbs[i], 10)
591
630
  self.cell_type_cbs[i].setPlaceholderText('e.g. T-cell, NK')
592
-
631
+
593
632
  hbox.addWidget(QLabel('antibody: '), 5)
594
633
  hbox.addWidget(self.antibodies_cbs[i], 10)
595
634
  self.antibodies_cbs[i].setPlaceholderText('e.g. anti-CD4')
@@ -602,27 +641,9 @@ class SetupConditionLabels(CelldetectiveWidget):
602
641
  hbox.addWidget(QLabel('pharmaceutical agents: '), 5)
603
642
  hbox.addWidget(self.pharmaceutical_agents_cbs[i], 10)
604
643
  self.pharmaceutical_agents_cbs[i].setPlaceholderText('e.g. dextran')
605
-
644
+
606
645
  self.layout.addLayout(hbox)
607
-
608
- concentration_units_layout = QHBoxLayout()
609
- concentration_units_layout.addWidget(QLabel('concentration\nunits: '),5,alignment=Qt.AlignLeft)
610
- concentration_units_layout.addWidget(self.concentration_units_le,10)
611
- concentration_units_layout.addWidget(QLabel(''), 85)
612
- self.layout.addLayout(concentration_units_layout)
613
-
614
- btn_hbox = QHBoxLayout()
615
- btn_hbox.setContentsMargins(0,20,0,0)
616
- self.skip_btn = QPushButton('Skip')
617
- self.skip_btn.setStyleSheet(self.button_style_sheet_2)
618
- self.skip_btn.clicked.connect(self.set_default_values)
619
- btn_hbox.addWidget(self.skip_btn)
620
-
621
- self.submit_btn = QPushButton('Submit')
622
- self.submit_btn.setStyleSheet(self.button_style_sheet)
623
- self.submit_btn.clicked.connect(self.set_user_values)
624
- btn_hbox.addWidget(self.submit_btn)
625
- self.layout.addLayout(btn_hbox)
646
+
626
647
 
627
648
  def set_default_values(self):
628
649
 
@@ -4,7 +4,7 @@ from celldetective.gui import CelldetectiveMainWindow, CelldetectiveWidget
4
4
 
5
5
  from PyQt5.QtCore import Qt, QSize
6
6
  from celldetective.gui.gui_utils import center_window, QHSeperationLine, QCheckableComboBox, generic_message
7
- from celldetective.utils import _extract_labels_from_config, ConfigSectionMap, extract_identity_col
7
+ from celldetective.utils import _extract_labels_from_config, config_section_to_dict, extract_identity_col
8
8
  from celldetective.gui import ConfigEditor, ProcessPanel, PreprocessingPanel, AnalysisPanel, NeighPanel
9
9
  from celldetective.io import extract_position_name, get_experiment_wells, get_config, get_spatial_calibration, get_temporal_calibration, get_experiment_concentrations, get_experiment_cell_types, get_experiment_antibodies, get_experiment_pharmaceutical_agents, get_experiment_populations, extract_well_name_and_number
10
10
  from natsort import natsorted
@@ -318,10 +318,10 @@ class ControlPanel(CelldetectiveMainWindow):
318
318
  self.FrameToMin = get_temporal_calibration(self.exp_dir)
319
319
 
320
320
 
321
- self.len_movie = int(ConfigSectionMap(self.exp_config,"MovieSettings")["len_movie"])
322
- self.shape_x = int(ConfigSectionMap(self.exp_config,"MovieSettings")["shape_x"])
323
- self.shape_y = int(ConfigSectionMap(self.exp_config,"MovieSettings")["shape_y"])
324
- self.movie_prefix = ConfigSectionMap(self.exp_config,"MovieSettings")["movie_prefix"]
321
+ self.len_movie = int(config_section_to_dict(self.exp_config, "MovieSettings")["len_movie"])
322
+ self.shape_x = int(config_section_to_dict(self.exp_config, "MovieSettings")["shape_x"])
323
+ self.shape_y = int(config_section_to_dict(self.exp_config, "MovieSettings")["shape_y"])
324
+ self.movie_prefix = config_section_to_dict(self.exp_config, "MovieSettings")["movie_prefix"]
325
325
 
326
326
  # Read channels
327
327
  self.exp_channels, channel_indices = extract_experiment_channels(self.exp_dir)
@@ -335,7 +335,7 @@ class ControlPanel(CelldetectiveMainWindow):
335
335
  self.antibodies = get_experiment_antibodies(self.exp_dir)
336
336
  self.pharmaceutical_agents = get_experiment_pharmaceutical_agents(self.exp_dir)
337
337
 
338
- self.metadata = ConfigSectionMap(self.exp_config,"Metadata")
338
+ self.metadata = config_section_to_dict(self.exp_config, "Metadata")
339
339
  print('Experiment configuration successfully read...')
340
340
 
341
341
  def closeEvent(self, event):
@@ -505,7 +505,10 @@ class ControlPanel(CelldetectiveMainWindow):
505
505
  for i,p in enumerate(self.ProcessPopulations):
506
506
  p.check_seg_btn.setEnabled(True)
507
507
  if os.path.exists(os.sep.join([self.pos,'output','tables',f'trajectories_{self.populations[i]}.csv'])):
508
- df = pd.read_csv(os.sep.join([self.pos,'output','tables',f'trajectories_{self.populations[i]}.csv']), nrows=1)
508
+ try:
509
+ df = pd.read_csv(os.sep.join([self.pos,'output','tables',f'trajectories_{self.populations[i]}.csv']), nrows=1)
510
+ except Exception as e:
511
+ continue
509
512
  id_col = extract_identity_col(df)
510
513
  p.check_measurements_btn.setEnabled(True)
511
514