class _PhaseViewer(wx.Frame): """This is a window for selecting the ROI for interferometry.""" def __init__(self, parent, input_image, image_ft, RMS_error): super().__init__(parent, title="Phase View") self._panel = wx.Panel(self) wx_img_real = _np_grey_img_to_wx_image(input_image) wx_img_fourier = _np_grey_img_to_wx_image(image_ft) self._canvas = FloatCanvas(self._panel, size=wx_img_real.GetSize()) self._real_bmp = self._canvas.AddBitmap(wx_img_real, (0, 0), Position="cc") self._fourier_bmp = self._canvas.AddBitmap(wx_img_fourier, (0, 0), Position="cc") # By default, show real and hide the fourier transform. self._fourier_bmp.Hide() save_btn = wx.ToggleButton(self._panel, label="Show Fourier") save_btn.Bind(wx.EVT_TOGGLEBUTTON, self.OnToggleFourier) rms_txt = wx.StaticText(self._panel, label="RMS difference: %.05f" % (RMS_error)) panel_sizer = wx.BoxSizer(wx.VERTICAL) panel_sizer.Add(self._canvas) bottom_sizer = wx.BoxSizer(wx.HORIZONTAL) bottom_sizer.Add(save_btn, wx.SizerFlags().Center().Border()) bottom_sizer.Add(rms_txt, wx.SizerFlags().Center().Border()) panel_sizer.Add(bottom_sizer) self._panel.SetSizer(panel_sizer) frame_sizer = wx.BoxSizer(wx.VERTICAL) frame_sizer.Add(self._panel) self.SetSizerAndFit(frame_sizer) def OnToggleFourier(self, event: wx.CommandEvent) -> None: show_fourier = event.IsChecked() # These bmp are wx.lib.floatcanvas.FCObjects.Bitmap and not # wx.Bitmap. Their Show method does not take show argument # and therefore we can't do `Show(show_fourier)`. if show_fourier: self._fourier_bmp.Show() self._real_bmp.Hide() else: self._real_bmp.Show() self._fourier_bmp.Hide() self._canvas.Draw(Force=True)
class _ROISelect(wx.Frame): """Display a window that allows the user to select a circular area. This is a window for selecting the ROI for interferometry. """ def __init__(self, parent, input_image: np.ndarray, initial_roi, scale_factor=1) -> None: super().__init__(parent, title="ROI selector") self._panel = wx.Panel(self) self._img = _np_grey_img_to_wx_image(input_image) self._scale_factor = scale_factor # What, if anything, is being dragged. # XXX: When we require Python 3.8, annotate better with # `typing.Literal[None, "xy", "r"]` self._dragging: typing.Optional[str] = None # Canvas self.canvas = FloatCanvas(self._panel, size=self._img.GetSize()) self.canvas.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouse) self.bitmap = self.canvas.AddBitmap(self._img, (0, 0), Position="cc") self.circle = self.canvas.AddCircle( self.canvas.PixelToWorld(initial_roi[:2]), initial_roi[2] * 2, LineColor="cyan", LineWidth=2, ) # Save button saveBtn = wx.Button(self._panel, label="Save ROI") saveBtn.Bind(wx.EVT_BUTTON, self.OnSave) panel_sizer = wx.BoxSizer(wx.VERTICAL) panel_sizer.Add(self.canvas) panel_sizer.Add(saveBtn, wx.SizerFlags().Border()) self._panel.SetSizer(panel_sizer) frame_sizer = wx.BoxSizer(wx.VERTICAL) frame_sizer.Add(self._panel) self.SetSizerAndFit(frame_sizer) @property def ROI(self): """Convert circle parameters to ROI x, y and radius""" roi_x, roi_y = self.canvas.WorldToPixel(self.circle.XY) roi_r = max(self.circle.WH) return (roi_x, roi_y, roi_r) def OnSave(self, event: wx.CommandEvent) -> None: del event roi = [x * self._scale_factor for x in self.ROI] userConfig.setValue("dm_circleParams", (roi[1], roi[0], roi[2])) def MoveCircle(self, pos: wx.Point, r) -> None: """Set position and radius of circle with bounds checks.""" x, y = pos _x, _y, _r = self.ROI xmax, ymax = self._img.GetSize() if r == _r: x_bounded = min(max(r, x), xmax - r) y_bounded = min(max(r, y), ymax - r) r_bounded = r else: r_bounded = max(_ROI_MIN_RADIUS, min(xmax - x, x, ymax - y, y, r)) x_bounded = min(max(r_bounded, x), xmax - r_bounded) y_bounded = min(max(r_bounded, y), ymax - r_bounded) self.circle.SetPoint(self.canvas.PixelToWorld((x_bounded, y_bounded))) self.circle.SetDiameter(2 * r_bounded) if any((x_bounded != x, y_bounded != y, r_bounded != r)): self.circle.SetColor("magenta") else: self.circle.SetColor("cyan") def OnMouse(self, event: wx.MouseEvent) -> None: pos = event.GetPosition() x, y, r = self.ROI if event.LeftDClick(): # Set circle centre self.MoveCircle(pos, r) elif event.Dragging(): # Drag circle centre or radius drag_r = np.sqrt((x - pos[0])**2 + (y - pos[1])**2) if self._dragging is None: # determine what to drag if drag_r < 0.5 * r: # closer to center self._dragging = "xy" else: # closer to edge self._dragging = "r" elif self._dragging == "r": # Drag circle radius self.MoveCircle((x, y), drag_r) elif self._dragging == "xy": # Drag circle centre self.MoveCircle(pos, r) if not event.Dragging(): # Stop dragging self._dragging = None self.circle.SetColor("cyan") self.canvas.Draw(Force=True)
class Designer(MyFrame): # config # builder = None brick_size = (0.2, 0.05) # bricks # cur_index = -1 bricks = [] bricks_count = 0 # canvas # canvas_scale = 500.0 canvas_offset = (0, -0.4) def __init__(self, builder=None, brick_size=(0.2, 0.05)): super(Designer, self).__init__(None) # config # self.builder = builder self.brick_size = brick_size # bind # self.Bind(wx.EVT_LISTBOX, self.on_listBox, self.listBox) self.Bind(wx.EVT_BUTTON, self.add_brick, self.button_add) self.Bind(wx.EVT_BUTTON, self.remove_brick, self.button_remove) self.Bind(wx.EVT_BUTTON, self.rotate_brick, self.button_rotate) self.Bind(wx.EVT_BUTTON, self.run, self.button_run) self.Bind(wx.EVT_BUTTON, self.load, self.button_load) self.Bind(wx.EVT_BUTTON, self.save, self.button_save) self.Bind(wx.EVT_TEXT_ENTER, self.update_brick, self.textBox_name) self.Bind(wx.EVT_TEXT_ENTER, self.update_input, self.textBox_X) self.Bind(wx.EVT_TEXT_ENTER, self.update_input, self.textBox_Y) self.Bind(wx.EVT_TEXT_ENTER, self.update_input, self.textBox_R) # Publisher().subscribe(self.on_msg, "update") # canvas # self.canvas = FloatCanvas(self.draw_panel, size=(600, 500), ProjectionFun=None, BackgroundColor="White") self.canvas.Bind(wx.EVT_MOUSE_EVENTS, self.on_mouse) self.refresh() def on_listBox(self, event): self.cur_index = event.GetEventObject().GetSelection() self.refresh() # def on_msg(self): # def is_selected(self): if 0 <= self.cur_index < len(self.bricks): return True else: self.cur_index = -1 return False # ---- bricks ---- # def add_brick(self, event): name = 'brick' + str(self.bricks_count) new_brick = Brick(name, 0, self.brick_size[1] / 2) self.bricks.append(new_brick) self.cur_index = len(self.bricks) - 1 self.bricks_count += 1 self.refresh() def remove_brick(self, event): if self.is_selected(): del self.bricks[self.cur_index] if self.cur_index >= len(self.bricks): self.cur_index = len(self.bricks) - 1 self.refresh() def rotate_brick(self, event): if self.is_selected(): d = pi / 4 cur = self.bricks[self.cur_index] cur.r = ((int(cur.r / d) + 1) * d) % pi self.refresh() def update_brick(self, event): if self.is_selected(): cur = self.bricks[self.cur_index] new_name = self.textBox_name.GetValue() if cur.name != new_name: for i in range(0, len(self.bricks), 1): if self.bricks[i].name == new_name: return cur.name = new_name cur.x = float(self.textBox_X.GetValue()) cur.y = float(self.textBox_Y.GetValue()) cur.r = float(self.textBox_R.GetValue()) self.refresh() def update_input(self, event): x = self.textBox_X.GetValue() y = self.textBox_Y.GetValue() r = self.textBox_R.GetValue() if self.is_selected( ) and x != '' and y != '' and r != '' and self.down_pos is None: cur = self.bricks[self.cur_index] cur.x = float(x) cur.y = float(y) cur.r = float(r) self.refresh_canvas() # ---- refresh ---- # def refresh(self): self.refresh_list() self.refresh_prop() self.refresh_canvas() def refresh_list(self): self.listBox.Clear() for brick in self.bricks: self.listBox.Append(brick.name) if self.is_selected(): self.listBox.Select(self.cur_index) def refresh_prop(self): if self.is_selected(): cur = self.bricks[self.cur_index] self.textBox_name.SetValue(str(cur.name)) self.textBox_X.SetValue(str(cur.x)) self.textBox_Y.SetValue(str(cur.y)) self.textBox_R.SetValue(str(cur.r)) # self.prop_panel.Show() else: self.textBox_name.Clear() self.textBox_X.Clear() self.textBox_Y.Clear() self.textBox_R.Clear() # self.prop_panel.Hide() # ---- canvas ---- # def refresh_canvas(self): self.canvas.ClearAll() self.draw_lines() for brick in self.bricks: if not self.is_selected() or brick != self.bricks[self.cur_index]: self.canvas.AddObject(self.get_box(brick, "Yellow")) if self.is_selected(): self.canvas.AddObject( self.get_box(self.bricks[self.cur_index], "Yellow", "Orange")) self.canvas.Draw() def draw_lines(self): self.canvas.AddObject( Line([self.to_offset( (1, 0)), self.to_offset((-1, 0))], LineWidth=5)) self.canvas.AddObject( Line([self.to_offset( (0, 1)), self.to_offset((0, -1))], LineWidth=2, LineStyle="ShortDash")) for x in range(-5, 6, 1): self.canvas.AddObject( Line([ self.to_offset((x * 0.1, 1)), self.to_offset((x * 0.1, -1)) ], LineStyle="Dot", LineWidth=0.1)) for y in range(-5, 10, 1): self.canvas.AddObject( Line([ self.to_offset((1, y * 0.1)), self.to_offset((-1, y * 0.1)) ], LineStyle="Dot", LineWidth=0.1)) def get_box(self, brick, FillColor, LineColor="Black"): brick = ((brick.x + self.canvas_offset[0]) * self.canvas_scale, (brick.y + self.canvas_offset[1]) * self.canvas_scale, brick.r) brick_size = (self.brick_size[0] * self.canvas_scale, self.brick_size[1] * self.canvas_scale) p1 = to_points(brick, (brick_size[0] / 2, brick_size[1] / 2)) p2 = to_points(brick, (brick_size[0] / 2, -brick_size[1] / 2)) p3 = to_points(brick, (-brick_size[0] / 2, -brick_size[1] / 2)) p4 = to_points(brick, (-brick_size[0] / 2, brick_size[1] / 2)) return Polygon([p1, p2, p3, p4], LineWidth=2, FillColor=FillColor, LineColor=LineColor) def to_offset(self, point): return ((point[0] + self.canvas_offset[0]) * self.canvas_scale, (point[1] + self.canvas_offset[1]) * self.canvas_scale) def from_offset(self, point): return ((point[0] / self.canvas_scale - self.canvas_offset[0]), (point[1] / self.canvas_scale - self.canvas_offset[1])) # ---- run ---- # def run(self, event): targets = self.get_targets() self.builder.set_targets(targets) self.builder.show_target() rospy.sleep(2) self.builder.show_source() thread.start_new_thread(self.builder.build, ()) def get_targets(self): targets = [] for brick in self.bricks: transform = build_frame( (brick.x, 0, brick.y), (-pi / 2, 0, 0)) * build_frame(rpy=(0, 0, brick.r)) targets.append(transform) return targets # ----- drag ---- # down_pos = None pre_pos = None def on_mouse(self, event): if not self.is_selected(): return brick = self.bricks[self.cur_index] cur_pos = self.from_offset(event.GetPosition()) if event.ButtonDown(): self.down_pos = cur_pos self.pre_pos = (brick.x, brick.y) elif event.Dragging(): if self.down_pos is not None: pos = (cur_pos[0] - self.down_pos[0], cur_pos[1] - self.down_pos[1]) brick.x = self.pre_pos[0] + pos[0] brick.y = self.pre_pos[1] - pos[1] if self.is_aligned.GetValue(): align = self.brick_size[1] / 2 brick.x = int(brick.x / align) * align brick.y = int(brick.y / align) * align self.refresh() elif event.ButtonUp(): self.down_pos = None self.pre_pos = None # ---- file ---- # def load(self, event): dlg = wx.FileDialog(self, "加载图纸", defaultDir=os.getcwd(), wildcard='图纸文件 (*.dsg)|*.dsg|全部文件|*', style=wx.OPEN | wx.FILE_MUST_EXIST) if dlg.ShowModal() == wx.ID_OK: try: with open(dlg.GetPath(), 'r') as design_file: bricks = [] for target in json.load(design_file): bricks.append( Brick(target['name'], target['x'], target['y'], target['r'])) self.bricks = bricks self.cur_index = -1 self.refresh() except BaseException, e: error_dlg = wx.MessageDialog(None, "无法识别的图纸文件!\n错误详情: " + e.message, "加载失败", wx.ICON_ERROR) error_dlg.ShowModal() error_dlg.Destroy() dlg.Destroy()