Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
from PyQt5.QtWidgets import (
QPushButton, QVBoxLayout, QWidget,
QSlider, QLabel, QGridLayout, QSizePolicy
)
from PyQt5.QtGui import QPixmap, QImage
from PyQt5.QtCore import Qt
import numpy as np
class AdvancedSettingsWidget(QWidget):
"""
Shows toggle rainbow, circle editor, line smoothing slider, contrast slider,
plus two image previews (contrasted-blurred and cost).
The images should maintain aspect ratio upon resize.
"""
def __init__(self, main_window, parent=None):
super().__init__(parent)
self._main_window = main_window
self._last_cb_pix = None # store QPixmap for contrasted-blurred
self._last_cost_pix = None # store QPixmap for cost
main_layout = QVBoxLayout()
self.setLayout(main_layout)
# A small grid for controls
controls_layout = QGridLayout()
# 1) Rainbow toggle
self.btn_toggle_rainbow = QPushButton("Toggle Rainbow")
self.btn_toggle_rainbow.clicked.connect(self._on_toggle_rainbow)
controls_layout.addWidget(self.btn_toggle_rainbow, 0, 0)
# 2) Circle editor
self.btn_circle_editor = QPushButton("Calibrate Kernel Size")
self.btn_circle_editor.clicked.connect(self._main_window.open_circle_editor)
controls_layout.addWidget(self.btn_circle_editor, 0, 1)
# 3) Line smoothing slider + label
self._lab_smoothing = QLabel("Line smoothing (3)")
controls_layout.addWidget(self._lab_smoothing, 1, 0)
self.line_smoothing_slider = QSlider(Qt.Horizontal)
self.line_smoothing_slider.setRange(3, 51)
self.line_smoothing_slider.setValue(3)
self.line_smoothing_slider.valueChanged.connect(self._on_line_smoothing_slider)
controls_layout.addWidget(self.line_smoothing_slider, 1, 1)
# 4) Contrast slider + label
self._lab_contrast = QLabel("Contrast (0.01)")
controls_layout.addWidget(self._lab_contrast, 2, 0)
self.contrast_slider = QSlider(Qt.Horizontal)
self.contrast_slider.setRange(1, 20)
self.contrast_slider.setValue(1) # i.e. 0.01
self.contrast_slider.setSingleStep(1)
self.contrast_slider.valueChanged.connect(self._on_contrast_slider)
controls_layout.addWidget(self.contrast_slider, 2, 1)
main_layout.addLayout(controls_layout)
# We'll set a minimum width so that the main window expands
# rather than overlapping the image
self.setMinimumWidth(350)
# Now a vertical layout for the two images, each with a label above it
images_layout = QVBoxLayout()
# 1) Contrasted-blurred label + image
self.label_cb_title = QLabel("Contrasted Blurred Image")
self.label_cb_title.setAlignment(Qt.AlignCenter)
images_layout.addWidget(self.label_cb_title)
self.label_contrasted_blurred = QLabel()
self.label_contrasted_blurred.setAlignment(Qt.AlignCenter)
self.label_contrasted_blurred.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
images_layout.addWidget(self.label_contrasted_blurred)
# 2) Cost image label + image
self.label_cost_title = QLabel("Current COST IMAGE")
self.label_cost_title.setAlignment(Qt.AlignCenter)
images_layout.addWidget(self.label_cost_title)
self.label_cost_image = QLabel()
self.label_cost_image.setAlignment(Qt.AlignCenter)
self.label_cost_image.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
images_layout.addWidget(self.label_cost_image)
main_layout.addLayout(images_layout)
def showEvent(self, event):
""" When shown, ask parent to resize to accommodate. """
super().showEvent(event)
if self.parentWidget():
self.parentWidget().adjustSize()
def resizeEvent(self, event):
"""
Keep the images at correct aspect ratio by re-scaling
our stored pixmaps to the new label sizes.
"""
super().resizeEvent(event)
self._update_labels()
def _update_labels(self):
if self._last_cb_pix is not None:
scaled_cb = self._last_cb_pix.scaled(
self.label_contrasted_blurred.size(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
)
self.label_contrasted_blurred.setPixmap(scaled_cb)
if self._last_cost_pix is not None:
scaled_cost = self._last_cost_pix.scaled(
self.label_cost_image.size(),
Qt.KeepAspectRatio,
Qt.SmoothTransformation
)
self.label_cost_image.setPixmap(scaled_cost)
def _on_toggle_rainbow(self):
self._main_window.toggle_rainbow()
def _on_line_smoothing_slider(self, value):
self._lab_smoothing.setText(f"Line smoothing ({value})")
self._main_window.image_view.set_savgol_window_length(value)
def _on_contrast_slider(self, value):
clip_limit = value / 100.0
self._lab_contrast.setText(f"Contrast ({clip_limit:.2f})")
self._main_window.update_contrast(clip_limit)
def update_displays(self, contrasted_img_np, cost_img_np):
"""
Called by main_window to refresh the two images in the advanced panel.
We'll store them as QPixmaps, then do the re-scale in _update_labels().
"""
cb_pix = self._np_array_to_qpixmap(contrasted_img_np)
cost_pix = self._np_array_to_qpixmap(cost_img_np, normalize=True)
self._last_cb_pix = cb_pix
self._last_cost_pix = cost_pix
self._update_labels()
def _np_array_to_qpixmap(self, arr, normalize=False):
if arr is None:
return None
arr_ = arr.copy()
if normalize:
mn, mx = arr_.min(), arr_.max()
if abs(mx - mn) < 1e-12:
arr_[:] = 0
else:
arr_ = (arr_ - mn) / (mx - mn)
arr_ = np.clip(arr_, 0, 1)
arr_255 = (arr_ * 255).astype(np.uint8)
h, w = arr_255.shape
qimage = QImage(arr_255.data, w, h, w, QImage.Format_Grayscale8)
return QPixmap.fromImage(qimage)