Coverage for /opt/conda/envs/apienv/lib/python3.10/site-packages/daiquiri/core/transform/simgui.py: 79%
180 statements
« prev ^ index » next coverage.py v7.6.4, created at 2024-11-14 02:13 +0000
« prev ^ index » next coverage.py v7.6.4, created at 2024-11-14 02:13 +0000
1"""Simulate a GUI based in a Canvas instance
2"""
4# TODO: this module should go somewhere else
6import numpy
7import matplotlib.pyplot as plt
8from matplotlib.path import Path
9import matplotlib.patches as patches
10from matplotlib.backend_bases import MouseButton
11from matplotlib.widgets import RadioButtons
14class SimGUI:
15 """Matplotlib based GUI simulator"""
17 def __init__(self, canvas):
18 self.canvas = canvas
20 def start(self):
21 fig, ax = plt.subplots()
22 self._draw_info = {
23 "fig": fig,
24 "ax": ax,
25 "limits": "canvas",
26 "fixsample": True,
27 "move": True,
28 "zoom": 1.2,
29 "skipevent": False,
30 }
31 fig.canvas.mpl_connect("button_press_event", self._button_press_event)
32 fig.canvas.mpl_connect("button_release_event", self._button_release_event)
33 fig.canvas.mpl_connect("scroll_event", self._scroll_event)
35 axcolor = "lightgoldenrodyellow"
36 rax = plt.axes([0, 0.7, 0.10, 0.15], facecolor=axcolor)
37 labels = self.canvas.vlm_to_vi.zoomlevels
38 radio1 = RadioButtons(rax, labels, active=labels.index(self.canvas.zoomlevel))
39 radio1.on_clicked(self._zoom_event)
41 rax = plt.axes([0, 0.5, 0.10, 0.15], facecolor=axcolor)
42 labels = ["image", "canvas"]
43 radio2 = RadioButtons(
44 rax, labels, active=labels.index(self._draw_info["limits"])
45 )
46 radio2.on_clicked(self._limits_event)
48 rax = plt.axes([0, 0.3, 0.10, 0.15], facecolor=axcolor)
49 labels = ["vlm", "sample"]
50 radio3 = RadioButtons(rax, labels, active=int(self._draw_info["fixsample"]))
51 radio3.on_clicked(self._fix_event)
53 rax = plt.axes([0, 0.1, 0.10, 0.15], facecolor=axcolor)
54 labels = ["scan", "move"]
55 radio4 = RadioButtons(rax, labels, active=int(self._draw_info["move"]))
56 radio4.on_clicked(self._action_event)
58 self._draw()
59 plt.show()
61 def _button_press_event(self, event):
62 if event.button == MouseButton.LEFT:
63 self._draw_info["start"] = [event.xdata, event.ydata]
65 def _button_release_event(self, event):
66 if self._draw_info["skipevent"]:
67 self._draw_info["skipevent"] = False
68 return
69 end = [event.xdata, event.ydata]
70 if event.button == MouseButton.LEFT:
71 if self._draw_info["move"]:
72 self.canvas.move(end)
73 else:
74 pts = [self._draw_info["start"], end]
75 print(f"Scan coordinates: {self.canvas.canvas_to_motor(pts)}")
76 elif event.button == MouseButton.RIGHT:
77 self.canvas.beamposition = end
78 else:
79 return
80 self._draw()
82 def _scroll_event(self, event):
83 if event.button == "up":
84 inc = 0.1
85 else:
86 inc = -0.1
87 self._draw_info["zoom"] = max(1.0, self._draw_info["zoom"] + inc)
88 self._draw()
90 def _zoom_event(self, label):
91 self.canvas.zoomlevel = label
92 self._draw()
93 self._draw_info["skipevent"] = True
95 def _limits_event(self, label):
96 self._draw_info["limits"] = label
97 self._draw()
98 self._draw_info["skipevent"] = True
100 def _fix_event(self, label):
101 self._draw_info["fixsample"] = label == "sample"
102 self._draw_info.pop("fixed", None)
103 self._draw()
104 self._draw_info["skipevent"] = True
106 def _action_event(self, label):
107 self._draw_info["move"] = label == "move"
108 self._draw_info["skipevent"] = True
110 def _draw(self):
111 # Borders
112 reach_polyline = self.canvas.reach_polyline
113 fine_polyline = self.canvas.fine_polyline
114 coarse_polyline = self.canvas.coarse_polyline
115 vi_polyline = self.canvas.vi_polyline
117 # Markers
118 beamposition = self.canvas.beamposition
119 sampleposition = self.canvas.sampleposition
120 focalpoint = self.canvas.focalpoint
121 vi_center = self.canvas.vi_center
123 # Part of the sample that the VLM is seeing
124 testdata = self._draw_info.get("testdata")
125 if testdata is None:
126 xmin, ymin = reach_polyline.min(axis=0)
127 xmax, ymax = reach_polyline.max(axis=0)
128 testdata = self._testimagedata(xmin, xmax, ymin, ymax, 200)
129 self._draw_info["testdata"] = testdata
130 (left, bottom), (right, top) = self.canvas.vi_corners
131 extent = (left, right, bottom, top)
132 imshape = tuple(self.canvas.vlm_to_vi.imageshape.tolist())
133 x = numpy.linspace(left, right, imshape[0])
134 y = numpy.linspace(bottom, top, imshape[1])
135 vlmimage = self._testimage(x, y, testdata)
137 # Draw objects
138 fig = self._draw_info["fig"]
139 ax = self._draw_info["ax"]
140 ax.clear()
142 ax.imshow(vlmimage, extent=extent, interpolation="nearest", origin="lower")
143 self._draw_polyline(ax, vi_polyline, edgecolor="black", fill=False, label="VLM")
144 ax.scatter(*sampleposition, s=100, marker="x", color="black", label="Sample")
145 ax.scatter(*focalpoint, s=100, marker="o", color="red", label="VLM foc")
146 ax.scatter(*vi_center, s=100, marker="^", color="blue", label="VLM cen")
147 self._draw_polyline(
148 ax, reach_polyline, edgecolor="magenta", fill=False, label="Reach"
149 )
150 self._draw_polyline(
151 ax, coarse_polyline, edgecolor="red", fill=False, label="Coarse"
152 )
153 self._draw_polyline(
154 ax, fine_polyline, edgecolor="orange", fill=False, label="Fine"
155 )
156 ax.scatter(
157 *beamposition, s=100, marker="+", linewidths=20, color="black", label="Beam"
158 )
160 # Axes limits
161 limits = self._draw_info["limits"]
162 if limits == "image":
163 fixed = vi_polyline
164 else:
165 fixed = reach_polyline
166 if self._draw_info["fixsample"]:
167 fixed = self._draw_info.setdefault("fixed", fixed)
168 xmin, ymin = fixed.min(axis=0)
169 xmax, ymax = fixed.max(axis=0)
170 dx = (xmax - xmin) * self._draw_info["zoom"]
171 dy = (ymax - ymin) * self._draw_info["zoom"]
172 xcen = (xmin + xmax) / 2.0
173 ycen = (ymin + ymax) / 2.0
174 ax.set_xlim(xcen - dx / 2, xcen + dx / 2)
175 ax.set_ylim(ycen - dy / 2, ycen + dy / 2)
177 # Axes labels, legend and title
178 ax.set_xlabel(f"Y({self.canvas.motors_to_world.units})")
179 ax.set_ylabel(f"Z({self.canvas.motors_to_world.units})")
180 ax.set_aspect("equal", adjustable="box")
181 info = self.canvas.current_motor_positions_dict
182 title = ", ".join(f"{k}={v}" for k, v in info.items())
183 title += "\n"
184 align_info = self.canvas.align_info
185 title += ", ".join(f"{k}: {v}" for k, v in align_info.items())
186 ax.set_title(title)
187 ax.legend(loc="upper left", bbox_to_anchor=(1, 1))
188 fig.canvas.draw()
190 @staticmethod
191 def _draw_polyline(ax, verts, **kwargs):
192 codes = [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY]
193 path = Path(verts, codes)
194 patch = patches.PathPatch(path, **kwargs)
195 ax.add_patch(patch)
197 def _testimage(self, x, y, data):
198 x, y = numpy.meshgrid(x, y)
199 ret = numpy.zeros(x.shape, dtype=float)
200 for x0, y0, sx, sy, angle, A in data:
201 particle = self._testimage_particle(x, y, x0, y0, sx, sy, angle, A)
202 ret = numpy.maximum(particle, ret)
203 return ret
205 @staticmethod
206 def _testimage_particle(x, y, x0, y0, sx, sy, angle, A):
207 angle = numpy.radians(angle)
208 cosa = numpy.cos(angle)
209 sina = numpy.sin(angle)
210 sin2a = numpy.sin(2 * angle)
211 varx = sx**2
212 vary = sy**2
213 a = cosa**2 / (2 * varx) + sina**2 / (2 * vary)
214 b = -sin2a / (4 * varx) + sin2a / (4 * vary)
215 c = sina**2 / (2 * varx) + cosa**2 / (2 * vary)
216 num = a * (x - x0) ** 2 - 2 * b * (x - x0) * (y - y0) + c * (y - y0) ** 2
217 return A * numpy.exp(-num)
219 def _testimagedata(self, xmin, xmax, ymin, ymax, npeaks):
220 # numpy.random.seed(1)
221 area = (xmax - xmin) * (ymax - ymin)
222 particlearea = area / (npeaks * 10.0)
223 sxy = numpy.sqrt(particlearea)
224 x0, y0, npeaks = self._testimage_positions(xmin, xmax, ymin, ymax, npeaks)
225 sx = numpy.random.uniform(sxy * 0.8, sxy * 1.2, npeaks)
226 sy = numpy.random.uniform(sxy * 0.8, sxy * 1.2, npeaks)
227 rho = numpy.random.uniform(0, 0.2, npeaks)
228 A = numpy.random.uniform(1, 5, npeaks)
229 return list(zip(x0, y0, sx, sy, rho, A))
231 @staticmethod
232 def _testimage_positions(a, b, c, d, npeaks):
233 x = numpy.random.uniform(a, b, npeaks)
234 y = numpy.random.uniform(c, d, npeaks)
235 dr = max(b - a, d - c) * 0.1
236 xm = (a + b) / 2.0
237 ym = (c + d) / 2.0
238 r = ((x - xm) ** 2 + (y - ym) ** 2) ** 0.5
239 ind = r > dr
240 x = x[ind]
241 y = y[ind]
242 npeaks = sum(ind)
243 return x, y, npeaks