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.
- mimetica-0.2.5.post4/PKG-INFO +64 -0
- mimetica-0.2.5.post4/README.md +42 -0
- mimetica-0.2.5.post4/mimetica/__init__.py +11 -0
- mimetica-0.2.5.post4/mimetica/gui/__init__.py +0 -0
- mimetica-0.2.5.post4/mimetica/gui/canvas.py +343 -0
- mimetica-0.2.5.post4/mimetica/gui/dock.py +201 -0
- mimetica-0.2.5.post4/mimetica/gui/image.py +45 -0
- mimetica-0.2.5.post4/mimetica/gui/main.py +206 -0
- mimetica-0.2.5.post4/mimetica/gui/plot.py +5 -0
- mimetica-0.2.5.post4/mimetica/gui/roi/__init__.py +0 -0
- mimetica-0.2.5.post4/mimetica/gui/roi/contour.py +23 -0
- mimetica-0.2.5.post4/mimetica/gui/roi/line.py +119 -0
- mimetica-0.2.5.post4/mimetica/gui/roi/target.py +13 -0
- mimetica-0.2.5.post4/mimetica/gui/settings.py +1 -0
- mimetica-0.2.5.post4/mimetica/gui/splitview.py +265 -0
- mimetica-0.2.5.post4/mimetica/gui/tab.py +180 -0
- mimetica-0.2.5.post4/mimetica/gui/thumbnail.py +49 -0
- mimetica-0.2.5.post4/mimetica/models/__init__.py +0 -0
- mimetica-0.2.5.post4/mimetica/scan/__init__.py +0 -0
- mimetica-0.2.5.post4/mimetica/scan/layer.py +140 -0
- mimetica-0.2.5.post4/mimetica/scan/stack.py +209 -0
- mimetica-0.2.5.post4/mimetica/utils/__init__.py +7 -0
- mimetica-0.2.5.post4/mimetica/utils/config.py +142 -0
- mimetica-0.2.5.post4/mimetica/utils/functions.py +107 -0
- mimetica-0.2.5.post4/mimetica/utils/logger.py +5 -0
- mimetica-0.2.5.post4/mimetica.egg-info/PKG-INFO +64 -0
- mimetica-0.2.5.post4/mimetica.egg-info/SOURCES.txt +31 -0
- mimetica-0.2.5.post4/mimetica.egg-info/dependency_links.txt +1 -0
- mimetica-0.2.5.post4/mimetica.egg-info/entry_points.txt +2 -0
- mimetica-0.2.5.post4/mimetica.egg-info/requires.txt +14 -0
- mimetica-0.2.5.post4/mimetica.egg-info/top_level.txt +1 -0
- mimetica-0.2.5.post4/pyproject.toml +32 -0
- 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)
|