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

1"""Simulate a GUI based in a Canvas instance 

2""" 

3 

4# TODO: this module should go somewhere else 

5 

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 

12 

13 

14class SimGUI: 

15 """Matplotlib based GUI simulator""" 

16 

17 def __init__(self, canvas): 

18 self.canvas = canvas 

19 

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) 

34 

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) 

40 

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) 

47 

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) 

52 

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) 

57 

58 self._draw() 

59 plt.show() 

60 

61 def _button_press_event(self, event): 

62 if event.button == MouseButton.LEFT: 

63 self._draw_info["start"] = [event.xdata, event.ydata] 

64 

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() 

81 

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() 

89 

90 def _zoom_event(self, label): 

91 self.canvas.zoomlevel = label 

92 self._draw() 

93 self._draw_info["skipevent"] = True 

94 

95 def _limits_event(self, label): 

96 self._draw_info["limits"] = label 

97 self._draw() 

98 self._draw_info["skipevent"] = True 

99 

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 

105 

106 def _action_event(self, label): 

107 self._draw_info["move"] = label == "move" 

108 self._draw_info["skipevent"] = True 

109 

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 

116 

117 # Markers 

118 beamposition = self.canvas.beamposition 

119 sampleposition = self.canvas.sampleposition 

120 focalpoint = self.canvas.focalpoint 

121 vi_center = self.canvas.vi_center 

122 

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) 

136 

137 # Draw objects 

138 fig = self._draw_info["fig"] 

139 ax = self._draw_info["ax"] 

140 ax.clear() 

141 

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 ) 

159 

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) 

176 

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() 

189 

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) 

196 

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 

204 

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) 

218 

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)) 

230 

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