celldetective 1.2.0__py3-none-any.whl → 1.2.2__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 (54) hide show
  1. celldetective/__main__.py +12 -5
  2. celldetective/events.py +28 -2
  3. celldetective/gui/about.py +0 -1
  4. celldetective/gui/analyze_block.py +3 -18
  5. celldetective/gui/btrack_options.py +126 -21
  6. celldetective/gui/classifier_widget.py +68 -107
  7. celldetective/gui/configure_new_exp.py +37 -4
  8. celldetective/gui/control_panel.py +14 -30
  9. celldetective/gui/generic_signal_plot.py +793 -0
  10. celldetective/gui/gui_utils.py +401 -226
  11. celldetective/gui/json_readers.py +0 -2
  12. celldetective/gui/layouts.py +269 -25
  13. celldetective/gui/measurement_options.py +14 -23
  14. celldetective/gui/neighborhood_options.py +6 -16
  15. celldetective/gui/plot_measurements.py +10 -23
  16. celldetective/gui/plot_signals_ui.py +53 -687
  17. celldetective/gui/process_block.py +320 -186
  18. celldetective/gui/retrain_segmentation_model_options.py +30 -47
  19. celldetective/gui/retrain_signal_model_options.py +5 -14
  20. celldetective/gui/seg_model_loader.py +129 -113
  21. celldetective/gui/signal_annotator.py +93 -103
  22. celldetective/gui/signal_annotator2.py +9 -13
  23. celldetective/gui/styles.py +32 -0
  24. celldetective/gui/survival_ui.py +49 -712
  25. celldetective/gui/tableUI.py +4 -39
  26. celldetective/gui/thresholds_gui.py +38 -11
  27. celldetective/gui/viewers.py +6 -7
  28. celldetective/io.py +62 -84
  29. celldetective/measure.py +374 -15
  30. celldetective/models/segmentation_effectors/ricm-bimodal/config_input.json +130 -0
  31. celldetective/models/segmentation_effectors/ricm-bimodal/ricm-bimodal +0 -0
  32. celldetective/models/segmentation_effectors/ricm-bimodal/training_instructions.json +37 -0
  33. celldetective/neighborhood.py +3 -7
  34. celldetective/preprocessing.py +2 -4
  35. celldetective/relative_measurements.py +0 -3
  36. celldetective/scripts/analyze_signals.py +0 -1
  37. celldetective/scripts/measure_cells.py +1 -3
  38. celldetective/scripts/measure_relative.py +1 -2
  39. celldetective/scripts/segment_cells.py +16 -12
  40. celldetective/scripts/segment_cells_thresholds.py +17 -10
  41. celldetective/scripts/track_cells.py +18 -18
  42. celldetective/scripts/train_segmentation_model.py +1 -2
  43. celldetective/scripts/train_signal_model.py +0 -3
  44. celldetective/segmentation.py +1 -1
  45. celldetective/signals.py +20 -8
  46. celldetective/tracking.py +2 -1
  47. celldetective/utils.py +126 -18
  48. {celldetective-1.2.0.dist-info → celldetective-1.2.2.dist-info}/METADATA +19 -12
  49. celldetective-1.2.2.dist-info/RECORD +92 -0
  50. {celldetective-1.2.0.dist-info → celldetective-1.2.2.dist-info}/WHEEL +1 -1
  51. celldetective-1.2.0.dist-info/RECORD +0 -88
  52. {celldetective-1.2.0.dist-info → celldetective-1.2.2.dist-info}/LICENSE +0 -0
  53. {celldetective-1.2.0.dist-info → celldetective-1.2.2.dist-info}/entry_points.txt +0 -0
  54. {celldetective-1.2.0.dist-info → celldetective-1.2.2.dist-info}/top_level.txt +0 -0
@@ -1,27 +1,98 @@
1
1
  import numpy as np
2
2
  from PyQt5.QtWidgets import QApplication, QMessageBox, QFrame, QSizePolicy, QWidget, QLineEdit, QListWidget, QVBoxLayout, QComboBox, \
3
- QPushButton, QLabel, QHBoxLayout, QCheckBox, QButtonGroup, QRadioButton, QGridLayout, QSpacerItem
4
- from PyQt5.QtCore import QEvent, Qt, QSize
3
+ QPushButton, QLabel, QHBoxLayout, QCheckBox, QFileDialog
4
+ from PyQt5.QtCore import Qt, QSize
5
5
  from PyQt5.QtGui import QDoubleValidator, QIntValidator
