-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMultiPlotSelectWidget.py
More file actions
250 lines (195 loc) · 8.64 KB
/
MultiPlotSelectWidget.py
File metadata and controls
250 lines (195 loc) · 8.64 KB
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
from qtpy.QtWidgets import QApplication, QMainWindow, QWidget, QGridLayout, QLabel
from qtpy.QtCore import QTimer, Signal, Qt
from qtpy.QtGui import QPixmap, QImage, QPen, QColor, QPainter
from image_display import ImageDisplay
from PIL import Image
import numpy as np
from dataclasses import dataclass
class MyImageWidget(QLabel):
__clicked_signal = Signal(int, int, name='clicked')
__doubleclicked_signal = Signal(int, int, name='doubleclicked')
# __selected_color = QColor(102, 255, 51)
__selected_color = QColor(0, 255, 0)
__selected_width = 8
__latest_color = QColor(255, 0, 0)
__latest_width = 8
def __init__(self, parent=None):
super().__init__(parent)
#self.setStyleSheet("background-color: gray;")
self.setScaledContents(False)
self.setAlignment(Qt.AlignCenter)
self.setMinimumSize(256, 256)
self._userdata = None
self._selected = False
self._latest = False
self._original_pixmap = None
self._first_show = False
@property
def userdata(self):
return self._userdata
@userdata.setter
def userdata(self, value):
self._userdata = value
@property
def latest(self):
return self._latest
@latest.setter
def latest(self, value: bool):
self._latest = bool(value)
def set_image(self, data: np.ndarray):
# qrgb_dict = {'r': lambda x: QtGui.qRgb(x, 0, 0),
# 'g': lambda x: QtGui.qRgb(0, x, 0),
# 'b': lambda x: QtGui.qRgb(0, 0, x)}
# colortable = [qrgb_dict[color](i) for i in range(256)]
s=data.shape
img = QImage(data, s[1], s[0], s[1], QImage.Format_Indexed8)
qpix = QPixmap(QImage(img))
self._original_pixmap = qpix
self._scaleAndSetPixmap()
self.update()
def mousePressEvent(self, ev):
if self._original_pixmap is not None:
d = self.userdata
self.__clicked_signal.emit(*d)
def mouseDoubleClickEvent(self, ev):
(r, c) = self.userdata
#print("double click at {:d},{:d}".format(r, c))
self.__doubleclicked_signal.emit(r, c)
def paintEvent(self, ev):
super().paintEvent(ev)
painter = QPainter()
painter.begin(self)
if self._selected:
# draw box at edge
self._drawOutlineBox(painter, self.__selected_color, self.__selected_width)
elif self._latest:
self._drawOutlineBox(painter, self.__latest_color, self.__latest_width)
painter.end()
def _drawOutlineBox(self, painter: QPainter, color: QColor = QColor(255, 255, 255), width: int = 8):
painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_Source)
pen = QPen(color)
pen.setWidth(width)
painter.setPen(pen)
W=self.pixmap().width()
H=self.pixmap().height()
hpw = width//2 # half pen width
x0 = (self.width() - W) // 2
y0 = (self.height() - H) // 2
painter.drawRect(x0, y0, W - hpw+1, H - hpw+1)
def _scaleAndSetPixmap(self):
if self._original_pixmap:
scaled = self._original_pixmap.scaled(self.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.setPixmap(scaled)
def showEvent(self, event):
if not self._first_show:
self._first_show = True
self._scaleAndSetPixmap()
super().showEvent(event)
def resizeEvent(self, event):
self._scaleAndSetPixmap()
super().resizeEvent(event)
@dataclass
class MPSWData:
ascan_data: None | np.ndarray = None
spectra_data: None | np.ndarray = None
class MPSW(QWidget):
'''
This is a widget that will display images in an MxN grid. After images fill the grid, new images replace the oldest ones.
User can select an image with a mouse click - the image is outlined, and will not be replaced while it is outlined. A double-click on an image
causes the save(row,col) signal to be emitted. Calling get_data(row,col) will get the data passed when the image was added. The first
object returned is the image that was displayed. The second is the data passed along with the image - presumaby the data you want to save.
'''
__save_signal = Signal(int, int, name='save')
def __init__(self, parent=None, columns=2, rows=2):
super().__init__(parent)
self._rows = rows
self._columns = columns
self._last_position = (0, 0) # this will keep track of the last position that was updated
layout = QGridLayout()
# a word of warning: the widgets in QGridLayout are specified (0-based) ROW,COL
# Naming of each element in _data and _widgets
self._data = [[None for _ in range(self._columns)] for _ in range(self._rows)]
self._widgets = [[0 for _ in range(self._columns)] for _ in range(self._rows)]
self._count = -1 # this will keep track of the next position to be replaced
for i in range(self._columns):
layout.setColumnStretch(i, 1) # add column stretch once to each column
for j in range(self._rows):
if i == 0:
layout.setRowStretch(j, 1) # add row stretch to each row, only at column 0
self._widgets[j][i] = MyImageWidget()
self._widgets[j][i].userdata = (j,i)
self._widgets[j][i].clicked.connect(self.image_clicked)
self._widgets[j][i].doubleclicked.connect(self.image_doubleclicked)
layout.addWidget(self._widgets[j][i], j, i)
self.setLayout(layout)
def get_data(self, r, c) -> MPSWData:
if r<self._rows and c < self._columns:
return self._data[r][c]
else:
raise RuntimeError("Indices out of range for this widget.")
def _update_selected(self, r, c):
for i in range(self._columns):
for j in range(self._rows):
if self._widgets[j][i]._selected:
self._widgets[j][i]._selected = False
self._widgets[j][i].update()
self._widgets[r][c]._selected = True
self._widgets[r][c].update()
def image_clicked(self, r, c):
self._update_selected(r, c)
#print("Image clicked r={:d} c={:d}, width={:d} height={:d}".format(r, c, self._widgets[r][c].width(), self._widgets[r][c].height()))
def image_doubleclicked(self, r, c):
#print("Image doubleclicked r={:d} c={:d}, width={:d} height={:d}".format(r, c, self._widgets[r][c].width(), self._widgets[r][c].height()))
self.__save_signal.emit(r, c)
def _ind2sub(self, ind):
i = ind % (self._rows * self._columns)
r = i // self._columns
c = i % self._columns
return (r, c)
def _next_position(self):
while True:
self._count+=1
(r,c) = self._ind2sub(self._count)
if self._data[r][c] is None or not self._widgets[r][c]._selected:
return (r,c)
def add_data(self, ascan_data, spectra_data):
(r, c) = self._next_position()
self._data[r][c] = MPSWData(ascan_data, spectra_data)
self._widgets[r][c].set_image(ascan_data)
self._widgets[self._last_position[0]][self._last_position[1]].latest = False
self._widgets[r][c].latest = True
self._last_position = (r, c)
class MainWindow(QMainWindow):
def __init__(self, list=[]):
super().__init__()
self._image_list = list
self._image_index = 0
self.setWindowTitle("PyQt5 Matplotlib 2D Image Example")
self.plot = MPSW(self, rows=3, columns=5)
self.plot.save.connect(self.savedata)
self.setCentralWidget(self.plot)
self.timer = QTimer()
self.timer.timeout.connect(self.timeout)
self.timer.start(1000) # might not actually start until event loop starts
def savedata(self, r, c):
print("save data {:d},{:d}".format(r, c))
def timeout(self):
if len(self._image_list) > 0:
image = Image.open(self._image_list[self._image_index])
# convert image to numpy array
data = np.asarray(image)
self.plot.add_data(data, data.shape) # the data.shape is dummy - should be spectra_data that might be saved
self._image_index+=1
if self._image_index >= len(self._image_list):
self._image_index = 0
else:
print('no images')
import sys
if __name__ == "__main__":
# load image filenames
with open('images.txt', 'r') as f:
image_list = [line.rstrip() for line in f]
app = QApplication(sys.argv)
window = MainWindow(image_list)
window.show()
sys.exit(app.exec())