mimetica 0.2.5.post4__tar.gz

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 (33) hide show
  1. mimetica-0.2.5.post4/PKG-INFO +64 -0
  2. mimetica-0.2.5.post4/README.md +42 -0
  3. mimetica-0.2.5.post4/mimetica/__init__.py +11 -0
  4. mimetica-0.2.5.post4/mimetica/gui/__init__.py +0 -0
  5. mimetica-0.2.5.post4/mimetica/gui/canvas.py +343 -0
  6. mimetica-0.2.5.post4/mimetica/gui/dock.py +201 -0
  7. mimetica-0.2.5.post4/mimetica/gui/image.py +45 -0
  8. mimetica-0.2.5.post4/mimetica/gui/main.py +206 -0
  9. mimetica-0.2.5.post4/mimetica/gui/plot.py +5 -0
  10. mimetica-0.2.5.post4/mimetica/gui/roi/__init__.py +0 -0
  11. mimetica-0.2.5.post4/mimetica/gui/roi/contour.py +23 -0
  12. mimetica-0.2.5.post4/mimetica/gui/roi/line.py +119 -0
  13. mimetica-0.2.5.post4/mimetica/gui/roi/target.py +13 -0
  14. mimetica-0.2.5.post4/mimetica/gui/settings.py +1 -0
  15. mimetica-0.2.5.post4/mimetica/gui/splitview.py +265 -0
  16. mimetica-0.2.5.post4/mimetica/gui/tab.py +180 -0
  17. mimetica-0.2.5.post4/mimetica/gui/thumbnail.py +49 -0
  18. mimetica-0.2.5.post4/mimetica/models/__init__.py +0 -0
  19. mimetica-0.2.5.post4/mimetica/scan/__init__.py +0 -0
  20. mimetica-0.2.5.post4/mimetica/scan/layer.py +140 -0
  21. mimetica-0.2.5.post4/mimetica/scan/stack.py +209 -0
  22. mimetica-0.2.5.post4/mimetica/utils/__init__.py +7 -0
  23. mimetica-0.2.5.post4/mimetica/utils/config.py +142 -0
  24. mimetica-0.2.5.post4/mimetica/utils/functions.py +107 -0
  25. mimetica-0.2.5.post4/mimetica/utils/logger.py +5 -0
  26. mimetica-0.2.5.post4/mimetica.egg-info/PKG-INFO +64 -0
  27. mimetica-0.2.5.post4/mimetica.egg-info/SOURCES.txt +31 -0
  28. mimetica-0.2.5.post4/mimetica.egg-info/dependency_links.txt +1 -0
  29. mimetica-0.2.5.post4/mimetica.egg-info/entry_points.txt +2 -0
  30. mimetica-0.2.5.post4/mimetica.egg-info/requires.txt +14 -0
  31. mimetica-0.2.5.post4/mimetica.egg-info/top_level.txt +1 -0
  32. mimetica-0.2.5.post4/pyproject.toml +32 -0
  33. mimetica-0.2.5.post4/setup.cfg +4 -0