6
6
 
7
+ from celldetective.gui import Styles
8
+ from superqt.fonticon import icon
9
+ from fonticon_mdi6 import MDI6
10
+
7
11
  from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
8
12
  from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT
9
13
  import matplotlib.pyplot as plt
10
14
  import celldetective.extra_properties as extra_properties
11
15
  from inspect import getmembers, isfunction
12
- from superqt import QLabeledDoubleRangeSlider, QLabeledSlider, QLabeledDoubleSlider
13
- from superqt.fonticon import icon
14
- from fonticon_mdi6 import MDI6
15
-
16
- from celldetective.io import auto_load_number_of_frames, load_frames
17
- from celldetective.utils import _extract_channel_indices_from_config
18
16
  from celldetective.filters import *
19
- from celldetective.segmentation import filter_image
20
- from stardist import fill_label_holes
17
+ from os import sep
18
+
19
+
20
+ class QuickSliderLayout(QHBoxLayout):
21
+
22
+ """
23
+ A layout class that combines a QLabel and a QSlider in a horizontal box layout.
24
+
25
+ This layout provides a convenient way to include a slider with an optional label and configurable
26
+ parameters such as range, precision, and step size. It allows for both integer and decimal step values,
27
+ making it versatile for different types of input adjustments.
28
+
29
+ Parameters
30
+ ----------
31
+ label : str, optional
32
+ The label to be displayed next to the slider (default is None).
33
+ slider : QSlider
34
+ The slider widget to be added to the layout.
35
+ layout_ratio : tuple of float, optional
36
+ Defines the width ratio between the label and the slider in the layout. The first element is the
37
+ ratio for the label, and the second is for the slider (default is (0.25, 0.75)).
38
+ slider_initial_value : int or float, optional
39
+ The initial value to set for the slider (default is 1).
40
+ slider_range : tuple of int or float, optional
41
+ A tuple specifying the minimum and maximum values for the slider (default is (0, 1)).
42
+ slider_tooltip : str, optional
43
+ Tooltip text to display when hovering over the slider (default is None).
44
+ decimal_option : bool, optional
45
+ If True, the slider allows decimal values with a specified precision (default is True).
46
+ precision : float, optional
47
+ The step size for the slider when `decimal_option` is enabled (default is 1.0E-03).
48
+
49
+ Attributes
50
+ ----------
51
+ qlabel : QLabel
52
+ The label widget that displays the provided label text (only if `label` is provided).
53
+ slider : QSlider
54
+ The slider widget that allows the user to select a value.
55
+ """
56
+
57
+ def __init__(self, label=None, slider=None, layout_ratio=(0.25,0.75), slider_initial_value=1, slider_range=(0,1), slider_tooltip=None, decimal_option=True, precision=1.0E-03, *args):
58
+ super().__init__(*args)
59
+
60
+ if label is not None and isinstance(label,str):
61
+ self.qlabel = QLabel(label)
62
+ self.addWidget(self.qlabel, int(100*layout_ratio[0]))
63
+
64
+ self.slider = slider
65
+ self.slider.setOrientation(1)
66
+ if decimal_option:
67
+ self.slider.setSingleStep(precision)
68
+ self.slider.setTickInterval(precision)
69
+ else:
70
+ self.slider.setSingleStep(1)
71
+ self.slider.setTickInterval(1)
72
+
73
+ self.slider.setRange(*slider_range)
74
+ self.slider.setValue(slider_initial_value)
75
+ if isinstance(slider_tooltip,str):
76
+ self.slider.setToolTip(slider_tooltip)
77
+
78
+ self.addWidget(self.slider, int(100*layout_ratio[1]))
79
+
21
80
 
22
81
  def center_window(window):
82
+
23
83
  """
24
- Center window in the middle of the screen.
84
+ Centers the given window in the middle of the screen.
85
+
86
+ This function calculates the current screen's geometry and moves the
87
+ specified window to the center of the screen. It works by retrieving the
88
+ frame geometry of the window, identifying the screen where the cursor is
89
+ currently located, and adjusting the window's position to be centrally
90
+ aligned on that screen.
91
+
92
+ Parameters
93
+ ----------
94
+ window : QMainWindow or QWidget
95
+ The window or widget to be centered on the screen.
25
96
  """