@@ -0,0 +1,64 @@
1
+ Metadata-Version: 2.4
2
+ Name: mimetica
3
+ Version: 0.2.5.post4
4
+ Author-email: Alexander Hadjiivanov <43831101+cantordust@users.noreply.github.com>
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.12
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: cloup
9
+ Requires-Dist: ipykernel
10
+ Requires-Dist: loguru
11
+ Requires-Dist: matplotlib
12
+ Requires-Dist: numpy
13
+ Requires-Dist: omegaconf
14
+ Requires-Dist: openpyxl
15
+ Requires-Dist: pandas
16
+ Requires-Dist: platformdirs
17
+ Requires-Dist: pyqtgraph
18
+ Requires-Dist: PySide6
19
+ Requires-Dist: rich
20
+ Requires-Dist: scikit-image[optional]
21
+ Requires-Dist: shapely
22
+
23
+ # Overview
24
+ Mimetica is a software package for analysing microCT scans. It can load a stack of scans and compute the radial and phase profiles of the material fraction of each layer. The results can also be exported to CSV for further analysis. Mimetica was written in Python using PySide6 as the GUI framework.
25
+
26
+ # Installation
27
+
28
+ Create a new Python environment and run
29
+
30
+ ```bash
31
+ pip install mimetica
32
+ ```
33
+
34
+ After that, you can launch the program from the terminal:
35
+
36
+ ```bash
37
+ mimetica
38
+ ```
39
+
40
+ You can see the available CLI options with the `--help` argument:
41
+
42
+ ```bash
43
+ mimetica --help
44
+ ```
45
+
46
+ You should see the following output:
47
+
48
+ ```bash
49
+ Usage: mimetica [OPTIONS]
50
+
51
+ Input: [mutually exclusive]
52
+ Open one or more images.
53
+ -i, --image TEXT Open a single image.
54
+ -s, --stack TEXT Open a stack (directory of images).
55
+
56
+ Other options:
57
+ --help Show this message and exit.
58
+ ```
59
+
60
+ For instance, to open a single image or a stack (a directory of images) from the CLI:
61
+
62
+ ```bash
63
+ mimetica -s <path_to_directory>
64
+ ```
@@ -0,0 +1,42 @@
1
+ # Overview
2
+ Mimetica is a software package for analysing microCT scans. It can load a stack of scans and compute the radial and phase profiles of the material fraction of each layer. The results can also be exported to CSV for further analysis. Mimetica was written in Python using PySide6 as the GUI framework.
3
+
4
+ # Installation
5
+
6
+ Create a new Python environment and run
7
+
8
+ ```bash
9
+ pip install mimetica
10
+ ```
11
+
12
+ After that, you can launch the program from the terminal:
13
+
14
+ ```bash
15
+ mimetica
16
+ ```
17
+
18
+ You can see the available CLI options with the `--help` argument:
19
+
20
+ ```bash
21
+ mimetica --help
22
+ ```
23
+
24
+ You should see the following output:
25
+
26
+ ```bash
27
+ Usage: mimetica [OPTIONS]
28
+
29
+ Input: [mutually exclusive]
30
+ Open one or more images.
31
+ -i, --image TEXT Open a single image.
32
+ -s, --stack TEXT Open a stack (directory of images).
33
+
34
+ Other options:
35
+ --help Show this message and exit.
36
+ ```
37
+
38
+ For instance, to open a single image or a stack (a directory of images) from the CLI:
39
+
40
+ ```bash
41
+ mimetica -s <path_to_directory>
42
+ ```
@@ -0,0 +1,11 @@
1
+ from .utils.config import conf
2
+ from .utils.logger import logger
3
+ from .gui.dock import Dock
4
+ from .gui.plot import Plot
5
+ from .scan.layer import Layer
6
+ from .gui.thumbnail import Thumbnail
7
+ from .scan.stack import Stack
8
+ from .gui.image import ImageView
9
+ from .gui.canvas import Canvas
10
+ from .gui.splitview import SplitView
11
+ from .gui.tab import Tab
File without changes
@@ -0,0 +1,343 @@
1
+ from PySide6.QtCore import Qt
2
+ from PySide6.QtCore import Slot
3
+ from PySide6.QtCore import Signal
4
+ from PySide6.QtCore import QEvent
5
+ from PySide6.QtCore import QObject
6
+
7
+ from PySide6.QtGui import QKeyEvent
8
+ from PySide6.QtGui import QMouseEvent
9
+ from PySide6.QtGui import QEnterEvent
10
+
11
+ from PySide6.QtWidgets import QWidget
12
+ from PySide6.QtWidgets import QGridLayout
13
+ from PySide6.QtWidgets import QHBoxLayout
14
+ from PySide6.QtWidgets import QScrollArea
15
+ from PySide6.QtWidgets import QSizePolicy
16
+
17
+ import numpy as np
18
+ import skimage as ski
19
+
20
+ import shapely as shp
21
+ import pyqtgraph as pg
22
+ from pyqtgraph.GraphicsScene.mouseEvents import MouseClickEvent
23
+ from pyqtgraph import SignalProxy
24
+
25
+ from mimetica import Thumbnail
26
+ from mimetica import ImageView
27
+ from mimetica import Layer
28
+ from mimetica import Stack
29
+ from mimetica import conf
30
+ from mimetica.gui.roi.contour import Contour
31
+ from mimetica.gui.roi.target import Target
32
+
33
+
34
+ class Canvas(QWidget):
35
+ plot = Signal()
36
+ highlight_plot = Signal(int)
37
+ update_radial_plot = Signal(float)
38
+ update_phase_plot = Signal(float)
39
+
40
+ class EventHandler(QObject):
41
+
42
+ ctrl_signal = Signal(bool)
43
+ focus_signal = Signal()
44
+
45
+ def __init__(self, wh, *args, **kwargs):
46
+
47
+ super().__init__(*args, **kwargs)
48
+ self.wh = wh
49
+
50
+ def eventFilter(self, obj: QObject, event: QEvent):
51
+ if obj is self.wh:
52
+ if isinstance(event, QEnterEvent):
53
+ self.focus_signal.emit()
54
+ elif isinstance(event, QKeyEvent) and event.key() == Qt.Key.Key_Control:
55
+ if event.type() == QKeyEvent.Type.KeyPress:
56
+ self.ctrl_signal.emit(True)
57
+ elif event.type() == QKeyEvent.Type.KeyRelease:
58
+ self.ctrl_signal.emit(False)
59
+ return QObject().eventFilter(obj, event)
60
+
61
+ def __init__(
62
+ self,
63
+ *args,
64
+ **kwargs,
65
+ ):
66
+ super().__init__(*args, **kwargs)
67
+
68
+ # Image viewport
69
+ # ==================================================
70
+ self.iv = ImageView(self)
71
+
72
+ # Current stack
73
+ # ==================================================
74
+ self.stack = None
75
+
76
+ # Arrays used for displaying parts of the layer
77
+ # ==================================================
78
+ self.image = None
79
+ self.centre = None
80
+ self.stack_mbc = None
81
+ self.layer_mbcs = []
82
+ self.phase_resolution = 1
83
+
84
+ # Layout grid
85
+ # ==================================================
86
+ self.grid = QGridLayout(self)
87
+ self.grid.addWidget(self.iv, 0, 0, 1, 2)
88
+
89
+ # Thumbnails
90
+ # ==================================================
91
+ self.thumbnails = []
92
+ self.tb_widget = QWidget()
93
+ self.tb_scroll_area = QScrollArea(self)
94
+ self.tb_layout = QHBoxLayout()
95
+ self.tb_layout.setContentsMargins(0, 0, 0, 0)
96
+ self.tb_widget.setLayout(self.tb_layout)
97
+
98
+ # Layer highlighters
99
+ # ==================================================
100
+ self.slice_contour_pen = pg.mkPen(color=conf.layer_contour_colour, width=1)
101
+ self.slice_contour = None
102
+ self.slice_centre_pen = pg.mkPen(
103
+ color=conf.layer_contour_colour, width=1
104
+ ) # TODO: Separate conf entry
105
+ self.slice_centre = None
106
+
107
+ # Stack highlighters
108
+ # ==================================================
109
+ self.tb_scroll_area.setFixedHeight(110)
110
+ self.tb_scroll_area.setHorizontalScrollBarPolicy(
111
+ Qt.ScrollBarPolicy.ScrollBarAsNeeded
112
+ )
113
+ self.tb_scroll_area.setVerticalScrollBarPolicy(
114
+ Qt.ScrollBarPolicy.ScrollBarAlwaysOff
115
+ )
116
+ self.tb_scroll_area.setSizePolicy(
117
+ QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum
118
+ )
119
+ self.tb_scroll_area.setWidgetResizable(True)
120
+ self.tb_scroll_area.setWidget(self.tb_widget)
121
+
122
+ self.grid.addWidget(self.tb_scroll_area, 1, 0, 1, 2)
123
+
124
+ # Slots, signals and proxies
125
+ # ==================================================
126
+
127
+ # Mouse tracking for the ROI
128
+ # ==================================================
129
+ self.mouse_tracking_toggle = False
130
+ self.mouse_tracker = SignalProxy(
131
+ self.iv.scene.sigMouseMoved,
132
+ rateLimit=100,
133
+ slot=self._mouse_coordinates,
134
+ )
135
+
136
+ self.event_handler = Canvas.EventHandler(self)
137
+ self.installEventFilter(self.event_handler)
138
+ self.event_handler.ctrl_signal.connect(self._toggle_mouse_tracking)
139
+ self.event_handler.focus_signal.connect(self._focus_canvas)
140
+
141
+ @property
142
+ def layer(self) -> Layer:
143
+ return self.stack.layers[self.stack.active_layer]
144
+
145
+ def set_stack(
146
+ self,
147
+ stack: Stack,
148
+ auto_range: bool = False,
149
+ ):
150
+ # Update the stack
151
+ # ==================================================
152
+ self.stack = stack
153
+ if len(self.stack.layers) > 0:
154
+ self.stack._set_active_layer()
155
+
156
+ # Update the thumbnails
157
+ # ==================================================
158
+ self._update_thumbnails()
159
+
160
+ # Select the first layer
161
+ # ==================================================
162
+ self.slot_select_layer(0, auto_range)
163
+
164
+ def process(
165
+ self,
166
+ auto_range: bool = False,
167
+ ):
168
+ # Paint the result onto the canvas
169
+ # ==================================================
170
+ self.draw(auto_range)
171
+
172
+ # Set up the ROI
173
+ # ==================================================
174
+ self.iv.set_roi(self.layer)
175
+
176
+ # Plot the radial and phase profiles
177
+ # ==================================================
178
+ self.plot.emit()
179
+
180
+ def draw(
181
+ self,
182
+ auto_range: bool = False,
183
+ ):
184
+ # Coordinates of the current layer and the stack
185
+ # ==================================================
186
+ lcx, lcy = self.layer.centre
187
+
188
+ # Reset the canvas
189
+ # ==================================================
190
+ self.image = np.zeros(self.layer.canvas.shape + (4,))
191
+
192
+ # Draw the slice and potentially the stack
193
+ # ==================================================
194
+ idx = np.argwhere(self.layer.canvas > 0).T
195
+ self.image[idx[0], idx[1], :3] = ski.exposure.rescale_intensity(
196
+ self.layer.canvas[idx[0], idx[1], None],
197
+ out_range=(0.0, 1.0),
198
+ ) * np.array(
199
+ [
200
+ conf.active_layer_colour.red(),
201
+ conf.active_layer_colour.green(),
202
+ conf.active_layer_colour.blue(),
203
+ ]
204
+ )
205
+
206
+ self.image[idx[0], idx[1], 3] = conf.active_layer_colour.alpha()
207
+
208
+ # Draw the centre
209
+ # ==================================================
210
+ if self.slice_centre is not None:
211
+ self.iv.removeItem(self.slice_centre)
212
+ self.slice_centre = Target(
213
+ (lcx + 0.5, lcy + 0.5),
214
+ pen=self.slice_centre_pen,
215
+ )
216
+ self.iv.addItem(self.slice_centre)
217
+
218
+ # Draw the slice contour
219
+ # ==================================================
220
+ if self.slice_contour is not None:
221
+ self.iv.removeItem(self.slice_contour)
222
+ self.slice_contour = Contour(
223
+ (lcx, lcy),
224
+ radius=self.layer.mbr + 0.5,
225
+ pen=self.slice_contour_pen,
226
+ )
227
+ self.iv.addItem(self.slice_contour)
228
+
229
+ # Set the image
230
+ # ==================================================
231
+ self.iv.setImage(
232
+ self.image, autoRange=auto_range, levels=(0, 255), levelMode="rgba"
233
+ )
234
+
235
+ def _update_thumbnails(self):
236
+ while True:
237
+ item = self.tb_layout.takeAt(0)
238
+ if item is None or item.isEmpty():
239
+ break
240
+ self.tb_layout.removeWidget(item.widget())
241
+ item.widget().deleteLater()
242
+
243
+ self.thumbnails.clear()
244
+ for index, layer in enumerate(self.stack.layers):
245
+ tb = Thumbnail(index, layer, self, 90)
246
+ tb._selected.connect(self.slot_select_layer)
247
+ self.thumbnails.append(tb)
248
+
249
+ for idx, tb in enumerate(self.thumbnails):
250
+ self.tb_layout.addWidget(tb, alignment=Qt.AlignmentFlag.AlignLeft)
251
+
252
+ self.tb_layout.addStretch()
253
+
254
+ @Slot()
255
+ def _reset_zoom(self):
256
+ self.draw(auto_range=True)
257
+
258
+ def eventFilter(self, obj, event):
259
+ if obj is self.window:
260
+ if event.type() == QEvent.KeyPress:
261
+ if event.key() == Qt.Key_Control:
262
+ self.ctrl_signal.emit(True)
263
+ if event.type() == QEvent.KeyRelease:
264
+ if event.key() == Qt.Key_Control:
265
+ self.ctrl_signal.emit(False)
266
+ return super().eventFilter(obj, event)
267
+
268
+ @Slot()
269
+ def _mouse_coordinates(
270
+ self,
271
+ event: MouseClickEvent,
272
+ ):
273
+
274
+ if self.mouse_tracking_toggle:
275
+ if self.iv.radial_roi is not None:
276
+ pos = self.iv.getView().vb.mapSceneToView(event)[0]
277
+
278
+ cx = pos.x() - self.layer.centre[0] - 0.5
279
+ cy = pos.y() - self.layer.centre[1] - 0.5
280
+ r = np.sqrt(cx**2 + cy**2)
281
+ radius = r / self.layer.mbr
282
+ if radius <= 1:
283
+ phase = 0 if r == 0 else np.rad2deg(np.arccos(cx / r))
284
+
285
+ if cy < 0:
286
+ phase = 360 - phase
287
+
288
+ self.iv.radial_roi.setSize(
289
+ 2 * r,
290
+ center=(0.5, 0.5),
291
+ update=True,
292
+ finish=True,
293
+ )
294
+ self.iv.phase_roi.set_end(pos)
295
+ self.update_radial_plot.emit(radius)
296
+ self.update_phase_plot.emit(phase)
297
+
298
+ @Slot(bool)
299
+ def _toggle_mouse_tracking(
300
+ self,
301
+ toggle: bool,
302
+ ):
303
+ self.mouse_tracking_toggle = toggle
304
+
305
+ # TODO: Add a setting to control the residual visibility
306
+ # self.iv.phase_roi.setVisible(toggle)
307
+ # self.iv.radial_roi.setVisible(toggle)
308
+
309
+ @Slot()
310
+ def _focus_canvas(self):
311
+ self.setFocus()
312
+
313
+ @Slot()
314
+ def _set_active_layer_colour(self):
315
+ self.draw()
316
+
317
+ @Slot()
318
+ def _set_slice_contour_colour(self):
319
+ self.slice_contour_pen = pg.mkPen(color=conf.layer_contour_colour, width=1)
320
+ self.draw()
321
+
322
+ @Slot(int, bool)
323
+ def slot_select_layer(
324
+ self,
325
+ layer: int,
326
+ auto_range: bool = False,
327
+ ):
328
+ cur_layer = self.stack.active_layer
329
+ if cur_layer is None:
330
+ cur_layer = 0
331
+
332
+ # Update the layer
333
+ self.stack._set_active_layer(layer)
334
+
335
+ # Highlight the selected thumbnail
336
+ self.thumbnails[cur_layer].deselect()
337
+ self.thumbnails[layer].select()
338
+
339
+ # Process the layer
340
+ self.process(auto_range)
341
+
342
+ # Emit a signal to highlight the relevant plots
343
+ self.highlight_plot.emit(layer)
@@ -0,0 +1,201 @@
1
+ from PySide6.QtCore import Qt
2
+ from PySide6.QtCore import Signal
3
+ from PySide6.QtCore import Slot
4
+
5
+ from PySide6.QtWidgets import QWidget
6
+ from PySide6.QtWidgets import QDockWidget
7
+ from PySide6.QtWidgets import QPushButton
8
+ from PySide6.QtWidgets import QCheckBox
9
+ from PySide6.QtWidgets import QSpinBox
10
+ from PySide6.QtWidgets import QLabel
11
+ from PySide6.QtWidgets import QFormLayout
12
+ from PySide6.QtWidgets import QSizePolicy
13
+
14
+ from mimetica import conf
15
+ from mimetica.utils.functions import as_rgba
16
+ from mimetica.utils.functions import get_colour
17
+
18
+
19
+ class Dock(QDockWidget):
20
+ sig_show_inactive_plots = Signal()
21
+ sig_set_active_plot_colour = Signal()
22
+ sig_set_inactive_plot_colour = Signal()
23
+ sig_set_layer_contour_colour = Signal()
24
+ sig_set_active_layer_colour = Signal()
25
+ # sig_show_stack = Signal()
26
+ sig_set_radial_segments = Signal(int)
27
+ sig_set_phase_segments = Signal(int)
28
+
29
+ def __init__(self, *args, **kwargs):
30
+
31
+ super().__init__(*args, **kwargs)
32
+
33
+ title = QLabel("Settings", self)
34
+ title.setAlignment(Qt.AlignmentFlag.AlignCenter)
35
+ title.setStyleSheet("font-size: 14pt; font-weight: bold; text-align: center;")
36
+ title.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
37
+ self.setTitleBarWidget(title)
38
+
39
+ self.setVisible(False)
40
+ self.setMinimumWidth(200)
41
+ topright = Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignRight
42
+
43
+ self.grid = QFormLayout()
44
+
45
+ # Show all plots checkbox
46
+ # ==================================================
47
+ self.show_inactive_plots_lbl = QLabel(f"Show all plots:", self)
48
+ self.show_inactive_plots_cbox = QCheckBox(self)
49
+ self.show_inactive_plots_cbox.setChecked(conf.show_inactive_plots)
50
+ self.grid.addRow(self.show_inactive_plots_lbl, self.show_inactive_plots_cbox)
51
+ self.show_inactive_plots_cbox.stateChanged.connect(
52
+ self._slot_show_inactive_plots
53
+ )
54
+
55
+ # Active plot colour
56
+ # ==================================================
57
+ self.active_plot_colour_lbl = QLabel(f"Active plot colour:", self)
58
+ self.active_plot_colour_btn = QPushButton(self)
59
+ self.active_plot_colour_btn.setStyleSheet(
60
+ f"background-color:rgba({as_rgba(conf.active_plot_colour)});"
61
+ )
62
+ self.grid.addRow(self.active_plot_colour_lbl, self.active_plot_colour_btn)
63
+ self.active_plot_colour_btn.pressed.connect(self._slot_set_active_plot_colour)
64
+
65
+ # Inactive plot colour
66
+ # ==================================================
67
+ self.inactive_plot_colour_lbl = QLabel(f"Inactive plot colour:", self)
68
+ self.inactive_plot_colour_btn = QPushButton(self)
69
+ self.inactive_plot_colour_btn.setStyleSheet(
70
+ f"background-color:rgba({as_rgba(conf.inactive_plot_colour)});"
71
+ )
72
+ self.grid.addRow(self.inactive_plot_colour_lbl, self.inactive_plot_colour_btn)
73
+ self.inactive_plot_colour_btn.pressed.connect(
74
+ self._slot_set_inactive_plot_colour
75
+ )
76
+
77
+ # Active layer colour
78
+ # ==================================================
79
+ self.layer_colour_lbl = QLabel(f"Slice colour:", self)
80
+ self.active_layer_colour_btn = QPushButton(self)
81
+ self.active_layer_colour_btn.setStyleSheet(
82
+ f"background-color:rgba({as_rgba(conf.active_layer_colour)});"
83
+ )
84
+ self.grid.addRow(self.layer_colour_lbl, self.active_layer_colour_btn)
85
+ self.active_layer_colour_btn.pressed.connect(self._slot_set_active_layer_colour)
86
+
87
+ # Layer contour colour
88
+ # ==================================================
89
+ self.layer_contour_colour_lbl = QLabel(f"Slice contour colour:", self)
90
+ self.layer_contour_colour_btn = QPushButton(self)
91
+ self.layer_contour_colour_btn.setStyleSheet(
92
+ f"background-color:rgba({as_rgba(conf.layer_contour_colour)});"
93
+ )
94
+ self.grid.addRow(self.layer_contour_colour_lbl, self.layer_contour_colour_btn)
95
+ self.layer_contour_colour_btn.pressed.connect(
96
+ self._slot_set_slice_contour_colour
97
+ )
98
+
99
+ # DEPRECATED
100
+ # Show the stack
101
+ # ==================================================
102
+ # self.show_stack_lbl = QLabel(f"Show the stack:", self)
103
+ # self.show_stack_cbox = QCheckBox(self)
104
+ # self.show_stack_cbox.setChecked(conf.show_stack)
105
+ # self.grid.addRow(self.show_stack_lbl, self.show_stack_cbox)
106
+ # self.show_stack_cbox.stateChanged.connect(self._slot_show_stack)
107
+
108
+ # Radial segments
109
+ # ==================================================
110
+ self.radial_segments_lbl = QLabel(f"Radial segments:", self)
111
+ self.radial_segments_sbox = QSpinBox(
112
+ self,
113
+ value=conf.radial_samples,
114
+ minimum=1,
115
+ maximum=1024,
116
+ singleStep=1,
117
+ )
118
+ self.radial_segments_sbox.setValue(conf.radial_samples)
119
+ self.grid.addRow(self.radial_segments_lbl, self.radial_segments_sbox)
120
+ self.radial_segments_sbox.valueChanged.connect(self._slot_set_radial_segments)
121
+
122
+ # Phase segments
123
+ # ==================================================
124
+ self.phase_segments_lbl = QLabel(f"Phase segments:", self)
125
+ self.phase_segments_sbox = QSpinBox(
126
+ self,
127
+ value=conf.phase_samples,
128
+ minimum=120,
129
+ maximum=720,
130
+ singleStep=1,
131
+ )
132
+ self.phase_segments_sbox.setValue(conf.phase_samples)
133
+ self.grid.addRow(self.phase_segments_lbl, self.phase_segments_sbox)
134
+ self.phase_segments_sbox.valueChanged.connect(self._slot_set_phase_segments)
135
+
136
+ # Set up the main widget
137
+ # ==================================================
138
+ self.body = QWidget()
139
+ self.setWidget(self.body)
140
+ self.body.setLayout(self.grid)
141
+ self.body.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum)
142
+
143
+ @Slot()
144
+ def _slot_show_inactive_plots(self):
145
+ conf.show_inactive_plots = self.show_inactive_plots_cbox.isChecked()
146
+ self.sig_show_inactive_plots.emit()
147
+
148
+ @Slot()
149
+ def _slot_set_active_plot_colour(self):
150
+ colour = get_colour(conf.active_plot_colour, self)
151
+ if colour.isValid():
152
+ conf.active_plot_colour = colour
153
+ self.sig_set_active_plot_colour.emit()
154
+ self.active_plot_colour_btn.setStyleSheet(
155
+ f"background-color:rgba({as_rgba(colour)});"
156
+ )
157
+
158
+ @Slot()
159
+ def _slot_set_inactive_plot_colour(self):
160
+ colour = get_colour(conf.inactive_plot_colour, self)
161
+ if colour.isValid():
162
+ conf.inactive_plot_colour = colour
163
+ self.sig_set_inactive_plot_colour.emit()
164
+ self.inactive_plot_colour_btn.setStyleSheet(
165
+ f"background-color:rgba({as_rgba(colour)});"
166
+ )
167
+
168
+ @Slot()
169
+ def _slot_set_active_layer_colour(self):
170
+ colour = get_colour(conf.active_layer_colour, self)
171
+ if colour.isValid():
172
+ conf.active_layer_colour = colour
173
+ self.sig_set_active_layer_colour.emit()
174
+ self.active_layer_colour_btn.setStyleSheet(
175
+ f"background-color:rgba({as_rgba(colour)});"
176
+ )
177
+
178
+ @Slot()
179
+ def _slot_set_slice_contour_colour(self):
180
+ colour = get_colour(conf.layer_contour_colour, self)
181
+ if colour.isValid():
182
+ conf.layer_contour_colour = colour
183
+ self.sig_set_layer_contour_colour.emit()
184
+ self.layer_contour_colour_btn.setStyleSheet(
185
+ f"background-color:rgba({as_rgba(colour)});"
186
+ )
187
+
188
+ # @Slot()
189
+ # def _slot_show_stack(self):
190
+ # conf.show_stack = self.show_stack_cbox.isChecked()
191
+ # self.sig_show_stack.emit()
192
+
193
+ @Slot()
194
+ def _slot_set_radial_segments(self):
195
+ conf.radial_samples = self.radial_segments_sbox.value()
196
+ self.sig_set_radial_segments.emit(conf.radial_samples)
197
+
198
+ @Slot()
199
+ def _slot_set_phase_segments(self):
200
+ conf.phase_samples = self.phase_segments_sbox.value()
201
+ self.sig_set_phase_segments.emit(conf.phase_samples)