26
97
 
27
98
  frameGm = window.frameGeometry()
@@ -30,6 +101,75 @@ def center_window(window):
30
101
  frameGm.moveCenter(centerPoint)
31
102
  window.move(frameGm.topLeft())
32
103
 
104
+ class ExportPlotBtn(QPushButton, Styles):
105
+
106
+ """
107
+ A custom QPushButton widget for exporting a matplotlib figure.
108
+
109
+ This class combines a QPushButton with functionality to export a given matplotlib
110
+ figure (`fig`) to an image file. The button includes an icon and a tooltip for easy
111
+ user interaction. When clicked, a file dialog is opened allowing the user to specify
112
+ the location and file format to save the plot.
113
+
114
+ Parameters
115
+ ----------
116
+ fig : matplotlib.figure.Figure
117
+ The matplotlib figure object to be exported.
118
+ export_dir : str, optional
119
+ The default directory where the file will be saved. If not provided, the current
120
+ working directory will be used.
121
+ *args : tuple
122
+ Additional positional arguments passed to the parent `QPushButton` constructor.
123
+ **kwargs : dict
124
+ Additional keyword arguments passed to the parent `QPushButton` constructor.
125
+
126
+ Attributes
127
+ ----------
128
+ fig : matplotlib.figure.Figure
129
+ The figure that will be saved when the button is clicked.
130
+ export_dir : str or None
131
+ The default directory where the file dialog will initially point when saving the image.
132
+
133
+ Methods
134
+ -------
135
+ save_plot():
136
+ Opens a file dialog to choose the file name and location for saving the figure.
137
+ The figure is then saved in the specified format and location.
138
+ """
139
+
140
+ def __init__(self, fig, export_dir=None, *args, **kwargs):
141
+
142
+ super().__init__()
143
+
144
+ self.export_dir = export_dir
145
+ self.fig = fig
146
+
147
+ self.setText('')
148
+ self.setIcon(icon(MDI6.content_save,color="black"))
149
+ self.setStyleSheet(self.button_select_all)
150
+ self.setToolTip('Export figure.')
151
+ self.setIconSize(QSize(20, 20))
152
+ self.clicked.connect(self.save_plot)
153
+
154
+ def save_plot(self):
155
+
156
+ """
157
+ Opens a file dialog for the user to specify the location and name to save the plot.
158
+
159
+ If the user selects a file, the figure is saved with tight layout and 300 DPI resolution.
160
+ Supported formats include PNG, JPG, SVG, and XPM.
161
+ """
162
+
163
+ if self.export_dir is not None:
164
+ guess_dir = self.export_dir+sep+'plot.png'
165
+ else:
166
+ guess_dir = 'plot.png'
167
+ fileName, _ = QFileDialog.getSaveFileName(self,
168
+ "Save Image", guess_dir, "Images (*.png *.xpm *.jpg *.svg)") #, options=options
169
+ if fileName:
170
+ self.fig.tight_layout()
171
+ self.fig.savefig(fileName, bbox_inches='tight', dpi=300)
172
+
33
173
 
34
174
  class QHSeperationLine(QFrame):
35
175
  '''
@@ -323,19 +463,58 @@ class DistanceChoice(QWidget):
323
463
 
324
464
 
325
465
  class ListWidget(QWidget):
466
+
326
467
  """
327
- Generic list widget.
468
+ A customizable widget for displaying and managing a list of items, with the
469
+ ability to add and remove items interactively.
470
+
471
+ This widget is built around a `QListWidget` and allows for initialization with
472
+ a set of features. It also provides options to retrieve the items, add new items
473
+ using a custom widget, and remove selected items. The items can be parsed and
474
+ returned as a list, with support for various data types and formatted input (e.g.,
475
+ ranges specified with a dash).
476
+
477
+ Parameters
478
+ ----------
479
+ choiceWidget : QWidget
480
+ A custom widget that is used to add new items to the list.
481
+ initial_features : list
482
+ A list of initial items to populate the list widget.
483
+ dtype : type, optional
484
+ The data type to cast the list items to. Default is `str`.
485
+
486
+ Attributes
487
+ ----------
488
+ initial_features : list
489
+ The initial set of features or items displayed in the list.
490
+ choiceWidget : QWidget
491
+ The widget used to prompt the user to add new items.
492
+ dtype : type
493
+ The data type to convert items into when retrieved from the list.
494
+ items : list
495
+ A list to store the current items in the list widget.
496
+ list_widget : QListWidget
497
+ The core Qt widget that displays the list of items.
498
+
499
+ Methods
500
+ -------
501
+ addItem()
502
+ Opens a new window to add an item to the list using the custom `choiceWidget`.
503
+ getItems()
504
+ Retrieves the items from the list widget, parsing ranges (e.g., 'min-max')
505
+ into two values, and converts them to the specified `dtype`.
506
+ removeSel()
507
+ Removes the currently selected item(s) from the list widget and updates the
508
+ internal `items` list accordingly.
328
509
  """
329
510
 
330
- def __init__(self, parent_window, choiceWidget, initial_features, dtype=str, channel_names=None):
511
+ def __init__(self, choiceWidget, initial_features, dtype=str, *args, **kwargs):
331
512
 
332
513
  super().__init__()
333
- self.parent_window = parent_window
334
514
  self.initial_features = initial_features
335
515
  self.choiceWidget = choiceWidget
336
516
  self.dtype = dtype
337
517
  self.items = []
338
- self.channel_names=channel_names
339
518
 
340
519
  self.setFixedHeight(80)
341
520
 
@@ -347,11 +526,12 @@ class ListWidget(QWidget):
347
526
  main_layout = QVBoxLayout()
348
527
  main_layout.addWidget(self.list_widget)
349
528
  self.setLayout(main_layout)
529
+ center_window(self)
350
530
 
351
531
  def addItem(self):
352
532
 
353
533
  """
354
- Add a new item.
534
+ Opens the custom choiceWidget to add a new item to the list.
355
535
  """
356
536
 
357
537
  self.addItemWindow = self.choiceWidget(self)
@@ -360,7 +540,15 @@ class ListWidget(QWidget):
360
540
  def getItems(self):
361
541
 
362
542
  """
363
- Get all the items as a list.
543
+ Retrieves and returns the items from the list widget.
544
+
545
+ This method parses any items that contain a range (formatted as 'min-max')
546
+ into a list of two values, and casts all items to the specified `dtype`.
547
+
548
+ Returns
549
+ -------
550
+ list
551
+ A list of the items in the list widget, with ranges split into two values.
364
552
  """
365
553
 
366
554
  items = []
@@ -377,11 +565,13 @@ class ListWidget(QWidget):
377
565
  return items
378
566
 
379
567
 
380
-
381
568
  def removeSel(self):
382
-
569
+
383
570
  """
384
- Remove selected items.
571
+ Removes the selected item(s) from the list widget.
572
+
573
+ If there are any selected items, they are removed both from the visual list
574
+ and the internal `items` list that tracks the current state of the widget.
385
575
  """
386
576
 
387
577
  listItems = self.list_widget.selectedItems()
@@ -424,38 +614,46 @@ class FigureCanvas(QWidget):
424
614
  plt.close(self.fig)
425
615
  super(FigureCanvas, self).closeEvent(event)
426
616
 
427
-
428
- class QuickSliderLayout(QHBoxLayout):
429
-
430
- """docstring for ClassName"""
431
-
432
- def __init__(self, label=None, slider=None, layout_ratio=(0.25,0.75), slider_initial_value=1, slider_range=(0,1), slider_tooltip=None, decimal_option=True, precision=1.0E-03, *args):
433
- super().__init__(*args)
434
-
435
- if label is not None and isinstance(label,str):
436
- self.qlabel = QLabel(label)
437
- self.addWidget(self.qlabel, int(100*layout_ratio[0]))
438
-
439
- self.slider = slider
440
- self.slider.setOrientation(1)
441
- if decimal_option:
442
- self.slider.setSingleStep(precision)
443
- self.slider.setTickInterval(precision)
444
- else:
445
- self.slider.setSingleStep(1)
446
- self.slider.setTickInterval(1)
447
-
448
- self.slider.setRange(*slider_range)
449
- self.slider.setValue(slider_initial_value)
450
- if isinstance(slider_tooltip,str):
451
- self.slider.setToolTip(slider_tooltip)
452
-
453
- self.addWidget(self.slider, int(100*layout_ratio[1]))
454
-
455
617
  class ThresholdLineEdit(QLineEdit):
456
618
 
457
- """docstring for ClassName"""
619
+ """
620
+ A custom QLineEdit widget to manage and validate threshold values.
621
+
622
+ This class extends QLineEdit to input and manage threshold values (either float or int),
623
+ with optional validation and interaction with connected QPushButtons. The widget can
624
+ validate the input and enable/disable buttons based on whether a valid threshold is set.
625
+
626
+ Parameters
627
+ ----------
628
+ init_value : float or int, optional
629
+ The initial threshold value to display in the input field (default is 2.0).
630
+ connected_buttons : QPushButton or list of QPushButton, optional
631
+ QPushButton(s) that should be enabled/disabled based on the validity of the threshold
632
+ value (default is None).
633
+ placeholder : str, optional
634
+ Placeholder text to show when no value is entered in the input field
635
+ (default is 'px > thresh are masked').
636
+ value_type : str, optional
637
+ Specifies the type of threshold value, either 'float' or 'int' (default is 'float').
638
+
639
+ Methods
640
+ -------
641
+ enable_btn():
642
+ Enables or disables connected QPushButtons based on the validity of the threshold value.
643
+ set_threshold(value):
644
+ Sets the input field to the given threshold value.
645
+ get_threshold(show_warning=True):
646
+ Retrieves the current threshold value from the input field, returning it as a float or int.
647
+ If invalid, optionally displays a warning dialog.
648
+
649
+ Example
650
+ -------
651
+ >>> threshold_input = ThresholdLineEdit(init_value=5, value_type='int')
652
+ >>> print(threshold_input.get_threshold())
653
+ 5
654
+ """
458
655
 
656
+
459
657
  def __init__(self, init_value=2.0, connected_buttons=None, placeholder='px > thresh are masked',value_type='float',*args):
460
658
  super().__init__(*args)
461
659
 
@@ -472,11 +670,17 @@ class ThresholdLineEdit(QLineEdit):
472
670
 
473
671
  if self.connected_buttons is not None:
474
672
  self.textChanged.connect(self.enable_btn)
475
- print("init value of ",self.init_value," for threshold LE")
476
673
  self.set_threshold(self.init_value)
477
674
 
478
675
  def enable_btn(self):
479
676
 
677
+ """
678
+ Enable or disable connected QPushButtons based on the threshold value.
679
+
680
+ If the current threshold value is valid, the connected buttons will be enabled.
681
+ If the value is invalid or empty, the buttons will be disabled.
682
+ """
683
+
480
684
  thresh = self.get_threshold(show_warning=False)
481
685
  if isinstance(self.connected_buttons, QPushButton):
482
686
  cbs = [self.connected_buttons]
@@ -492,6 +696,15 @@ class ThresholdLineEdit(QLineEdit):
492
696
 
493
697
  def set_threshold(self, value):
494
698
 
699
+ """
700
+ Set the input field to the specified threshold value.
701
+
702
+ Parameters
703
+ ----------
704
+ value : float or int
705
+ The value to set in the input field.
706
+ """
707
+
495
708
  try:
496
709
  self.setText(str(value).replace('.',','))
497
710
  except:
@@ -499,6 +712,23 @@ class ThresholdLineEdit(QLineEdit):
499
712
 
500
713
  def get_threshold(self, show_warning=True):
501
714
 
715
+ """
716
+ Retrieve the current threshold value from the input field.
717
+
718
+ Converts the value to a float or int based on the `value_type` attribute. If the value
719
+ is invalid and `show_warning` is True, a warning dialog is shown.
720
+
721
+ Parameters
722
+ ----------
723
+ show_warning : bool, optional
724
+ If True, show a warning dialog if the value is invalid (default is True).
725
+
726
+ Returns
727
+ -------
728
+ float or int or None
729
+ The threshold value as a float or int, or None if the value is invalid.
730
+ """
731
+
502
732
  try:
503
733
  if self.value_type=='float':
504
734
  thresh = float(self.text().replace(',','.'))
@@ -517,176 +747,6 @@ class ThresholdLineEdit(QLineEdit):
517
747
 
518
748
  return thresh
519
749
 
520
- # class BackgroundFitCorrectionLayout(QGridLayout):
521
-
522
- # """docstring for ClassName"""
523
-
524
- # def __init__(self, parent=None, *args):
525
- # super().__init__(*args)
526
-
527
- # self.parent = parent
528
- # self.channel_names = self.parent.channel_names # check this
529
-
530
- # self.setContentsMargins(15,15,15,15)
531
- # self.generate_widgets()
532
- # self.add_to_layout()
533
-
534
- # def generate_widgets(self):
535
-
536
- # self.channel_lbl = QLabel('Channel: ')
537
- # self.channels_cb = QComboBox()
538
- # self.channels_cb.addItems(self.channel_names)
539
-
540
- # self.thresh_lbl = QLabel('Threshold: ')
541
- # self.thresh_lbl.setToolTip('Threshold on the STD-filtered image.\nPixel values above the threshold are\nconsidered as non-background and are\nmasked prior to background estimation.')
542
- # self.threshold_viewer_btn = QPushButton()
543
- # self.threshold_viewer_btn.setIcon(icon(MDI6.image_check, color="k"))
544
- # self.threshold_viewer_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
545
- # self.threshold_viewer_btn.clicked.connect(self.set_threshold_graphically)
546
-
547
- # self.model_lbl = QLabel('Model: ')
548
- # self.models_cb = QComboBox()
549
- # self.models_cb.addItems(['Paraboloid', 'Place'])
550
-
551
- # self.operation_lbl = QLabel('Operation: ')
552
- # self.operation_group = QButtonGroup()
553
- # self.subtract_btn = QRadioButton('Subtract')
554
- # self.divide_btn = QRadioButton('Divide')
555
- # self.subtract_btn.toggled.connect(self.activate_clipping_options)
556
- # self.divide_btn.toggled.connect(self.activate_clipping_options)
557
-
558
- # self.operation_group.addButton(self.subtract_btn)
559
- # self.operation_group.addButton(self.divide_btn)
560
-
561
- # self.clip_group = QButtonGroup()
562
- # self.clip_btn = QRadioButton('Clip')
563
- # self.clip_not_btn = QRadioButton('Do not clip')
564
-
565
- # self.clip_group.addButton(self.clip_btn)
566
- # self.clip_group.addButton(self.clip_not_btn)
567
-
568
- # self.corrected_stack_viewer = QPushButton("")
569
- # self.corrected_stack_viewer.setStyleSheet(self.parent.parent.parent.button_select_all)
570
- # self.corrected_stack_viewer.setIcon(icon(MDI6.eye_outline, color="black"))
571
- # self.corrected_stack_viewer.setToolTip("View corrected image")
572
- # self.corrected_stack_viewer.setIconSize(QSize(20, 20))
573
-
574
- # self.add_correction_btn = QPushButton('Add correction')
575
- # self.add_correction_btn.setStyleSheet(self.parent.parent.parent.button_style_sheet_2)
576
- # self.add_correction_btn.setIcon(icon(MDI6.plus, color="#1565c0"))
577
- # self.add_correction_btn.setToolTip('Add correction.')
578
- # self.add_correction_btn.setIconSize(QSize(25, 25))
579
- # self.add_correction_btn.clicked.connect(self.add_instructions_to_parent_list)
580
-
581
- # self.threshold_le = ThresholdLineEdit(init_value=2, connected_buttons=[self.threshold_viewer_btn,
582
- # self.corrected_stack_viewer,
583
- # self.add_correction_btn
584
- # ])
585
-
586
- # def add_to_layout(self):
587
-
588
- # channel_layout = QHBoxLayout()
589
- # channel_layout.addWidget(self.channel_lbl, 25)
590
- # channel_layout.addWidget(self.channels_cb, 75)
591
- # self.addLayout(channel_layout, 0, 0, 1, 3)
592
-
593
- # threshold_layout = QHBoxLayout()
594
- # threshold_layout.addWidget(self.thresh_lbl, 25)
595
- # threshold_layout.addWidget(self.threshold_le, 70)
596
- # threshold_layout.addWidget(self.threshold_viewer_btn, 5)
597
- # self.addLayout(threshold_layout, 1, 0, 1, 3)
598
-
599
- # model_layout = QHBoxLayout()
600
- # model_layout.addWidget(self.model_lbl, 25)
601
- # model_layout.addWidget(self.models_cb, 75)
602
- # self.addLayout(model_layout, 2, 0, 1, 3)
603
-
604
- # operation_layout = QHBoxLayout()
605
- # operation_layout.addWidget(self.operation_lbl, 25)
606
- # operation_layout.addWidget(self.subtract_btn, 75//2, alignment=Qt.AlignCenter)
607
- # operation_layout.addWidget(self.divide_btn, 75//2, alignment=Qt.AlignCenter)
608
- # self.addLayout(operation_layout, 3, 0, 1, 3)
609
-
610
- # clip_layout = QHBoxLayout()
611
- # clip_layout.addWidget(QLabel(''), 25)
612
- # clip_layout.addWidget(self.clip_btn, 75//4, alignment=Qt.AlignCenter)
613
- # clip_layout.addWidget(self.clip_not_btn, 75//4, alignment=Qt.AlignCenter)
614
- # clip_layout.addWidget(QLabel(''), 75//2)
615
- # self.addLayout(clip_layout, 4, 0, 1, 3)
616
-
617
- # self.addWidget(self.corrected_stack_viewer, 4, 2, 1, 1)
618
- # self.addWidget(self.add_correction_btn, 5, 0, 1, 3)
619
-
620
- # self.subtract_btn.click()
621
- # self.clip_not_btn.click()
622
-
623
- # verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
624
- # self.addItem(verticalSpacer, 5, 0, 1, 3)
625
-
626
- # def add_instructions_to_parent_list(self):
627
-
628
- # self.generate_instructions()
629
- # self.parent.background_correction.append(self.instructions)
630
- # correction_description = ""
631
- # for index, (key, value) in enumerate(self.instructions.items()):
632
- # if index > 0:
633
- # correction_description += ", "
634
- # correction_description += str(key) + " : " + str(value)
635
- # self.parent.normalisation_list.addItem(correction_description)
636
-
637
- # def generate_instructions(self):
638
-
639
- # if self.subtract_btn.isChecked():
640
- # operation = "subtract"
641
- # else:
642
- # operation = "divide"
643
- # clip = None
644
-
645
- # if self.clip_btn.isChecked() and self.subtract_btn.isChecked():
646
- # clip = True
647
- # else:
648
- # clip = False
649
-
650
- # self.instructions = {
651
- # "target_channel": self.channels_cb.currentText(),
652
- # "correction_type": "fit",
653
- # "threshold_on_std": self.threshold_le.get_threshold(),
654
- # "operation": operation,
655
- # "clip": clip
656
- # }
657
-
658
- # def activate_clipping_options(self):
659
-
660
- # if self.subtract_btn.isChecked():
661
- # self.clip_btn.setEnabled(True)
662
- # self.clip_not_btn.setEnabled(True)
663
-
664
- # else:
665
- # self.clip_btn.setEnabled(False)
666
- # self.clip_not_btn.setEnabled(False)
667
-
668
- # def set_target_channel(self):
669
-
670
- # channel_indices = _extract_channel_indices_from_config(self.parent.parent.exp_config, [self.channels_cb.currentText()])
671
- # self.target_channel = channel_indices[0]
672
-
673
- # def set_threshold_graphically(self):
674
-
675
- # self.parent.locate_image()
676
- # self.set_target_channel()
677
- # thresh = self.threshold_le.get_threshold()
678
-
679
- # if self.parent.current_stack is not None and thresh is not None:
680
- # self.viewer = ThresholdedStackVisualizer(initial_threshold=thresh,
681
- # parent_le = self.threshold_le,
682
- # preprocessing=[['gauss',2],["std",4]],
683
- # stack_path=self.parent.current_stack,
684
- # n_channels=len(self.channel_names),
685
- # target_channel=self.target_channel,
686
- # window_title='Set the exclusion threshold',
687
- # )
688
- # self.viewer.show()
689
-
690
750
 
691
751
  def color_from_status(status, recently_modified=False):
692
752
  if not recently_modified:
@@ -708,19 +768,40 @@ def color_from_status(status, recently_modified=False):
708
768
  else:
709
769
  return 'k'
710
770
 
711
- def color_from_state(state, recently_modified=False):
771
+ def color_from_state(state):
772
+
773
+ """
774
+ Generate a color map based on unique values in the provided state array.
775
+
776
+ This function creates a mapping between the unique values found in the `state` array
777
+ and colors from the `tab10` colormap in Matplotlib. A special condition is applied
778
+ to the value `99`, which is assigned the color black ('k').
779
+
780
+ Parameters
781
+ ----------
782
+ state : array-like
783
+ An array or list of state values to be used for generating the color map.
784
+
785
+ Returns
786
+ -------
787
+ dict
788
+ A dictionary where the keys are the unique state values from the `state` array,
789
+ and the values are the corresponding colors from Matplotlib's `tab10` colormap.
790
+ The value `99` is mapped to the color black ('k').
791
+
792
+ Notes
793
+ -----
794
+ - Matplotlib's `tab10` colormap is used for values other than `99`.
795
+ """
796
+
712
797
  unique_values = np.unique(state)
713
798
  color_map={}
714
799
  for value in unique_values:
715
800
  color_map[value] = plt.cm.tab10(value)
716
801
  if value == 99:
717
802
  color_map[value] = 'k'
718
- # colors = plt.cm.tab10(len(unique_values))
719
- # color_map = dict(zip(unique_values, colors))
720
- # print(color_map)
721
- return color_map
722
-
723
803
 
804
+ return color_map
724
805
 
725
806
 
726
807
  def color_from_class(cclass, recently_modified=False):
@@ -771,3 +852,97 @@ class ChannelChoice(QWidget):
771
852
  filtername = self.combo_box.currentText()
772
853
  self.parent_window.list_widget.addItems([filtername])
773
854
  self.close()
855
+
856
+ def help_generic(tree):
857
+
858
+ """
859
+ Interactively traverse a decision tree to provide user guidance based on a nested dictionary structure.
860
+
861
+ This function takes a nested dictionary representing a decision tree and guides the user through
862
+ it step-by-step by displaying messages for user input using the `generic_msg()` function.
863
+ At each step, the user selects a key that corresponds to a further step in the tree, until a
864
+ final suggestion (leaf node) is reached.
865
+
866
+ Parameters
867
+ ----------
868
+ tree : dict
869
+ A dictionary where keys represent options and values represent either further steps (as dictionaries)
870
+ or a final suggestion (leaf nodes).
871
+
872
+ Returns
873
+ -------
874
+ any
875
+ The final suggestion or outcome after traversing the decision tree.
876
+
877
+ Example
878
+ -------
879
+ >>> decision_tree = {
880
+ ... 'Start': {
881
+ ... 'Option 1': {
882
+ ... 'Sub-option 1': 'Final suggestion 1',
883
+ ... 'Sub-option 2': 'Final suggestion 2'
884
+ ... },
885
+ ... 'Option 2': 'Final suggestion 3'
886
+ ... }
887
+ ... }
888
+ >>> result = help_generic(decision_tree)
889
+ # The function prompts the user to choose between "Option 1" or "Option 2",
890
+ # and then proceeds through the tree based on the user's choices.
891
+ """
892
+
893
+ output = generic_msg(list(tree.keys())[0])
894
+ while output is not None:
895
+ tree = tree[list(tree.keys())[0]][output]
896
+ if isinstance(tree,dict):
897
+ output = generic_msg(list(tree.keys())[0])
898
+ else:
899
+ # return the final suggestion
900
+ output = None
901
+ return tree
902
+
903
+ def generic_msg(text):
904
+
905
+ """
906
+ Display a message box with a question and capture the user's response.
907
+
908
+ This function creates a message box with a `Yes`, `No`, and `Cancel` option,
909
+ displaying the provided `text` as the question. It returns the user's selection as a string.
910
+
911
+ Parameters
912
+ ----------
913
+ text : str
914
+ The message or question to display in the message box.
915
+
916
+ Returns
917
+ -------
918
+ str or None
919
+ The user's response: "yes" if Yes is selected, "no" if No is selected,
920
+ and `None` if Cancel is selected or the dialog is closed.
921
+
922
+ Example
923
+ -------
924
+ >>> response = generic_msg("Would you like to continue?")
925
+ >>> if response == "yes":
926
+ ... print("User chose Yes")
927
+ ... elif response == "no":
928
+ ... print("User chose No")
929
+ ... else:
930
+ ... print("User cancelled the action")
931
+
932
+ Notes
933
+ -----
934
+ - The message box displays a window with three options: Yes, No, and Cancel.
935
+ """
936
+
937
+ msgBox = QMessageBox()
938
+ msgBox.setIcon(QMessageBox.Question)
939
+ msgBox.setText(text)
940
+ msgBox.setWindowTitle("Question")
941
+ msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
942
+ returnValue = msgBox.exec()
943
+ if returnValue == QMessageBox.Yes:
944
+ return "yes"
945
+ elif returnValue == QMessageBox.No:
946
+ return "no"
947
+ else:
948
+ return None