def diagonal(rect: Rectangle, to: tuple, step: float = .1, interval: float = .001): end = to[0] if to[0] < rect.get_x(): step *= -1 end += step else: end += step x_range = np.arange(rect.get_x(), end, step) slope = (to[1] - rect.get_y()) / (to[0] - rect.get_x()) start_y = rect.get_y() start_x = rect.get_x() figure = rect.figure for x in x_range: rect.set_xy((x, slope * (x - start_x) + start_y)) figure.canvas.draw() figure.canvas.flush_events() plt.pause(interval)
class click_yrange: '''An interactive yrange selector. Given an axis and a starting y0 location, draw a full-width rectange that follows the mouise. Similar to click_window, but more appropriate for selecting out a y-range.''' def __init__(self, ax, y0): self.ax = ax self.y0 = y0 x0,x1 = ax.get_xbound() self.rect = Rectangle((x0,y0), width=(x1-x0), height=0, alpha=0.1) ax.add_artist(self.rect) def connect(self): self.cidmotion = self.rect.figure.canvas.mpl_connect( 'motion_notify_event', self.on_motion) def on_motion(self, event): # Have we left the axes? if event.inaxes != self.rect.axes: return self.rect.set_height(event.ydata - self.y0) self.ax.figure.canvas.draw() def close(self): self.rect.figure.canvas.mpl_disconnect(self.cidmotion) self.rect.remove() self.ax.figure.canvas.draw() return(self.y0, self.rect.get_y()+self.rect.get_height())
class click_yrange: '''An interactive yrange selector. Given an axis and a starting y0 location, draw a full-width rectange that follows the mouise. Similar to click_window, but more appropriate for selecting out a y-range.''' def __init__(self, ax, y0): self.ax = ax self.y0 = y0 x0, x1 = ax.get_xbound() self.rect = Rectangle((x0, y0), width=(x1 - x0), height=0, alpha=0.1) ax.add_artist(self.rect) def connect(self): self.cidmotion = self.rect.figure.canvas.mpl_connect( 'motion_notify_event', self.on_motion) def on_motion(self, event): # Have we left the axes? if event.inaxes != self.rect.axes: return self.rect.set_height(event.ydata - self.y0) self.ax.figure.canvas.draw() def close(self): self.rect.figure.canvas.mpl_disconnect(self.cidmotion) self.rect.remove() self.ax.figure.canvas.draw() return (self.y0, self.rect.get_y() + self.rect.get_height())
def up_down(rect: Rectangle, to: tuple, step: float = .1, interval: float = .001): new_step = step if to[1] < rect.get_y(): new_step *= -1 figure = rect.figure up = rect.get_y() < to[1] while (rect.get_y() - to[1] <= step / 2) if up else ( rect.get_y() - to[1] >= step / 2): rect.set_y(rect.get_y() + new_step) figure.canvas.draw() figure.canvas.flush_events() plt.pause(interval)
def show_image(image, labels): rect = Rectangle((labels[0], labels[1]), labels[2] - labels[0], labels[3] - labels[1], edgecolor='r', fill=False) plt.imshow(image) gca = plt.gca() gca.add_patch(rect) return rect.get_x(), rect.get_y(), rect.get_width(), rect.get_height()
class ChipSelector(object): def __init__(self, image_name, chip, **kwargs): plt.ioff() self.chip = chip fig, ax = plt.subplots() im, extent = create_image_for_viewer(image_name, chip) plt.imshow(np.flipud(im), extent=extent) xy = (chip['coordinates']['xmin'], chip['coordinates']['ymin']) width = chip['coordinates']['xmax'] - chip['coordinates']['xmin'] height = chip['coordinates']['ymax'] - chip['coordinates']['ymin'] from matplotlib.patches import Rectangle self.__rect = Rectangle(xy, width, height, fill=False, color='r') ax.add_patch(self.__rect) drr = DraggableResizeableRectangle(self.__rect, fixed_aspect_ratio=False) drr.connect() plt.subplots_adjust(bottom=0.2) ax.axes.get_xaxis().set_visible(False) ax.axes.get_yaxis().set_visible(False) ax_reject = plt.axes([0.2, 0.05, 0.1, 0.075]) ax_accept = plt.axes([0.75, 0.05, 0.1, 0.075]) self._b_reject = Button(ax_reject, 'Reject') self._b_accept = Button(ax_accept, "Accept") self._b_reject.on_clicked(self.answer) self._b_accept.on_clicked(self.answer) plt.show() def answer(self, event): if event.inaxes.texts[0]._text == 'Accept': self.chip = {'name': self.chip['name'], 'coordinates': {'xmin': self.__rect.get_x(), 'ymin': self.__rect.get_y(), 'xmax': self.__rect.get_x()+self.__rect.get_width(), 'ymax': self.__rect.get_y()+self.__rect.get_height()} } else: self.chip = None plt.close()
def move_to(rect: Rectangle, to: tuple, step: float = .1, interval: float = .001, show_animation: bool = True): if show_animation: if abs(rect.get_x() - to[0]) < .001: if not abs(rect.get_y() - to[1]) < .001: up_down(rect, to, step, interval) else: return elif abs(rect.get_y() - to[1]) < .001: right_left(rect, to, step, interval) else: diagonal(rect, to, step, interval) figure = rect.figure rect.set_xy(to) figure.canvas.draw() figure.canvas.flush_events() plt.pause(interval)
def draw_label(self, bar: Rectangle, fmt: str) -> plt.Annotation: w, h = bar.get_width(), bar.get_height() x = bar.get_x() + w * 0.5 y = bar.get_y() + h label = self._ax.annotate(format(h, fmt), (x, y), xytext=(0.0, 4.0), textcoords='offset points', ha='center', va='bottom') label.draggable() label.set_rotation(self.label_rot) return label
class EditableRectangle: _angle = 0 def __init__(self, ax): self.ax = ax # Set up main rectangle self.rect = Rectangle((0, 0), 0, 0, visible=False, transform=None, picker=True) self.ax.add_patch(self.rect) # Set up anchors self.anchors = [] for i in range(len(RECTANGLE_ANCHOR_LOCS)): anchor = Rectangle((0, 0), ANCHOR_SIZE, ANCHOR_SIZE, visible=False, transform=None, facecolor='red', picker=True) self.anchors.append(anchor) self.ax.add_patch(anchor) self.press = None self.mode = None self.connect() def connect(self): self.cid_press = self.ax.figure.canvas.mpl_connect('button_press_event', self.on_press) self.cid_release = self.ax.figure.canvas.mpl_connect('button_release_event', self.on_release) self.cid_motion = self.ax.figure.canvas.mpl_connect('motion_notify_event', self.on_motion) self.cid_pick = self.ax.figure.canvas.mpl_connect('pick_event', self.on_pick) def on_pick(self, event): if event.artist in self.anchors: if event.mouseevent.key == 'r': self.mode = 'anchor-rotate' self.center_x = self.x0 + self.width * 0.5 self.center_y = self.y0 + self.height * 0.5 self.angle_start = self.angle self.angle_drag_start = np.degrees(np.arctan2(event.mouseevent.y - self.center_y, event.mouseevent.x - self.center_x)) print(self.angle_start) else: self.mode = 'anchor-drag' anchor_index = self.anchors.index(event.artist) self.active_anchor_index = anchor_index self.press = True elif event.artist is self.rect: self.mode = 'rectangle-drag' self.drag_start_x0 = self.x0 self.drag_start_y0 = self.y0 self.drag_start_x = event.mouseevent.x self.drag_start_y = event.mouseevent.y self.press = True def on_press(self, event): if event.inaxes != self.ax: return if self.mode == 'create': self.x0 = event.x self.y0 = event.y self.rect.set_visible(True) self.press = True # contains, attrd = self.rect.contains(event) # if not contains: return # print('event contains', self.rect.xy) # x0, y0 = self.rect.xy # self.press = x0, y0, event.x, event.y @property def angle(self): return self._angle @angle.setter def angle(self, value): self._angle = value self.rect.angle = value self.rect._update_patch_transform() @property def x0(self): return self.rect.get_x() @x0.setter def x0(self, value): self.rect.set_x(value) @property def y0(self): return self.rect.get_y() @y0.setter def y0(self, value): self.rect.set_y(value) @property def width(self): return self.rect.get_width() @width.setter def width(self, value): self.rect.set_width(value) @property def height(self): return self.rect.get_height() @height.setter def height(self, value): self.rect.set_height(value) def on_motion(self, event): if self.press is None: return if event.inaxes != self.ax: return if self.mode == 'create': self.width = event.x - self.x0 self.height = event.y - self.y0 self.rect.figure.canvas.draw() elif self.mode == 'rectangle-drag': self.x0 = self.drag_start_x0 + event.x - self.drag_start_x self.y0 = self.drag_start_y0 + event.y - self.drag_start_y self.update_anchors() self.rect.figure.canvas.draw() elif self.mode == 'anchor-drag': px, py = RECTANGLE_ANCHOR_LOCS[self.active_anchor_index] if px == -1: self.x0, self.width = event.x, self.x0 + self.width - event.x elif px == 1: self.width = event.x - self.x0 if py == -1: self.y0, self.height = event.y, self.y0 + self.height - event.y elif py == 1: self.height = event.y - self.y0 self.update_anchors() self.rect.figure.canvas.draw() elif self.mode == 'anchor-rotate': angle_current = np.degrees(np.arctan2(event.y - self.center_y, event.x - self.center_x)) self.angle = self.angle_start + (angle_current - self.angle_drag_start) self.update_anchors() self.rect.figure.canvas.draw() def on_release(self, event): if self.mode == 'create': self.update_anchors() self.set_anchor_visibility(True) self.press = None self.mode = None self.rect.figure.canvas.draw() def set_anchor_visibility(self, visible): for anchor in self.anchors: anchor.set_visible(visible) def update_anchors(self): for anchor, (dx, dy) in zip(self.anchors, RECTANGLE_ANCHOR_LOCS): xc = self.x0 + 0.5 * self.width yc = self.y0 + 0.5 * self.height dx = 0.5 * (dx * self.width - ANCHOR_SIZE) dy = 0.5 * (dy * self.height - ANCHOR_SIZE) dxr = dx * np.cos(np.radians(self.angle)) - dy * np.sin(np.radians(self.angle)) dyr = dx * np.sin(np.radians(self.angle)) + dy * np.cos(np.radians(self.angle)) anchor.set_xy((xc + dxr, yc + dyr)) def disconnect(self): self.ax.figure.canvas.mpl_disconnect(self.cid_press) self.ax.figure.canvas.mpl_disconnect(self.cid_release) self.ax.figure.canvas.mpl_disconnect(self.cid_motion)
def show_image(im: Union[np.ndarray, Tensor], axis: plt.Axes = None, fig: plt.Figure = None, title: Optional[str] = None, color_map: str = "inferno", stack_depth: int = 0) -> Optional[plt.Figure]: """Plots a given image onto an axis. The repeated invocation of this function will cause figure plot overlap. If `im` is 2D and the length of second dimension are 4 or 5, it will be viewed as bounding box data (x0, y0, w, h, <label>). ```python boxes = np.array([[0, 0, 10, 20, "apple"], [10, 20, 30, 50, "dog"], [40, 70, 200, 200, "cat"], [0, 0, 0, 0, "not_shown"], [0, 0, -10, -20, "not_shown2"]]) img = np.zeros((150, 150)) fig, axis = plt.subplots(1, 1) fe.util.show_image(img, fig=fig, axis=axis) # need to plot image first fe.util.show_image(boxes, fig=fig, axis=axis) ``` Users can also directly plot text ```python fig, axis = plt.subplots(1, 1) fe.util.show_image("apple", fig=fig, axis=axis) ``` Args: axis: The matplotlib axis to plot on, or None for a new plot. fig: A reference to the figure to plot on, or None if new plot. im: The image (width X height) / bounding box / text to display. title: A title for the image. color_map: Which colormap to use for greyscale images. stack_depth: Multiple images can be drawn onto the same axis. When stack depth is greater than zero, the `im` will be alpha blended on top of a given axis. Returns: plotted figure. It will be the same object as user have provided in the argument. """ if axis is None: fig, axis = plt.subplots(1, 1) axis.axis('off') # Compute width of axis for text font size bbox = axis.get_window_extent().transformed(fig.dpi_scale_trans.inverted()) width, height = bbox.width * fig.dpi, bbox.height * fig.dpi space = min(width, height) if not hasattr(im, 'shape') or len(im.shape) < 2: # text data im = to_number(im) if hasattr(im, 'shape') and len(im.shape) == 1: im = im[0] im = im.item() if isinstance(im, bytes): im = im.decode('utf8') text = "{}".format(im) axis.text(0.5, 0.5, im, ha='center', transform=axis.transAxes, va='center', wrap=False, family='monospace', fontsize=min(45, space // len(text))) elif len(im.shape) == 2 and (im.shape[1] == 4 or im.shape[1] == 5): # Bounding Box Data. Should be (x0, y0, w, h, <label>) boxes = [] im = to_number(im) color = ["m", "r", "c", "g", "y", "b"][stack_depth % 6] for box in im: # Unpack the box, which may or may not have a label x0 = float(box[0]) y0 = float(box[1]) width = float(box[2]) height = float(box[3]) label = None if len(box) < 5 else str(box[4]) # Don't draw empty boxes, or invalid box if width <= 0 or height <= 0: continue r = Rectangle((x0, y0), width=width, height=height, fill=False, edgecolor=color, linewidth=3) boxes.append(r) if label: axis.text(r.get_x() + 3, r.get_y() + 3, label, ha='left', va='top', color=color, fontsize=max(8, min(14, width // len(label))), fontweight='bold', family='monospace') pc = PatchCollection(boxes, match_original=True) axis.add_collection(pc) else: if isinstance(im, torch.Tensor) and len(im.shape) > 2: # Move channel first to channel last channels = list(range(len(im.shape))) channels.append(channels.pop(0)) im = im.permute(*channels) # image data im = to_number(im) im_max = np.max(im) im_min = np.min(im) if np.issubdtype(im.dtype, np.integer): # im is already in int format im = im.astype(np.uint8) elif 0 <= im_min <= im_max <= 1: # im is [0,1] im = (im * 255).astype(np.uint8) elif -0.5 <= im_min < 0 < im_max <= 0.5: # im is [-0.5, 0.5] im = ((im + 0.5) * 255).astype(np.uint8) elif -1 <= im_min < 0 < im_max <= 1: # im is [-1, 1] im = ((im + 1) * 127.5).astype(np.uint8) else: # im is in some arbitrary range, probably due to the Normalize Op ma = abs( np.max(im, axis=tuple([i for i in range(len(im.shape) - 1)]) if len(im.shape) > 2 else None)) mi = abs( np.min(im, axis=tuple([i for i in range(len(im.shape) - 1)]) if len(im.shape) > 2 else None)) im = (((im + mi) / (ma + mi)) * 255).astype(np.uint8) # matplotlib doesn't support (x,y,1) images, so convert them to (x,y) if len(im.shape) == 3 and im.shape[2] == 1: im = np.reshape(im, (im.shape[0], im.shape[1])) alpha = 1 if stack_depth == 0 else 0.3 if len(im.shape) == 2: axis.imshow(im, cmap=plt.get_cmap(name=color_map), alpha=alpha) else: axis.imshow(im, alpha=alpha) if title is not None: axis.set_title(title, fontsize=min(20, 1 + width // len(title)), family='monospace') return fig
class StatsPanel(wx.Panel): def __init__(self, parent): wx.Panel.__init__(self, parent, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize) self.ztv_frame = self.GetTopLevelParent() self.ztv_frame.primary_image_panel.popup_menu_cursor_modes.append('Stats box') self.ztv_frame.primary_image_panel.available_cursor_modes['Stats box'] = { 'set-to-mode':self.set_cursor_to_stats_box_mode, 'on_button_press':self.on_button_press, 'on_motion':self.on_motion, 'on_button_release':self.on_button_release} self.textentry_font = wx.Font(14, wx.FONTFAMILY_MODERN, wx.NORMAL, wx.FONTWEIGHT_LIGHT, False) self.stats_info = None self.last_string_values = {'x0':'', 'xsize':'', 'x1':'', 'y0':'', 'ysize':'', 'y1':''} self.stats_rect = Rectangle((0, 0), 10, 10, color='magenta', fill=False, zorder=100) # use self.stats_rect as where we store/retrieve the x0,y0,x1,y1 # x0,y0,x1,y1 should be limited to range of 0 to shape-1 # but, stats should be calculated over e.g. x0:x1+1 (so that have pixels to do stats on even if x0==x1) # and, width/height of stats_rect should always be >= 0 values_sizer = wx.FlexGridSizer( 10, 5, 0, 0 ) values_sizer.SetFlexibleDirection( wx.BOTH ) values_sizer.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED ) values_sizer.AddSpacer((0,0), 0, wx.EXPAND) self.low_static_text = wx.StaticText( self, wx.ID_ANY, u"Low", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT ) self.low_static_text.Wrap( -1 ) values_sizer.Add(self.low_static_text, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 0) self.low_static_text = wx.StaticText( self, wx.ID_ANY, u"# pix", wx.DefaultPosition, wx.DefaultSize, 0 ) self.low_static_text.Wrap( -1 ) values_sizer.Add(self.low_static_text, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 0) self.high_static_text = wx.StaticText( self, wx.ID_ANY, u"High", wx.DefaultPosition, wx.DefaultSize, 0 ) self.high_static_text.Wrap( -1 ) values_sizer.Add(self.high_static_text, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 0) values_sizer.AddSpacer((0,0), 0, wx.EXPAND) self.x_static_text = wx.StaticText( self, wx.ID_ANY, u"x", wx.DefaultPosition, wx.DefaultSize, 0 ) self.x_static_text.Wrap( -1 ) values_sizer.Add(self.x_static_text, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 0) self.x0_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_PROCESS_ENTER) self.x0_textctrl.SetFont(self.textentry_font) values_sizer.Add(self.x0_textctrl, 0, wx.ALL, 2) self.x0_textctrl.Bind(wx.EVT_TEXT, self.x0_textctrl_changed) self.x0_textctrl.Bind(wx.EVT_TEXT_ENTER, self.x0_textctrl_entered) self.xsize_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_PROCESS_ENTER) self.xsize_textctrl.SetFont(self.textentry_font) values_sizer.Add(self.xsize_textctrl, 0, wx.ALL, 2) self.xsize_textctrl.Bind(wx.EVT_TEXT, self.xsize_textctrl_changed) self.xsize_textctrl.Bind(wx.EVT_TEXT_ENTER, self.xsize_textctrl_entered) self.x1_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_PROCESS_ENTER) self.x1_textctrl.SetFont(self.textentry_font) values_sizer.Add(self.x1_textctrl, 0, wx.ALL, 2) self.x1_textctrl.Bind(wx.EVT_TEXT, self.x1_textctrl_changed) self.x1_textctrl.Bind(wx.EVT_TEXT_ENTER, self.x1_textctrl_entered) self.npix_static_text = wx.StaticText( self, wx.ID_ANY, u"# pixels", wx.DefaultPosition, wx.DefaultSize, 0 ) self.npix_static_text.Wrap( -1 ) values_sizer.Add(self.npix_static_text, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_BOTTOM, 0) self.y_static_text = wx.StaticText( self, wx.ID_ANY, u"y", wx.DefaultPosition, wx.DefaultSize, 0 ) self.y_static_text.Wrap( -1 ) values_sizer.Add(self.y_static_text, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 0) self.y0_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_PROCESS_ENTER) self.y0_textctrl.SetFont(self.textentry_font) values_sizer.Add(self.y0_textctrl, 0, wx.ALL, 2) self.y0_textctrl.Bind(wx.EVT_TEXT, self.y0_textctrl_changed) self.y0_textctrl.Bind(wx.EVT_TEXT_ENTER, self.y0_textctrl_entered) self.ysize_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_PROCESS_ENTER) self.ysize_textctrl.SetFont(self.textentry_font) values_sizer.Add(self.ysize_textctrl, 0, wx.ALL, 2) self.ysize_textctrl.Bind(wx.EVT_TEXT, self.ysize_textctrl_changed) self.ysize_textctrl.Bind(wx.EVT_TEXT_ENTER, self.ysize_textctrl_entered) self.y1_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_PROCESS_ENTER) self.y1_textctrl.SetFont(self.textentry_font) values_sizer.Add(self.y1_textctrl, 0, wx.ALL, 2) self.y1_textctrl.Bind(wx.EVT_TEXT, self.y1_textctrl_changed) self.y1_textctrl.Bind(wx.EVT_TEXT_ENTER, self.y1_textctrl_entered) self.npix_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY) self.npix_textctrl.SetFont(self.textentry_font) self.npix_textctrl.SetBackgroundColour(textctrl_output_only_background_color) values_sizer.Add(self.npix_textctrl, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT, 0) values_sizer.AddSpacer((0,15), 0, wx.EXPAND) values_sizer.AddSpacer((0,0), 0, wx.EXPAND) values_sizer.AddSpacer((0,0), 0, wx.EXPAND) values_sizer.AddSpacer((0,0), 0, wx.EXPAND) values_sizer.AddSpacer((0,0), 0, wx.EXPAND) values_sizer.AddSpacer((0,0), 0, wx.EXPAND) self.median_static_text = wx.StaticText( self, wx.ID_ANY, u"Median", wx.DefaultPosition, wx.DefaultSize, 0 ) self.median_static_text.Wrap( -1 ) values_sizer.Add(self.median_static_text, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT, 0) self.median_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY) self.median_textctrl.SetFont(self.textentry_font) self.median_textctrl.SetBackgroundColour(textctrl_output_only_background_color) values_sizer.Add(self.median_textctrl, 0, wx.ALL, 2) self.robust_static_text = wx.StaticText( self, wx.ID_ANY, u"Robust", wx.DefaultPosition, wx.DefaultSize, 0 ) self.robust_static_text.Wrap( -1 ) values_sizer.Add(self.robust_static_text, 0, wx.ALL|wx.ALIGN_BOTTOM|wx.ALIGN_CENTER_HORIZONTAL, 0) values_sizer.AddSpacer((0,0), 0, wx.EXPAND) values_sizer.AddSpacer((0,0), 0, wx.EXPAND) self.mean_static_text = wx.StaticText( self, wx.ID_ANY, u"Mean", wx.DefaultPosition, wx.DefaultSize, 0 ) self.mean_static_text.Wrap( -1 ) values_sizer.Add(self.mean_static_text, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT, 0) self.mean_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY) self.mean_textctrl.SetFont(self.textentry_font) self.mean_textctrl.SetBackgroundColour(textctrl_output_only_background_color) values_sizer.Add(self.mean_textctrl, 0, wx.ALL, 2) self.robust_mean_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY) self.robust_mean_textctrl.SetFont(self.textentry_font) self.robust_mean_textctrl.SetBackgroundColour(textctrl_output_only_background_color) values_sizer.Add(self.robust_mean_textctrl, 0, wx.ALL, 2) values_sizer.AddSpacer((0,0), 0, wx.EXPAND) values_sizer.AddSpacer((0,0), 0, wx.EXPAND) self.stdev_static_text = wx.StaticText( self, wx.ID_ANY, u"Stdev", wx.DefaultPosition, wx.DefaultSize, 0 ) self.stdev_static_text.Wrap( -1 ) values_sizer.Add(self.stdev_static_text, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT, 0) self.stdev_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY) self.stdev_textctrl.SetFont(self.textentry_font) self.stdev_textctrl.SetBackgroundColour(textctrl_output_only_background_color) values_sizer.Add(self.stdev_textctrl, 0, wx.ALL, 2) self.robust_stdev_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY) self.robust_stdev_textctrl.SetFont(self.textentry_font) self.robust_stdev_textctrl.SetBackgroundColour(textctrl_output_only_background_color) values_sizer.Add(self.robust_stdev_textctrl, 0, wx.ALL, 2) values_sizer.AddSpacer((0,0), 0, wx.EXPAND) values_sizer.AddSpacer((0,15), 0, wx.EXPAND) values_sizer.AddSpacer((0,0), 0, wx.EXPAND) values_sizer.AddSpacer((0,0), 0, wx.EXPAND) values_sizer.AddSpacer((0,0), 0, wx.EXPAND) values_sizer.AddSpacer((0,0), 0, wx.EXPAND) values_sizer.AddSpacer((0,0), 0, wx.EXPAND) self.min_static_text = wx.StaticText( self, wx.ID_ANY, u"Min", wx.DefaultPosition, wx.DefaultSize, 0 ) self.min_static_text.Wrap( -1 ) values_sizer.Add(self.min_static_text, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT, 0) self.minval_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY) self.minval_textctrl.SetFont(self.textentry_font) self.minval_textctrl.SetBackgroundColour(textctrl_output_only_background_color) values_sizer.Add(self.minval_textctrl, 0, wx.ALL, 2) self.minpos_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY) self.minpos_textctrl.SetFont(self.textentry_font) self.minpos_textctrl.SetBackgroundColour(textctrl_output_only_background_color) values_sizer.Add(self.minpos_textctrl, 0, wx.ALL, 2) values_sizer.AddSpacer((0,0), 0, wx.EXPAND) values_sizer.AddSpacer((0,0), 0, wx.EXPAND) self.max_static_text = wx.StaticText( self, wx.ID_ANY, u"Max", wx.DefaultPosition, wx.DefaultSize, 0 ) self.max_static_text.Wrap( -1 ) values_sizer.Add(self.max_static_text, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT, 0) self.maxval_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY) self.maxval_textctrl.SetFont(self.textentry_font) self.maxval_textctrl.SetBackgroundColour(textctrl_output_only_background_color) values_sizer.Add(self.maxval_textctrl, 0, wx.ALL, 2) self.maxpos_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY) self.maxpos_textctrl.SetFont(self.textentry_font) self.maxpos_textctrl.SetBackgroundColour(textctrl_output_only_background_color) values_sizer.Add(self.maxpos_textctrl, 0, wx.ALL, 2) self.hideshow_button = wx.Button(self, wx.ID_ANY, u"Show", wx.DefaultPosition, wx.DefaultSize, 0) values_sizer.Add(self.hideshow_button, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, 2) self.hideshow_button.Bind(wx.EVT_BUTTON, self.on_hideshow_button) v_sizer1 = wx.BoxSizer(wx.VERTICAL) v_sizer1.AddStretchSpacer(1.0) v_sizer1.Add(values_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL) v_sizer1.AddStretchSpacer(1.0) self.SetSizer(v_sizer1) pub.subscribe(self.queue_update_stats, 'recalc-display-image-called') pub.subscribe(self._set_stats_box_parameters, 'set-stats-box-parameters') pub.subscribe(self.publish_stats_to_stream, 'get-stats-box-info') def publish_stats_to_stream(self, msg=None): wx.CallAfter(send_to_stream, sys.stdout, ('stats-box-info', self.stats_info)) def on_button_press(self, event): self.select_panel() self.update_stats_box(event.xdata, event.ydata, event.xdata, event.ydata) self.redraw_overplot_on_image() self.cursor_stats_box_x0, self.cursor_stats_box_y0 = event.xdata, event.ydata def on_motion(self, event): if event.button is not None: self.update_stats_box(self.cursor_stats_box_x0, self.cursor_stats_box_y0, event.xdata, event.ydata) self.redraw_overplot_on_image() self.update_stats() def on_button_release(self, event): self.redraw_overplot_on_image() self.update_stats() def set_cursor_to_stats_box_mode(self, event): self.ztv_frame.primary_image_panel.cursor_mode = 'Stats box' self.ztv_frame.stats_panel.select_panel() self.ztv_frame.stats_panel.highlight_panel() def queue_update_stats(self, msg=None): """ wrapper to call update_stats from CallAfter in order to make GUI as responsive as possible. """ wx.CallAfter(self.update_stats, msg=None) def _set_stats_box_parameters(self, msg): """ wrapper to update_stats_box to receive messages & translate them correctly """ x0,x1,y0,y1 = [None]*4 if msg['xrange'] is not None: x0,x1 = msg['xrange'] if msg['yrange'] is not None: y0,y1 = msg['yrange'] if msg['xrange'] is not None or msg['yrange'] is not None: self.update_stats_box(x0, y0, x1, y1) if msg['show_overplot'] is not None: if msg['show_overplot']: self.redraw_overplot_on_image() else: self.remove_overplot_on_image() send_to_stream(sys.stdout, ('set-stats-box-parameters-done', True)) def update_stats_box(self, x0=None, y0=None, x1=None, y1=None): if x0 is None: x0 = self.stats_rect.get_x() if y0 is None: y0 = self.stats_rect.get_y() if x1 is None: x1 = self.stats_rect.get_x() + self.stats_rect.get_width() if y1 is None: y1 = self.stats_rect.get_y() + self.stats_rect.get_height() if x0 > x1: x0, x1 = x1, x0 if y0 > y1: y0, y1 = y1, y0 x0 = min(max(0, x0), self.ztv_frame.display_image.shape[1] - 1) y0 = min(max(0, y0), self.ztv_frame.display_image.shape[0] - 1) x1 = min(max(0, x1), self.ztv_frame.display_image.shape[1] - 1) y1 = min(max(0, y1), self.ztv_frame.display_image.shape[0] - 1) self.stats_rect.set_bounds(x0, y0, x1 - x0, y1 - y0) if self.hideshow_button.GetLabel() == 'Hide': self.ztv_frame.primary_image_panel.figure.canvas.draw() self.update_stats() def remove_overplot_on_image(self): self.ztv_frame.primary_image_panel.remove_patch('stats_panel:stats_rect') self.hideshow_button.SetLabel(u"Show") def redraw_overplot_on_image(self): self.ztv_frame.primary_image_panel.add_patch('stats_panel:stats_rect', self.stats_rect) self.hideshow_button.SetLabel(u"Hide") def on_hideshow_button(self, evt): if self.hideshow_button.GetLabel() == 'Hide': self.remove_overplot_on_image() else: self.redraw_overplot_on_image() def get_x0y0x1y1_from_stats_rect(self): x0 = self.stats_rect.get_x() y0 = self.stats_rect.get_y() x1 = x0 + self.stats_rect.get_width() y1 = y0 + self.stats_rect.get_height() return x0,y0,x1,y1 def update_stats(self, msg=None): x0,y0,x1,y1 = self.get_x0y0x1y1_from_stats_rect() x0, y0 = int(np.round(x0)), int(np.round(y0)) x1, y1 = int(np.round(x1)), int(np.round(y1)) self.last_string_values['x0'] = str(int(x0)) self.x0_textctrl.SetValue(self.last_string_values['x0']) self.last_string_values['y0'] = str(int(y0)) self.y0_textctrl.SetValue(self.last_string_values['y0']) x_npix = int(x1 - x0 + 1) self.last_string_values['xsize'] = str(x_npix) self.xsize_textctrl.SetValue(self.last_string_values['xsize']) y_npix = int(y1 - y0 + 1) self.last_string_values['ysize'] = str(y_npix) self.ysize_textctrl.SetValue(self.last_string_values['ysize']) self.last_string_values['x1'] = str(int(x1)) self.x1_textctrl.SetValue(self.last_string_values['x1']) self.last_string_values['y1'] = str(int(y1)) self.y1_textctrl.SetValue(self.last_string_values['y1']) self.npix_textctrl.SetValue(str(x_npix * y_npix)) stats_data = self.ztv_frame.display_image[y0:y1+1, x0:x1+1] finite_mask = np.isfinite(stats_data) if finite_mask.max() is np.True_: stats_data_mean = stats_data[finite_mask].mean() stats_data_median = np.median(stats_data[finite_mask]) stats_data_std = stats_data[finite_mask].std() robust_mean, robust_median, robust_std = sigma_clipped_stats(stats_data[finite_mask]) else: stats_data_mean = np.nan stats_data_median = np.nan stats_data_std = np.inf robust_mean, robust_median, robust_std = np.nan, np.nan, np.inf self.stats_info = {'xrange':[x0,x1], 'yrange':[y0,y1], 'mean':stats_data_mean, 'median':stats_data_median, 'std':stats_data_std, 'min':stats_data.min(), 'max':stats_data.max()} # want min/max to reflect any Inf/NaN self.mean_textctrl.SetValue("{:0.4g}".format(self.stats_info['mean'])) self.median_textctrl.SetValue("{:0.4g}".format(self.stats_info['median'])) self.stdev_textctrl.SetValue("{:0.4g}".format(self.stats_info['std'])) self.stats_info['robust-mean'] = robust_mean self.stats_info['robust-median'] = robust_median self.stats_info['robust-std'] = robust_std self.robust_mean_textctrl.SetValue("{:0.4g}".format(robust_mean)) self.robust_stdev_textctrl.SetValue("{:0.4g}".format(robust_std)) self.minval_textctrl.SetValue("{:0.4g}".format(self.stats_info['min'])) self.maxval_textctrl.SetValue("{:0.4g}".format(self.stats_info['max'])) wmin = np.where(stats_data == stats_data.min()) wmin = [(wmin[1][i] + x0,wmin[0][i] + y0) for i in np.arange(wmin[0].size)] if len(wmin) == 1: wmin = wmin[0] self.minpos_textctrl.SetValue("{}".format(wmin)) self.stats_info['wmin'] = wmin wmax = np.where(stats_data == stats_data.max()) wmax = [(wmax[1][i] + x0,wmax[0][i] + y0) for i in np.arange(wmax[0].size)] if len(wmax) == 1: wmax = wmax[0] self.maxpos_textctrl.SetValue("{}".format(wmax)) self.stats_info['wmax'] = wmax set_textctrl_background_color(self.x0_textctrl, 'ok') set_textctrl_background_color(self.x1_textctrl, 'ok') set_textctrl_background_color(self.xsize_textctrl, 'ok') set_textctrl_background_color(self.y0_textctrl, 'ok') set_textctrl_background_color(self.y1_textctrl, 'ok') set_textctrl_background_color(self.ysize_textctrl, 'ok') def x0_textctrl_changed(self, evt): validate_textctrl_str(self.x0_textctrl, int, self.last_string_values['x0']) def x0_textctrl_entered(self, evt): if validate_textctrl_str(self.x0_textctrl, int, self.last_string_values['x0']): self.last_string_values['x0'] = self.x0_textctrl.GetValue() self.update_stats_box(int(self.last_string_values['x0']), None, None, None) self.x0_textctrl.SetSelection(-1, -1) self.redraw_overplot_on_image() def xsize_textctrl_changed(self, evt): validate_textctrl_str(self.xsize_textctrl, int, self.last_string_values['xsize']) def xsize_textctrl_entered(self, evt): if validate_textctrl_str(self.xsize_textctrl, int, self.last_string_values['xsize']): self.last_string_values['xsize'] = self.xsize_textctrl.GetValue() xsize = int(self.last_string_values['xsize']) sys.stderr.write("\n\nxsize = {}\n\n".format(xsize)) x0,y0,x1,y1 = self.get_x0y0x1y1_from_stats_rect() xc = (x0 + x1) / 2. x0 = max(0, int(xc - xsize / 2.)) x1 = x0 + xsize - 1 x1 = min(x1, self.ztv_frame.display_image.shape[1] - 1) x0 = x1 - xsize + 1 x0 = max(0, int(xc - xsize / 2.)) self.update_stats_box(x0, y0, x1, y1) self.xsize_textctrl.SetSelection(-1, -1) self.redraw_overplot_on_image() def x1_textctrl_changed(self, evt): validate_textctrl_str(self.x1_textctrl, int, self.last_string_values['x1']) def x1_textctrl_entered(self, evt): if validate_textctrl_str(self.x1_textctrl, int, self.last_string_values['x1']): self.last_string_values['x1'] = self.x1_textctrl.GetValue() self.update_stats_box(None, None, int(self.last_string_values['x1']), None) self.x1_textctrl.SetSelection(-1, -1) self.redraw_overplot_on_image() def y0_textctrl_changed(self, evt): validate_textctrl_str(self.y0_textctrl, int, self.last_string_values['y0']) def y0_textctrl_entered(self, evt): if validate_textctrl_str(self.y0_textctrl, int, self.last_string_values['y0']): self.last_string_values['y0'] = self.y0_textctrl.GetValue() self.update_stats_box(None, int(self.last_string_values['y0']), None, None) self.y0_textctrl.SetSelection(-1, -1) self.redraw_overplot_on_image() def ysize_textctrl_changed(self, evt): validate_textctrl_str(self.ysize_textctrl, int, self.last_string_values['ysize']) def ysize_textctrl_entered(self, evt): if validate_textctrl_str(self.ysize_textctrl, int, self.last_string_values['ysize']): self.last_string_values['ysize'] = self.ysize_textctrl.GetValue() ysize = int(self.last_string_values['ysize']) x0,y0,x1,y1 = self.get_x0y0x1y1_from_stats_rect() yc = (y0 + y1) / 2. y0 = max(0, int(yc - ysize / 2.)) y1 = y0 + ysize - 1 y1 = min(y1, self.ztv_frame.display_image.shape[0] - 1) y0 = y1 - ysize + 1 y0 = max(0, int(yc - ysize / 2.)) self.update_stats_box(x0, y0, x1, y1) self.ysize_textctrl.SetSelection(-1, -1) self.redraw_overplot_on_image() def y1_textctrl_changed(self, evt): validate_textctrl_str(self.y1_textctrl, int, self.last_string_values['y1']) def y1_textctrl_entered(self, evt): if validate_textctrl_str(self.y1_textctrl, int, self.last_string_values['y1']): self.last_string_values['y1'] = self.y1_textctrl.GetValue() self.update_stats_box(None, None, None, int(self.last_string_values['y1'])) self.y1_textctrl.SetSelection(-1, -1) self.redraw_overplot_on_image()
class rectSelector(object): def __init__(self, fig, df): self.fig = fig self.df = df self.cid_on = fig.canvas.mpl_connect('button_press_event', self) self.cid_off = fig.canvas.mpl_connect('button_release_event', self) self.cid_move = None self.feats = {'lim1': [-np.inf, +np.inf], 'lim2': [-np.inf, +np.inf], 'feat1': None, 'feat2': None} self.rect = None def __call__(self, event): if event.inaxes is None: return if event.name=='button_press_event': self.on_press(event) elif event.name=='button_release_event': self.on_release(event) def on_press(self, event): if self.rect is not None: self.rect.remove() self.feats['lim1'] = [-np.inf, +np.inf] self.feats['lim2'] = [-np.inf, +np.inf] (feat1, feat2) = [feat for ax, feat in ax2feat.iteritems() if ax==event.inaxes][0] self.feats = {'feat1': feat1, 'feat2': feat2, 'lim1': [event.xdata]*2, 'lim2':[event.ydata]*2} self.rect = Rectangle([event.xdata, event.ydata], 0., 0., alpha=0.3) event.inaxes.add_patch(self.rect) self.cid_move = self.fig.canvas.mpl_connect('motion_notify_event', self.on_move) def on_release(self, event): self.fig.canvas.mpl_disconnect(self.cid_move) def on_move(self, event): if event.inaxes is None: return # resize the rectangle so the new width tracks the mouse pointer self.rect.set_width(event.xdata - self.rect.get_x()) self.rect.set_height(event.ydata - self.rect.get_y()) # update the limits for each feature self.feats['lim1'] = [self.rect.get_x(), event.xdata] if event.xdata<self.rect.get_x(): self.feats['lim1'] = self.feats['lim1'][::-1] self.feats['lim2'] = [self.rect.get_y(), event.ydata] if event.ydata<self.rect.get_y(): self.feats['lim2'] = self.feats['lim2'][::-1] self.filterData() def filterData(self): feat1 = self.feats['feat1'] feat2 = self.feats['feat2'] lim1 = self.feats['lim1'] lim2 = self.feats['lim2'] df = self.df ix = np.vstack((df[feat1]>lim1[0], df[feat1]<lim1[1], df[feat2]>lim2[0], df[feat2]<lim2[1])).all(0) colors = np.array([colordict[i] for i in df['species']], dtype = 'S4') colors[~ix] = 'gray' for sc in scatters: sc.set_color(colors) plt.show()
class StatsPanel(wx.Panel): def __init__(self, parent): wx.Panel.__init__(self, parent, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize) self.ztv_frame = self.GetTopLevelParent() self.ztv_frame.primary_image_panel.popup_menu_cursor_modes.append( 'Stats box') self.ztv_frame.primary_image_panel.available_cursor_modes[ 'Stats box'] = { 'set-to-mode': self.set_cursor_to_stats_box_mode, 'on_button_press': self.on_button_press, 'on_motion': self.on_motion, 'on_button_release': self.on_button_release } self.stats_info = None self.last_string_values = { 'x0': '', 'xsize': '', 'x1': '', 'y0': '', 'ysize': '', 'y1': '' } self.stats_rect = Rectangle((0, 0), 10, 10, color='magenta', fill=False, zorder=100) # use self.stats_rect as where we store/retrieve the x0,y0,x1,y1 # x0,y0,x1,y1 should be limited to range of 0 to shape-1 # but, stats should be calculated over e.g. x0:x1+1 (so that have pixels to do stats on even if x0==x1) # and, width/height of stats_rect should always be >= 0 textentry_font = wx.Font(14, wx.FONTFAMILY_MODERN, wx.NORMAL, wx.FONTWEIGHT_LIGHT, False) values_sizer = wx.FlexGridSizer(10, 5, 0, 0) values_sizer.SetFlexibleDirection(wx.BOTH) values_sizer.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_SPECIFIED) values_sizer.AddSpacer((0, 0), 0, wx.EXPAND) self.low_static_text = wx.StaticText(self, wx.ID_ANY, u"Low", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT) self.low_static_text.Wrap(-1) values_sizer.Add(self.low_static_text, 0, wx.ALL | wx.ALIGN_CENTER_HORIZONTAL, 0) self.low_static_text = wx.StaticText(self, wx.ID_ANY, u"# pix", wx.DefaultPosition, wx.DefaultSize, 0) self.low_static_text.Wrap(-1) values_sizer.Add(self.low_static_text, 0, wx.ALL | wx.ALIGN_CENTER_HORIZONTAL, 0) self.high_static_text = wx.StaticText(self, wx.ID_ANY, u"High", wx.DefaultPosition, wx.DefaultSize, 0) self.high_static_text.Wrap(-1) values_sizer.Add(self.high_static_text, 0, wx.ALL | wx.ALIGN_CENTER_HORIZONTAL, 0) values_sizer.AddSpacer((0, 0), 0, wx.EXPAND) self.x_static_text = wx.StaticText(self, wx.ID_ANY, u"x", wx.DefaultPosition, wx.DefaultSize, 0) self.x_static_text.Wrap(-1) values_sizer.Add(self.x_static_text, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 0) self.x0_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_PROCESS_ENTER) self.x0_textctrl.SetFont(textentry_font) values_sizer.Add(self.x0_textctrl, 0, wx.ALL, 2) self.x0_textctrl.Bind(wx.EVT_TEXT, self.x0_textctrl_changed) self.x0_textctrl.Bind(wx.EVT_TEXT_ENTER, self.x0_textctrl_entered) self.xsize_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_PROCESS_ENTER) self.xsize_textctrl.SetFont(textentry_font) values_sizer.Add(self.xsize_textctrl, 0, wx.ALL, 2) self.xsize_textctrl.Bind(wx.EVT_TEXT, self.xsize_textctrl_changed) self.xsize_textctrl.Bind(wx.EVT_TEXT_ENTER, self.xsize_textctrl_entered) self.x1_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_PROCESS_ENTER) self.x1_textctrl.SetFont(textentry_font) values_sizer.Add(self.x1_textctrl, 0, wx.ALL, 2) self.x1_textctrl.Bind(wx.EVT_TEXT, self.x1_textctrl_changed) self.x1_textctrl.Bind(wx.EVT_TEXT_ENTER, self.x1_textctrl_entered) self.npix_static_text = wx.StaticText(self, wx.ID_ANY, u"# pixels", wx.DefaultPosition, wx.DefaultSize, 0) self.npix_static_text.Wrap(-1) values_sizer.Add(self.npix_static_text, 0, wx.ALL | wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_BOTTOM, 0) self.y_static_text = wx.StaticText(self, wx.ID_ANY, u"y", wx.DefaultPosition, wx.DefaultSize, 0) self.y_static_text.Wrap(-1) values_sizer.Add(self.y_static_text, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 0) self.y0_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_PROCESS_ENTER) self.y0_textctrl.SetFont(textentry_font) values_sizer.Add(self.y0_textctrl, 0, wx.ALL, 2) self.y0_textctrl.Bind(wx.EVT_TEXT, self.y0_textctrl_changed) self.y0_textctrl.Bind(wx.EVT_TEXT_ENTER, self.y0_textctrl_entered) self.ysize_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_PROCESS_ENTER) self.ysize_textctrl.SetFont(textentry_font) values_sizer.Add(self.ysize_textctrl, 0, wx.ALL, 2) self.ysize_textctrl.Bind(wx.EVT_TEXT, self.ysize_textctrl_changed) self.ysize_textctrl.Bind(wx.EVT_TEXT_ENTER, self.ysize_textctrl_entered) self.y1_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_PROCESS_ENTER) self.y1_textctrl.SetFont(textentry_font) values_sizer.Add(self.y1_textctrl, 0, wx.ALL, 2) self.y1_textctrl.Bind(wx.EVT_TEXT, self.y1_textctrl_changed) self.y1_textctrl.Bind(wx.EVT_TEXT_ENTER, self.y1_textctrl_entered) self.npix_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY) self.npix_textctrl.SetFont(textentry_font) self.npix_textctrl.SetBackgroundColour( textctrl_output_only_background_color) values_sizer.Add(self.npix_textctrl, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT, 0) values_sizer.AddSpacer((0, 15), 0, wx.EXPAND) values_sizer.AddSpacer((0, 0), 0, wx.EXPAND) values_sizer.AddSpacer((0, 0), 0, wx.EXPAND) values_sizer.AddSpacer((0, 0), 0, wx.EXPAND) values_sizer.AddSpacer((0, 0), 0, wx.EXPAND) values_sizer.AddSpacer((0, 0), 0, wx.EXPAND) self.median_static_text = wx.StaticText(self, wx.ID_ANY, u"Median", wx.DefaultPosition, wx.DefaultSize, 0) self.median_static_text.Wrap(-1) values_sizer.Add(self.median_static_text, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT, 0) self.median_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY) self.median_textctrl.SetFont(textentry_font) self.median_textctrl.SetBackgroundColour( textctrl_output_only_background_color) values_sizer.Add(self.median_textctrl, 0, wx.ALL, 2) self.robust_static_text = wx.StaticText(self, wx.ID_ANY, u"Robust", wx.DefaultPosition, wx.DefaultSize, 0) self.robust_static_text.Wrap(-1) values_sizer.Add(self.robust_static_text, 0, wx.ALL | wx.ALIGN_BOTTOM | wx.ALIGN_CENTER_HORIZONTAL, 0) values_sizer.AddSpacer((0, 0), 0, wx.EXPAND) values_sizer.AddSpacer((0, 0), 0, wx.EXPAND) self.mean_static_text = wx.StaticText(self, wx.ID_ANY, u"Mean", wx.DefaultPosition, wx.DefaultSize, 0) self.mean_static_text.Wrap(-1) values_sizer.Add(self.mean_static_text, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT, 0) self.mean_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY) self.mean_textctrl.SetFont(textentry_font) self.mean_textctrl.SetBackgroundColour( textctrl_output_only_background_color) values_sizer.Add(self.mean_textctrl, 0, wx.ALL, 2) self.robust_mean_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY) self.robust_mean_textctrl.SetFont(textentry_font) self.robust_mean_textctrl.SetBackgroundColour( textctrl_output_only_background_color) values_sizer.Add(self.robust_mean_textctrl, 0, wx.ALL, 2) values_sizer.AddSpacer((0, 0), 0, wx.EXPAND) values_sizer.AddSpacer((0, 0), 0, wx.EXPAND) self.stdev_static_text = wx.StaticText(self, wx.ID_ANY, u"Stdev", wx.DefaultPosition, wx.DefaultSize, 0) self.stdev_static_text.Wrap(-1) values_sizer.Add(self.stdev_static_text, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT, 0) self.stdev_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY) self.stdev_textctrl.SetFont(textentry_font) self.stdev_textctrl.SetBackgroundColour( textctrl_output_only_background_color) values_sizer.Add(self.stdev_textctrl, 0, wx.ALL, 2) self.robust_stdev_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY) self.robust_stdev_textctrl.SetFont(textentry_font) self.robust_stdev_textctrl.SetBackgroundColour( textctrl_output_only_background_color) values_sizer.Add(self.robust_stdev_textctrl, 0, wx.ALL, 2) values_sizer.AddSpacer((0, 0), 0, wx.EXPAND) values_sizer.AddSpacer((0, 15), 0, wx.EXPAND) values_sizer.AddSpacer((0, 0), 0, wx.EXPAND) values_sizer.AddSpacer((0, 0), 0, wx.EXPAND) values_sizer.AddSpacer((0, 0), 0, wx.EXPAND) values_sizer.AddSpacer((0, 0), 0, wx.EXPAND) values_sizer.AddSpacer((0, 0), 0, wx.EXPAND) self.min_static_text = wx.StaticText(self, wx.ID_ANY, u"Min", wx.DefaultPosition, wx.DefaultSize, 0) self.min_static_text.Wrap(-1) values_sizer.Add(self.min_static_text, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT, 0) self.minval_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY) self.minval_textctrl.SetFont(textentry_font) self.minval_textctrl.SetBackgroundColour( textctrl_output_only_background_color) values_sizer.Add(self.minval_textctrl, 0, wx.ALL, 2) self.minpos_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY) self.minpos_textctrl.SetFont(textentry_font) self.minpos_textctrl.SetBackgroundColour( textctrl_output_only_background_color) values_sizer.Add(self.minpos_textctrl, 0, wx.ALL, 2) values_sizer.AddSpacer((0, 0), 0, wx.EXPAND) values_sizer.AddSpacer((0, 0), 0, wx.EXPAND) self.max_static_text = wx.StaticText(self, wx.ID_ANY, u"Max", wx.DefaultPosition, wx.DefaultSize, 0) self.max_static_text.Wrap(-1) values_sizer.Add(self.max_static_text, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT, 0) self.maxval_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY) self.maxval_textctrl.SetFont(textentry_font) self.maxval_textctrl.SetBackgroundColour( textctrl_output_only_background_color) values_sizer.Add(self.maxval_textctrl, 0, wx.ALL, 2) self.maxpos_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_READONLY) self.maxpos_textctrl.SetFont(textentry_font) self.maxpos_textctrl.SetBackgroundColour( textctrl_output_only_background_color) values_sizer.Add(self.maxpos_textctrl, 0, wx.ALL, 2) self.hideshow_button = wx.Button(self, wx.ID_ANY, u"Show", wx.DefaultPosition, wx.DefaultSize, 0) values_sizer.Add( self.hideshow_button, 0, wx.ALL | wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL, 2) self.hideshow_button.Bind(wx.EVT_BUTTON, self.on_hideshow_button) v_sizer1 = wx.BoxSizer(wx.VERTICAL) v_sizer1.AddStretchSpacer(1.0) v_sizer1.Add(values_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL) v_sizer1.AddStretchSpacer(1.0) self.SetSizer(v_sizer1) pub.subscribe(self.queue_update_stats, 'recalc-display-image-called') pub.subscribe(self._set_stats_box_parameters, 'set-stats-box-parameters') pub.subscribe(self.publish_stats_to_stream, 'get-stats-box-info') def publish_stats_to_stream(self, msg=None): wx.CallAfter(send_to_stream, sys.stdout, ('stats-box-info', self.stats_info)) def on_button_press(self, event): self.select_panel() self.stats_start_timestamp = event.guiEvent.GetTimestamp() # millisec self.update_stats_box(event.xdata, event.ydata, event.xdata, event.ydata) self.redraw_overplot_on_image() self.cursor_stats_box_x0, self.cursor_stats_box_y0 = event.xdata, event.ydata def on_motion(self, event): self.update_stats_box(self.cursor_stats_box_x0, self.cursor_stats_box_y0, event.xdata, event.ydata) self.redraw_overplot_on_image() self.update_stats() def on_button_release(self, event): self.redraw_overplot_on_image() self.update_stats() def set_cursor_to_stats_box_mode(self, event): self.ztv_frame.primary_image_panel.cursor_mode = 'Stats box' self.ztv_frame.stats_panel.select_panel() self.ztv_frame.stats_panel.highlight_panel() def queue_update_stats(self, msg=None): """ wrapper to call update_stats from CallAfter in order to make GUI as responsive as possible. """ wx.CallAfter(self.update_stats, msg=None) def _set_stats_box_parameters(self, msg): """ wrapper to update_stats_box to receive messages & translate them correctly """ x0, x1, y0, y1 = [None] * 4 if msg['xrange'] is not None: x0, x1 = msg['xrange'] if msg['yrange'] is not None: y0, y1 = msg['yrange'] if msg['xrange'] is not None or msg['yrange'] is not None: self.update_stats_box(x0, y0, x1, y1) if msg['show_overplot'] is not None: if msg['show_overplot']: self.redraw_overplot_on_image() else: self.remove_overplot_on_image() send_to_stream(sys.stdout, ('set-stats-box-parameters-done', True)) def update_stats_box(self, x0=None, y0=None, x1=None, y1=None): if x0 is None: x0 = self.stats_rect.get_x() if y0 is None: y0 = self.stats_rect.get_y() if x1 is None: x1 = self.stats_rect.get_x() + self.stats_rect.get_width() if y1 is None: y1 = self.stats_rect.get_y() + self.stats_rect.get_height() if x0 > x1: x0, x1 = x1, x0 if y0 > y1: y0, y1 = y1, y0 x0 = min(max(0, x0), self.ztv_frame.display_image.shape[1] - 1) y0 = min(max(0, y0), self.ztv_frame.display_image.shape[0] - 1) x1 = min(max(0, x1), self.ztv_frame.display_image.shape[1] - 1) y1 = min(max(0, y1), self.ztv_frame.display_image.shape[0] - 1) self.stats_rect.set_bounds(x0, y0, x1 - x0, y1 - y0) self.ztv_frame.primary_image_panel.figure.canvas.draw() self.update_stats() def remove_overplot_on_image(self): if self.stats_rect in self.ztv_frame.primary_image_panel.axes.patches: self.ztv_frame.primary_image_panel.axes.patches.remove( self.stats_rect) self.ztv_frame.primary_image_panel.figure.canvas.draw() self.hideshow_button.SetLabel(u"Show") def redraw_overplot_on_image(self): if self.stats_rect not in self.ztv_frame.primary_image_panel.axes.patches: self.ztv_frame.primary_image_panel.axes.add_patch(self.stats_rect) self.ztv_frame.primary_image_panel.figure.canvas.draw() self.hideshow_button.SetLabel(u"Hide") def on_hideshow_button(self, evt): if self.hideshow_button.GetLabel() == 'Hide': self.remove_overplot_on_image() else: self.redraw_overplot_on_image() def get_x0y0x1y1_from_stats_rect(self): x0 = self.stats_rect.get_x() y0 = self.stats_rect.get_y() x1 = x0 + self.stats_rect.get_width() y1 = y0 + self.stats_rect.get_height() return x0, y0, x1, y1 def update_stats(self, msg=None): x0, y0, x1, y1 = self.get_x0y0x1y1_from_stats_rect() x0, y0 = int(np.round(x0)), int(np.round(y0)) x1, y1 = int(np.round(x1)), int(np.round(y1)) self.last_string_values['x0'] = str(int(x0)) self.x0_textctrl.SetValue(self.last_string_values['x0']) self.last_string_values['y0'] = str(int(y0)) self.y0_textctrl.SetValue(self.last_string_values['y0']) x_npix = int(x1 - x0 + 1) self.last_string_values['xsize'] = str(x_npix) self.xsize_textctrl.SetValue(self.last_string_values['xsize']) y_npix = int(y1 - y0 + 1) self.last_string_values['ysize'] = str(y_npix) self.ysize_textctrl.SetValue(self.last_string_values['ysize']) self.last_string_values['x1'] = str(int(x1)) self.x1_textctrl.SetValue(self.last_string_values['x1']) self.last_string_values['y1'] = str(int(y1)) self.y1_textctrl.SetValue(self.last_string_values['y1']) self.npix_textctrl.SetValue(str(x_npix * y_npix)) stats_data = self.ztv_frame.display_image[y0:y1 + 1, x0:x1 + 1] finite_mask = np.isfinite(stats_data) if finite_mask.max() is np.True_: stats_data_mean = stats_data[finite_mask].mean() stats_data_median = np.median(stats_data[finite_mask]) stats_data_std = stats_data[finite_mask].std() robust_mean, robust_median, robust_std = sigma_clipped_stats( stats_data[finite_mask]) else: stats_data_mean = np.nan stats_data_median = np.nan stats_data_std = np.inf robust_mean, robust_median, robust_std = np.nan, np.nan, np.inf self.stats_info = { 'xrange': [x0, x1], 'yrange': [y0, y1], 'mean': stats_data_mean, 'median': stats_data_median, 'std': stats_data_std, 'min': stats_data.min(), 'max': stats_data.max() } # want min/max to reflect any Inf/NaN self.mean_textctrl.SetValue("{:0.4g}".format(self.stats_info['mean'])) self.median_textctrl.SetValue("{:0.4g}".format( self.stats_info['median'])) self.stdev_textctrl.SetValue("{:0.4g}".format(self.stats_info['std'])) self.stats_info['robust-mean'] = robust_mean self.stats_info['robust-median'] = robust_median self.stats_info['robust-std'] = robust_std self.robust_mean_textctrl.SetValue("{:0.4g}".format(robust_mean)) self.robust_stdev_textctrl.SetValue("{:0.4g}".format(robust_std)) self.minval_textctrl.SetValue("{:0.4g}".format(self.stats_info['min'])) self.maxval_textctrl.SetValue("{:0.4g}".format(self.stats_info['max'])) wmin = np.where(stats_data == stats_data.min()) wmin = [(wmin[1][i] + x0, wmin[0][i] + y0) for i in np.arange(wmin[0].size)] if len(wmin) == 1: wmin = wmin[0] self.minpos_textctrl.SetValue("{}".format(wmin)) self.stats_info['wmin'] = wmin wmax = np.where(stats_data == stats_data.max()) wmax = [(wmax[1][i] + x0, wmax[0][i] + y0) for i in np.arange(wmax[0].size)] if len(wmax) == 1: wmax = wmax[0] self.maxpos_textctrl.SetValue("{}".format(wmax)) self.stats_info['wmax'] = wmax set_textctrl_background_color(self.x0_textctrl, 'ok') set_textctrl_background_color(self.x1_textctrl, 'ok') set_textctrl_background_color(self.xsize_textctrl, 'ok') set_textctrl_background_color(self.y0_textctrl, 'ok') set_textctrl_background_color(self.y1_textctrl, 'ok') set_textctrl_background_color(self.ysize_textctrl, 'ok') def x0_textctrl_changed(self, evt): validate_textctrl_str(self.x0_textctrl, int, self.last_string_values['x0']) def x0_textctrl_entered(self, evt): if validate_textctrl_str(self.x0_textctrl, int, self.last_string_values['x0']): self.last_string_values['x0'] = self.x0_textctrl.GetValue() self.update_stats_box(int(self.last_string_values['x0']), None, None, None) self.x0_textctrl.SetSelection(-1, -1) self.redraw_overplot_on_image() def xsize_textctrl_changed(self, evt): validate_textctrl_str(self.xsize_textctrl, int, self.last_string_values['xsize']) def xsize_textctrl_entered(self, evt): if validate_textctrl_str(self.xsize_textctrl, int, self.last_string_values['xsize']): self.last_string_values['xsize'] = self.xsize_textctrl.GetValue() xsize = int(self.last_string_values['xsize']) sys.stderr.write("\n\nxsize = {}\n\n".format(xsize)) x0, y0, x1, y1 = self.get_x0y0x1y1_from_stats_rect() xc = (x0 + x1) / 2. x0 = max(0, int(xc - xsize / 2.)) x1 = x0 + xsize - 1 x1 = min(x1, self.ztv_frame.display_image.shape[1] - 1) x0 = x1 - xsize + 1 x0 = max(0, int(xc - xsize / 2.)) self.update_stats_box(x0, y0, x1, y1) self.xsize_textctrl.SetSelection(-1, -1) self.redraw_overplot_on_image() def x1_textctrl_changed(self, evt): validate_textctrl_str(self.x1_textctrl, int, self.last_string_values['x1']) def x1_textctrl_entered(self, evt): if validate_textctrl_str(self.x1_textctrl, int, self.last_string_values['x1']): self.last_string_values['x1'] = self.x1_textctrl.GetValue() self.update_stats_box(None, None, int(self.last_string_values['x1']), None) self.x1_textctrl.SetSelection(-1, -1) self.redraw_overplot_on_image() def y0_textctrl_changed(self, evt): validate_textctrl_str(self.y0_textctrl, int, self.last_string_values['y0']) def y0_textctrl_entered(self, evt): if validate_textctrl_str(self.y0_textctrl, int, self.last_string_values['y0']): self.last_string_values['y0'] = self.y0_textctrl.GetValue() self.update_stats_box(None, int(self.last_string_values['y0']), None, None) self.y0_textctrl.SetSelection(-1, -1) self.redraw_overplot_on_image() def ysize_textctrl_changed(self, evt): validate_textctrl_str(self.ysize_textctrl, int, self.last_string_values['ysize']) def ysize_textctrl_entered(self, evt): if validate_textctrl_str(self.ysize_textctrl, int, self.last_string_values['ysize']): self.last_string_values['ysize'] = self.ysize_textctrl.GetValue() ysize = int(self.last_string_values['ysize']) x0, y0, x1, y1 = self.get_x0y0x1y1_from_stats_rect() yc = (y0 + y1) / 2. y0 = max(0, int(yc - ysize / 2.)) y1 = y0 + ysize - 1 y1 = min(y1, self.ztv_frame.display_image.shape[0] - 1) y0 = y1 - ysize + 1 y0 = max(0, int(yc - ysize / 2.)) self.update_stats_box(x0, y0, x1, y1) self.ysize_textctrl.SetSelection(-1, -1) self.redraw_overplot_on_image() def y1_textctrl_changed(self, evt): validate_textctrl_str(self.y1_textctrl, int, self.last_string_values['y1']) def y1_textctrl_entered(self, evt): if validate_textctrl_str(self.y1_textctrl, int, self.last_string_values['y1']): self.last_string_values['y1'] = self.y1_textctrl.GetValue() self.update_stats_box(None, None, None, int(self.last_string_values['y1'])) self.y1_textctrl.SetSelection(-1, -1) self.redraw_overplot_on_image()
class ZoneInteret: """ Cette classe permet de gérer les information de zone interet Une zone interet est un cadre d'image sur l'image entière. On se concentre sur cette zone interet pour faire le traitement. C'est une manière de réduire le bruit sur le résultat de traitement @version 2.0 """ @staticmethod def verifier_presence_fichier_ini(): """ Vérifier si les fichiers de zone interet sont déjà présents dans le dossier :return: true si présent, false sinon """ return os.path.isfile('./zi/param.ini') @staticmethod def supprimer_ZI(window): """ La méthode pour gérer la suppresion de zone interet :param window: le fenetre principale :return: """ if os.path.isfile('./zi/param.ini'): try: os.remove("./zi/param.ini") os.remove("./zi/image_modele.png") os.remove("./zi/image_zone_interet.png") QMessageBox.information(window, "Information", "Supprimer la Zone d'intérêt avec succès", QMessageBox.Ok) except OSError: QMessageBox.warning(window, "Erreur", "Impossible de supprimer les fichiers dans le repertoire /zi", QMessageBox.Ok) else: QMessageBox.warning(window, "Erreur", "Impossible de trouver les fichiers dans le repertoire /zi", QMessageBox.Ok) def __init__(self, video): """ Initialise les variables nécessaires à l'affichage de l'image et aux événements :param video: la vidéo à traiter """ self.flag = False self.get_one_image_from_video(video) # On se sert de l'image extraite précédemment self.img = mpimg.imread('./zi/image_modele.png') # On initialise le titre de la fenêtre fig = plt.figure(1) fig.canvas.set_window_title("Zone Interet") # On récupère les infos des axes self.ax = plt.gca() # On initialise le futur rectangle dessiné (non rempli aux bordures rouges) self.rect = Rectangle((0, 0), 1, 1, fill=False, edgecolor="red") # Initialisation des points du rectangle self.x0 = None self.y0 = None self.x1 = None self.y1 = None self.ax.add_patch(self.rect) # Liaison des événements self.ax.figure.canvas.mpl_connect('button_press_event', self.on_mouseclick_press) self.ax.figure.canvas.mpl_connect('button_release_event', self.on_mouseclick_release) self.ax.figure.canvas.mpl_connect('key_press_event', self.on_keyboard_press) # Affichage de l'image dans la fenêtre self.imgplot = plt.imshow(self.img) self.show_window() def on_mouseclick_press(self, event): """ Un click gauche -> sauvegarde des coordonnées du pointeur :param event: évènement de clique :return: """ self.x0 = event.xdata self.y0 = event.ydata def on_mouseclick_release(self, event): """ Click gauche relâché -> dessin du rectangle :param event: évènement de souris :return: """ self.x1 = event.xdata self.y1 = event.ydata self.rect.set_width(self.x1 - self.x0) self.rect.set_height(self.y1 - self.y0) self.rect.set_xy((self.x0, self.y0)) self.ax.figure.canvas.draw() def on_keyboard_press(self, event): """ Si la touche "enter" est appuyée, on sauvegarde la zone d'intérêt :param event: évenenment de keyboard :return: """ if event.key == 'enter': self.flag = True with open("./zi/param.ini", "w") as file: file.write(str(int(self.rect.get_x())) + ",") file.write(str(int(self.rect.get_y())) + ",") file.write(str(int(self.rect.get_width())) + ",") file.write(str(int(self.rect.get_height()))) # On cache les axes avant d'enregistrer l'image modele avec la zone d'interet self.ax.get_xaxis().set_visible(False) self.ax.get_yaxis().set_visible(False) plt.title("Zone interet") plt.savefig("./zi/image_zone_interet.png") plt.close() def show_window(self): """ Pour afficher la fenêtre qui est utilisée pour choisir une zone interet :return: """ plt.title("Selectionner la zone interet avec la souris. Appuyez sur entrer pour valider.") plt.show() def get_one_image_from_video(self, video): """ Extrait une image de la vidéo selectionnée Cette image est utilisée pour choisir une zone interet :param video: la vidéo choisie :return: """ video_capture = cv2.VideoCapture(video) nb_frame = video_capture.get(cv2.CAP_PROP_FRAME_COUNT) video_capture.set(cv2.CAP_PROP_FRAME_COUNT, int(nb_frame - 1)) success, self.image = video_capture.read() #sauvegarder l'image cv2.imwrite("zi/image_modele.png", self.image)
class ComboBox(LineEdit): """ A ComboxBox, upon clicking button, drops down a list of items to choose. Items can be edited also. """ def __init__(self, width, height, text_list, edit_notify=None, selection_notify=None, **kwargs): if len(text_list) > 0: text = text_list[0] else: text = '' super(ComboBox, self).__init__(width, height, text, self._on_edit_notify, **kwargs) self._text_list = text_list self._edit_notify = edit_notify self._selection_notify = selection_notify if edit_notify and not callable(edit_notify): raise RuntimeError('edit_notify must be a callable function') if selection_notify and not callable(selection_notify): raise RuntimeError('selection_notify must be a callable function') #--------------------------------------------------------------------- # additional items # # selection axes for showing the possible selections # a rectangle to highlight the selection row # a button to show the selection drop down self._select_axes = None self._select_highlight = None # just a rectangle self._select_posx = None self._select_entries = [] self._cb_state = ComboState.IDLE self._n_lines = 5 self._mouse_motion_cid = None self._ignore_edit_notify = False def _render(self, fig, x, y): super(ComboBox, self)._render(fig, x, y) self._render_dropdown_button(fig) self._render_dropdown_axis(fig, x, y) def _render_dropdown_axis(self, fig, x, y): W, H = fig.get_size_inches() h = self._n_lines * self._height y -= h x /= W y /= H # create the other gui assets but keep them hidden # selection axes, same width, by 10 times in height w = self._width / W h /= H ax = fig.add_axes([x, y, w, h], xticks=[], yticks=[]) ax.set_xlim([0, self._width]) ax.set_ylim([0, self._n_lines * self._height]) ax.set_axis_bgcolor('white') ax.set_visible(False) ax.set_zorder(1000) self._select_axes = ax def _render_dropdown_button(self, fig): w, h = 0.25, 0.125 hw = w / 2.0 hh = h / 2.0 x = self._width - w - 0.02 y = (self._height - h) / 2.0 self._select_posx = x + hw # Three point polygon: # # 2 O-----O 3 # \ / # \ / # O # 1 # points = [ [x + hw, y], [x, y + h], [x + w, y + h], ] points = np.array(points) patch = Polygon(points, closed=True, ec='black', fc='black') self._axes.add_patch(patch) def _cb_change_state(self, new_state): if self._cb_state == new_state: raise RuntimeError("Already in state %s" % new_state) if self._cb_state == ComboState.IDLE: if new_state == ComboState.DROP_SELECT: self._select_axes.set_visible(True) x = self._pad_left y = ((self._n_lines - 1) * self._height) #-------------------------------------------------------------- # create highlight if self._select_highlight is None: self._select_highlight = Rectangle( (0, y - self._height / 2.0), self._width, self._height, ec=self._hl_color, fc=self._hl_color) self._select_axes.add_patch(self._select_highlight) else: self._select_highlight.set_visible(True) # delete existing text objects for t in self._select_entries: t.remove() del t self._select_entries = [] for t in self._text_list: txt = self._select_axes.text(x, y, t, ha='left', va='center') y -= self._height self._select_entries.append(txt) self._mouse_motion_cid = self._select_axes.figure.canvas.mpl_connect( 'motion_notify_event', self._on_mouse_motion) self.canvas().draw() else: self._unhandled_state(new_state) elif self._cb_state == ComboState.DROP_SELECT: if new_state == ComboState.IDLE: self._select_axes.set_visible(False) self.canvas().draw() else: self._unhandled_state(new_state) else: self._unhandled_state(new_state) self._cb_state = new_state def _unhandled_state(self, new_state): if _DEV: print("unhandled %s --> %s" % (self._cb_state, new_state)) def _on_mouse_down(self, event): if event.inaxes not in [self._axes, self._select_axes]: self._ignore_edit_notify = True if self._cb_state != ComboState.IDLE: self._cb_change_state(ComboState.IDLE) super(ComboBox, self)._on_mouse_down(event) return x, y = event.xdata, event.ydata if x is None or y is None: super(ComboBox, self)._on_mouse_down(event) return if self._cb_state == ComboState.IDLE: cx = self._select_posx d = np.sqrt((x - cx)**2) if d <= 0.16: self._cb_change_state(ComboState.DROP_SELECT) else: super(ComboBox, self)._on_mouse_down(event) elif self._cb_state == ComboState.DROP_SELECT: y = self._select_highlight.get_y() idx = self._find_text_entry(y) selection = self._text_list[idx] self._ignore_edit_notify = True self.text(selection) if self._selection_notify: self._selection_notify(idx, selection) self._cb_change_state(ComboState.IDLE) elif _DEV: print("on_mouse_down(): unhandled %s" % self._cb_state) def _on_mouse_motion(self, event): if event.inaxes != self._select_axes: return x, y = event.xdata, event.ydata if x is None or y is None: return if self._cb_state == ComboState.DROP_SELECT: idx = self._find_text_entry(y) _, y = self._select_entries[idx].get_position() self._select_highlight.set_y(y - self._height / 2.0) self.canvas().draw() def _on_key_press(self, event): if event.key == 'escape' and self._cb_state == ComboState.DROP_SELECT: self._cb_change_state(ComboState.IDLE) self._ignore_edit_notify = False super(ComboBox, self)._on_key_press(event) def _find_text_entry(self, y): # find nearest text dist = [] for txt in self._select_entries: _, ydata = txt.get_position() d = np.abs(ydata - y) dist.append(d) return np.argmin(dist) def _on_edit_notify(self, text): if self._ignore_edit_notify: self._ignore_edit_notify = False return # add to the list self._text_list.append(text) if self._edit_notify: self._edit_notify(text)
class ZoneInteret: @staticmethod def verifier_presence_fichier_ini(): return os.path.isfile('./zi/param.ini') @staticmethod def supprimer_ZI(window): if os.path.isfile('./zi/param.ini'): try: os.remove("./zi/param.ini") os.remove("./zi/image_modele.png") os.remove("./zi/image_zone_interet.png") QMessageBox.information( window, "Information", "Supprimer la Zone d'intérêt avec succès", QMessageBox.Ok) except OSError: QMessageBox.warning( window, "Erreur", "Impossible de supprimer les fichiers dans le repertoire /zi", QMessageBox.Ok) else: QMessageBox.warning( window, "Erreur", "Impossible de trouver les fichiers dans le repertoire /zi", QMessageBox.Ok) # Initialise les variables nécessaires à l'affichage de l'image et aux événements def __init__(self, video): self.flag = False self.get_one_image_from_video(video) # On se sert de l'image extraite précédemment self.img = mpimg.imread('./zi/image_modele.png') # On initialise le titre de la fenêtre fig = plt.figure(1) fig.canvas.set_window_title("Zone Interet") # On récupère les infos des axes self.ax = plt.gca() # On initialise le futur rectangle dessiné (non rempli aux bordures rouges) self.rect = Rectangle((0, 0), 1, 1, fill=False, edgecolor="red") # Initialisation des points du rectangle self.x0 = None self.y0 = None self.x1 = None self.y1 = None self.ax.add_patch(self.rect) # Liaison des événements self.ax.figure.canvas.mpl_connect('button_press_event', self.on_mouseclick_press) self.ax.figure.canvas.mpl_connect('button_release_event', self.on_mouseclick_release) self.ax.figure.canvas.mpl_connect('key_press_event', self.on_keyboard_press) # Affichage de l'image dans la fenêtre self.imgplot = plt.imshow(self.img) self.show_window() # Un click gauche -> sauvegarde des coordonnées du pointeur def on_mouseclick_press(self, event): self.x0 = event.xdata self.y0 = event.ydata # Click gauche relâché -> dessin du rectangle def on_mouseclick_release(self, event): self.x1 = event.xdata self.y1 = event.ydata self.rect.set_width(self.x1 - self.x0) self.rect.set_height(self.y1 - self.y0) self.rect.set_xy((self.x0, self.y0)) self.ax.figure.canvas.draw() # Si la touche "enter" est appuyée, on sauvegarde la zone d'intérêt def on_keyboard_press(self, event): if event.key == 'enter': self.flag = True with open("./zi/param.ini", "w") as file: file.write(str(int(self.rect.get_x())) + ",") file.write(str(int(self.rect.get_y())) + ",") file.write(str(int(self.rect.get_width())) + ",") file.write(str(int(self.rect.get_height()))) # On cache les axes avant d'enregistrer l'image modele avec la zone d'interet self.ax.get_xaxis().set_visible(False) self.ax.get_yaxis().set_visible(False) plt.title("Zone interet") plt.savefig("./zi/image_zone_interet.png") plt.close() def show_window(self): plt.title( "Selectionner la zone interet avec la souris. Appuyez sur entrer pour valider." ) plt.show() # extrait une image de la vidéo selectionnée def get_one_image_from_video(self, video): video_capture = cv2.VideoCapture(video) # TODO : bien récupérer la dernière frame nb_frame = video_capture.get(cv2.CAP_PROP_FRAME_COUNT) video_capture.set(cv2.CAP_PROP_FRAME_COUNT, int(nb_frame - 1)) success, self.image = video_capture.read() print(success) cv2.imwrite("zi/image_modele.png", self.image)
def visualize(Grid): fig, axes = plt.subplots(2, 1, dpi=100, figsize=(12, 8)) axes[0].plot(Grid.Ez) axes[0].xaxis.grid(linestyle='dotted') axes[0].set_xlabel('Cell') axes[0].set_ylabel(r'$E_z$', fontsize=12, rotation=0) axes[0].set_title( 'time passed: {0:.3e}s, timesteps passed: {timesteps}'.format( Grid.time_passed, timesteps=Grid.timesteps_passed)) #axes[0].set_ylim([-1.5, 1.5]) axes[1].plot(Grid.Hy) axes[1].xaxis.grid(linestyle='dotted') axes[1].set_xlabel('Cell') axes[1].set_ylabel(r'$H_y$', fontsize=12, rotation=0) for mat in Grid.materials: media_repr_0 = Rectangle(xy=(mat.position[0] - 0.5, -1.4), height=2.8, width=(mat.position[-1] - mat.position[0] + 1), color='grey', fill=True, alpha=mat.eps * 0.12) axes[0].add_patch(media_repr_0) axes[0].annotate(s=r'$\epsilon_r$={0:.2f}'.format(mat.eps) + '\n' + r'$\sigma$={0:.2f}'.format(mat.conductivity), xy=(media_repr_0.get_x() + 0.1, media_repr_0.get_y() + 0.2), color='black') media_repr_1 = Rectangle(xy=(mat.position[0] - 0.5, -1.4), height=2.8, width=(mat.position[-1] - mat.position[0] + 1), color='grey', fill=True, alpha=mat.eps * 0.12) axes[1].add_patch(media_repr_1) if mat.model == 'Lorentz': s = r'$\epsilon(\omega)$' + '\n' + r'$\sigma$={0:.2f}'.format( mat.conductivity) else: s = r'$\epsilon_r$={0:.2f}'.format( mat.eps) + '\n' + r'$\sigma$={0:.2f}'.format(mat.conductivity) axes[1].annotate(s, xy=(media_repr_1.get_x() + 0.1, media_repr_1.get_y() + 0.2), color='black') for src in Grid.sources: src_repr = Rectangle(xy=(src.position - 0.5, -src.ampl), height=2 * src.ampl, width=1, color='red', alpha=0.3) axes[0].add_patch(src_repr) for obs in Grid.local_observers: if isinstance(obs, QuasiHarmonicObserver): obs_repr = Rectangle(xy=(obs.position - 0.5, -1.4), height=2.8, width=1, color='green', alpha=0.3) elif isinstance(obs, E_FFTObserver): obs_repr = Rectangle(xy=(obs.position - 0.5, -1.4), height=2.8, width=1, color='indigo', alpha=0.3) elif isinstance(obs, P_FFTObserver): obs_repr = Rectangle(xy=(obs.position - 0.5, -1.4), height=2.8, width=1, color='orange', alpha=0.3) elif isinstance(obs, MovingFrame): obs_repr = Rectangle(xy=(obs.position[0] - 0.5, -1.4), height=2.8, width=obs.position[-1] - obs.position[0], fc='none', color='red') axes[0].add_patch(obs_repr) fig.tight_layout() plt.show()
class CaseSelector(_my_SelectorWidget): """ Select a min/max range of the x or y axes for a matplotlib Axes. For the selector to remain responsive you much keep a reference to it. Example usage:: ax = subplot(111) ax.plot(x,y) def onselect(vmin, vmax): print vmin, vmax span = SpanSelector(ax, onselect, 'horizontal') *onmove_callback* is an optional callback that is called on mouse move within the span range """ def __init__(self, ax, onselect, onclick, minspan=0.1, nrect=10, update_on_ext_event=True, rectprops=None, stay_rectprops=None, lineprops=None, onmove_callback=None, button=None): """ Create a case selector in *ax*. When a selection is made, call *onselect* with:: onselect(vmin, vmax) and clear the span. *direction* must be 'horizontal' or 'vertical' If *minspan* is not *None*, ignore events smaller than *minspan* The span rectangle is drawn with *rectprops*; default:: rectprops = dict(facecolor='red', alpha=0.5) Set the visible attribute to *False* if you want to turn off the functionality of the span selector If *span_stays* is True, the span stays visble after making a valid selection. *button* is a list of integers indicating which mouse buttons should be used for selection. You can also specify a single integer if only a single button is desired. Default is *None*, which does not limit which button can be used. Note, typically: 1 = left mouse button 2 = center mouse button (scroll wheel) 3 = right mouse button """ _my_SelectorWidget.__init__(self, ax, onselect, update_on_ext_event, button=button) if rectprops is None: self.rectprops = dict(facecolor='#f5f5f5', alpha=0.3) else: self.rectprops = rectprops if lineprops is None: self.lineprops = dict(color='#e66101', lw=2) # bar color else: self.lineprops = lineprops if not isinstance(nrect, list): nrect = [nrect] if stay_rectprops is None: cc = ['#984ea3', '#ffff33', '#d8b365', '#5ab4ac'] # green yellow blue red color = [cc[i % len(cc)] for i in range(0, len(nrect))] self.stay_rectprops = [dict(facecolor=c, alpha=0.5) for c in color] else: assert (len(nrect) == len(stay_rectprops)) self.stay_rectprops = stay_rectprops self.pressv = None self.onclick = onclick self.onmove_callback = onmove_callback self.minspan = minspan # Needed when dragging out of axes self.prev = (0, 0) # Reset canvas so that `new_axes` connects events. self.canvas = None self.new_axes(ax, nrect) def new_axes(self, ax, nrect): self.ax = ax if self.canvas is not ax.figure.canvas: if self.canvas is not None: self.disconnect_events() self.canvas = ax.figure.canvas self.connect_default_events() # span trans = blended_transform_factory(self.ax.transData, self.ax.transAxes) w, h = 0, 1 self.rect = Rectangle((0, 0), w, h, transform=trans, visible=False, animated=True, **self.rectprops) self.ax.add_patch(self.rect) self.artists = [self.rect] # stay rect self.stay_rects = [] for set in range(0, len(nrect)): self.stay_rects.append([]) for n in range(0, nrect[set]): stay_rect = Rectangle((0, 0), w, h, transform=trans, visible=False, animated=True, **self.stay_rectprops[set]) self.ax.add_patch(stay_rect) self.stay_rects[set].append(stay_rect) self.artists.extend(self.stay_rects[set]) # bar self.bar = ax.axvline(0, w, h, visible=False, **self.lineprops) self.artists.append(self.bar) def set_bar_position(self, x): self.bar.set_xdata(x) self.bar.set_visible(True) def set_stay_rects_x_bounds(self, xarr, set=0): for n, stay_rect in enumerate(self.stay_rects[set]): try: xmin, xmax = xarr[n] except IndexError: stay_rect.set_visible(False) else: stay_rect.set_x(xmin) stay_rect.set_y(self.rect.get_y()) stay_rect.set_width(abs(xmax - xmin)) stay_rect.set_height(self.rect.get_height()) stay_rect.set_visible(True) def set_stay_rect_visible(self, b=True, set=0): for stay_rect in self.stay_rects[set]: stay_rect.set_visible(b) def ignore(self, event): """return *True* if *event* should be ignored""" return _SelectorWidget.ignore(self, event) or not self.visible def _press(self, event): """on button press event""" xdata, ydata = self._get_data(event) self.pressv = xdata return False def _release(self, event): """on button release event""" if self.pressv is None: return self.buttonDown = False self.rect.set_visible(False) vmin = self.pressv xdata, ydata = self._get_data(event) vmax = xdata or self.prev[0] if vmin > vmax: vmin, vmax = vmax, vmin span = vmax - vmin if span < self.minspan and event.button == 3: # right click to remove span self.onclick(vmin) return elif span > self.minspan and event.button == 1: self.onselect(vmin, vmax) self.pressv = None return False elif span > self.minspan and event.button == 3: self.onselect(vmin, vmax, True) self.pressv = None return False def _onmove(self, event): self.rect.set_visible(self.visible) """on motion notify event""" if self.pressv is None: return x, y = self._get_data(event) if x is None: return self.prev = x, y v = x minv, maxv = v, self.pressv if minv > maxv: minv, maxv = maxv, minv self.rect.set_x(minv) self.rect.set_width(maxv - minv) if self.onmove_callback is not None: vmin = self.pressv xdata, ydata = self._get_data(event) vmax = xdata or self.prev[0] if vmin > vmax: vmin, vmax = vmax, vmin self.onmove_callback(vmin, vmax) if not self.update_on_ext_event: self.update() return False
def __init__(self, grid_obj, final_timestep): self.grid = grid_obj fig_ani, self.axes_ani = plt.subplots(2, 1, dpi=100, figsize=(12, 10)) self.final_timestep = final_timestep self.grid.timesteps = self.final_timestep self.axes_ani[0].xaxis.grid(linestyle='dotted') self.axes_ani[0].set_xlabel('Cell') self.axes_ani[0].set_ylabel(r'$E_z$', fontsize=12, rotation=0) self.axes_ani[1].xaxis.grid(linestyle='dotted') self.axes_ani[1].set_xlabel('Cell') self.axes_ani[1].set_ylabel(r'$H_y$', fontsize=12, rotation=0) self.line_0 = Line2D([], []) self.line_1 = Line2D([], []) self.axes_ani[0].add_line(self.line_0) self.axes_ani[1].add_line(self.line_1) self.axes_ani[0].set_xlim([0, self.grid.nx - 1]) self.axes_ani[1].set_xlim([0, self.grid.nx - 1]) self.axes_ani[0].set_ylim([-1.5, 1.5]) self.axes_ani[1].set_ylim( [-1.5 / np.sqrt(mu0 / eps0), 1.5 / np.sqrt(mu0 / eps0)]) for mat in self.grid.materials: media_repr_0 = Rectangle(xy=(mat.position[0] - 0.5, -1.4), height=2.8, width=(mat.position[-1] - mat.position[0] + 1), color='grey', fill=True, alpha=mat.eps * 0.12) self.axes_ani[0].add_patch(media_repr_0) if mat.model == 'Lorentz': s = r'$\epsilon(\omega)$' + '\n' + r'$\sigma$={0:.2f}'.format( mat.conductivity) else: s = r'$\epsilon_r$={0:.2f}'.format( mat.eps) + '\n' + r'$\sigma$={0:.2f}'.format( mat.conductivity) self.axes_ani[0].annotate(s, xy=(media_repr_0.get_x() + 0.1, media_repr_0.get_y() + 0.2), color='black') media_repr_1 = Rectangle(xy=(mat.position[0] - 0.5, -1.4), height=2.8, width=(mat.position[-1] - mat.position[0] + 1), color='grey', fill=True, alpha=mat.eps * 0.12) self.axes_ani[1].add_patch(media_repr_1) for src in self.grid.sources: src_repr = Rectangle(xy=(src.position - 0.5, -src.ampl), height=2 * src.ampl, width=1, color='red', alpha=0.3) self.axes_ani[0].add_patch(src_repr) for obs in self.grid.local_observers: if isinstance(obs, QuasiHarmonicObserver): obs_repr = Rectangle(xy=(obs.position - 0.5, -1.4), height=2.8, width=1, color='green', alpha=0.3) elif isinstance(obs, E_FFTObserver): obs_repr = Rectangle(xy=(obs.position - 0.5, -1.4), height=2.8, width=1, color='indigo', alpha=0.3) elif isinstance(obs, P_FFTObserver): obs_repr = Rectangle(xy=(obs.position - 0.5, -1.4), height=2.8, width=1, color='orange', alpha=0.3) self.axes_ani[0].add_patch(obs_repr) ani.TimedAnimation.__init__(self, fig_ani, blit=True, interval=5, repeat=False)
def drawHeatPlot(comparisonDescription, detector, layer, dof, outputDir, layerFillList, GeometryDict, diffColorRange, drawNames=False): fig = plt.figure(figsize=(11.6929134, 8.26771654), subplotpars=SubplotParams(wspace=0.35, left=0.1, bottom=0.1, right=0.98)) ax = fig.add_subplot(111, axisbg='#E6E6E6') ax.set_aspect("equal", adjustable="box") ax.set_title(comparisonDescription + "\n Detector: %s, Layer: %s, Degree of Freedom: %s" % (detector, layer, dof)) ax.set_xlabel( "A side $\qquad \qquad \qquad \qquad \qquad$ x (cm) $\qquad \qquad \qquad \qquad \qquad$ C side" ) # ax.set_xlabel("x (cm)") ax.set_ylabel("y (cm)") ax.grid(True, linestyle='-', linewidth=1.5, alpha=0.1) # put grid behind polygons ax.set_axisbelow(True) # reverse x axis to match LHCb coodrinates from VELO perspective ax.set_xlim(ax.get_xlim()[::-1]) patches = [] # values will be overwritten, we just need a numpy array at least as big as the fill list colorArray = np.array([x for x in range(len(layerFillList))], dtype=np.float64) stereoRotation = 0 if layer.find("U") != -1: stereoRotation = -5 if layer.find("V") != -1: stereoRotation = 5 logging.debug( "Building list of alignment elements and color array of corresponding alignment parameters" ) for i, (name, unused, matrix) in enumerate(layerFillList): _shape = lambda j: GeometryDict[name][ j] # (xy, width, height, rotateY, zorder) # nb: with x axis reversed, xy of rectangle is lower right point poly = Rectangle(_shape(0), _shape(1), _shape(2), zorder=_shape(4)) if stereoRotation != 0: rotate = mpl.transforms.Affine2D().rotate_deg_around( poly.get_x() + _shape(1) * 0.5, _shape(3), stereoRotation) poly.set_transform(rotate) patches.append(poly) colorArray[i] = getattr(matrix, dof) # element labels if drawNames: splitName = name.split("/") if detector == "TT": elementName = "\n".join(splitName[-3:]) labelRotation = 0 textSize = 4 elif detector == "IT": elementName = "\n".join(splitName[-3::2]) labelRotation = 90 textSize = 8 elif detector == "OT": elementName = "/".join(splitName[-2:]) labelRotation = 90 textSize = 10 smallAngleShift = 0 if stereoRotation != 0 and detector != "IT": tan = 0.08748866 smallAngleShift = -(poly.get_y() + _shape(2) * 0.5) * tan * cmp(stereoRotation, 0) elementLabel = plt.text(poly.get_x() + _shape(1) * 0.5 + smallAngleShift, poly.get_y() + _shape(2) * 0.5, elementName, verticalalignment='center', horizontalalignment='center', rotation=labelRotation - stereoRotation, size=textSize) polyCollection = PatchCollection(patches, cmap=mpl.cm.RdBu) polyCollection.set_array(colorArray) polyCollection.set_clim([-diffColorRange, diffColorRange]) ax.add_collection(polyCollection) cbar = plt.colorbar(polyCollection) if dof.startswith("T"): cbar.set_label("%s (mm)" % dof) elif dof.startswith("R"): cbar.set_label("%s (mrad)" % dof) plt.axis('equal') # this is busted for stereo layers, just putting the labels in the x axis title # if detector == "IT": # ax.text(ax.get_xlim()[0], 0, '$\quad$A side', horizontalalignment='left', verticalalignment='center') # ax.text(ax.get_xlim()[1], 0, '$\!\!\!$ C side', horizontalalignment='right', verticalalignment='center') # else: # ax.text(ax.get_xlim()[0], 0, 'A side $\qquad$', horizontalalignment='right', verticalalignment='center') # ax.text(ax.get_xlim()[1], 0, '$\quad$C side', horizontalalignment='left', verticalalignment='center') detectorOutputDir = os.path.join(*([outputDir] + [detector])) if not os.path.isdir(detectorOutputDir): os.makedirs(detectorOutputDir) layerName = layer.replace("/", "_") fileName = "_".join((detector, layerName, dof)) + ".pdf" outputPath = os.path.join(*([detectorOutputDir] + [fileName])) print " Writing %s" % outputPath fig.savefig(outputPath)
class SpanSelector(_SelectorWidget): """Custom SpanSelector.""" # pylint: disable=too-many-instance-attributes # pylint: disable=too-many-arguments # pylint: disable=attribute-defined-outside-init # pylint: disable=invalid-name def __init__(self, ax, onselect, direction, minspan=None, useblit=False, rectprops=None, onmove_callback=None, span_stays=False, button=None): _SelectorWidget.__init__(self, ax, onselect, useblit=useblit, button=button) if rectprops is None: rectprops = dict(facecolor='red', alpha=0.5) rectprops['animated'] = self.useblit if direction not in ['horizontal', 'vertical']: msg = "direction must be in [ 'horizontal' | 'vertical' ]" raise ValueError(msg) self.direction = direction self.rect = None self.pressv = None self.rectprops = rectprops self.onmove_callback = onmove_callback self.minspan = minspan self.span_stays = span_stays # Needed when dragging out of axes self.prev = (0, 0) # Reset canvas so that `new_axes` connects events. self.canvas = None self.new_axes(ax) def new_axes(self, ax): """Set SpanSelector to operate on a new Axes""" self.ax = ax if self.canvas is not ax.figure.canvas: if self.canvas is not None: self.disconnect_events() self.canvas = ax.figure.canvas self.connect_default_events() if self.direction == 'horizontal': trans = blended_transform_factory(self.ax.transData, self.ax.transAxes) w, h = 0, 2 else: trans = blended_transform_factory(self.ax.transAxes, self.ax.transData) w, h = 1, 0 self.rect = Rectangle((0, -0.5), w, h, transform=trans, visible=False, **self.rectprops) if self.span_stays: self.stay_rect = Rectangle((0, 0), w, h, transform=trans, visible=False, **self.rectprops) self.stay_rect.set_animated(False) self.ax.add_patch(self.stay_rect) self.ax.add_patch(self.rect) self.artists = [self.rect] def set_rectprops(self, rectprops): """Custom: set new rectprops.""" self.rectprops = rectprops self.new_axes(self.ax) def ignore(self, event): """return *True* if *event* should be ignored""" return _SelectorWidget.ignore(self, event) or not self.visible def _press(self, event): """on button press event""" if self.ignore(event): return True self.rect.set_visible(self.visible) if self.span_stays: self.stay_rect.set_visible(False) # really force a draw so that the stay rect is not in # the blit background if self.useblit: self.canvas.draw() xdata, ydata = self._get_data(event) if self.direction == 'horizontal': self.pressv = xdata else: self.pressv = ydata return False def _release(self, event): """on button release event""" if self.ignore(event): return True if self.pressv is None: return True self.buttonDown = False self.rect.set_visible(False) if self.span_stays: self.stay_rect.set_x(self.rect.get_x()) self.stay_rect.set_y(self.rect.get_y()) self.stay_rect.set_width(self.rect.get_width()) self.stay_rect.set_height(self.rect.get_height()) self.stay_rect.set_visible(True) self.canvas.draw_idle() vmin = self.pressv xdata, ydata = self._get_data(event) if self.direction == 'horizontal': vmax = xdata or self.prev[0] else: vmax = ydata or self.prev[1] if vmin > vmax: vmin, vmax = vmax, vmin span = vmax - vmin if self.minspan is not None and span < self.minspan: return True self.onselect(vmin, vmax) self.pressv = None return False def _onmove(self, event): """on motion notify event""" if self.ignore(event): return True if self.pressv is None: return True x, y = self._get_data(event) if x is None: return True self.prev = x, y if self.direction == 'horizontal': v = x else: v = y minv, maxv = v, self.pressv if minv > maxv: minv, maxv = maxv, minv if self.direction == 'horizontal': self.rect.set_x(minv) self.rect.set_width(maxv - minv) else: self.rect.set_y(minv) self.rect.set_height(maxv - minv) if self.onmove_callback is not None: vmin = self.pressv xdata, ydata = self._get_data(event) if self.direction == 'horizontal': vmax = xdata or self.prev[0] else: vmax = ydata or self.prev[1] if vmin > vmax: vmin, vmax = vmax, vmin self.onmove_callback(vmin, vmax) self.update() return False
class identificationWidget(QMainWindow): def __init__(self): super().__init__() self.peakThreshold = 0.01 self.peakNumber = 50 self.peakDistance = 2 self.pickDistance = 1 self.wavelengthPixelList = pd.DataFrame({ 'Wavelength': [' '], 'Pixel': [' '] }) self.selfPeakPixs = [] self.standardPeakWavelengths = [] self.matchedPeakPixs = [] self.isPressed = False self.isPicked = False self.isMatchFinished = True self.standardSpectrum = [] self.selfSpectrum = [] self.currentPickedPeakWavelength = 0 self.selfFWHM = 2 self.REIDYStep = 2 self.selfImageY = [0, 0] self.selfData = [] self.reidentificationWidget = reIdentificationWidget( matchList=self.wavelengthPixelList, flux=self.selfData, FWHM=self.selfFWHM) self.initUI() def initUI(self): self.layout = QGridLayout() self.mainWidget = QWidget() self.calibrationFileOpenAction = QAction('CalibrationFileOpen', self) self.calibrationFileOpenAction.setShortcut('Ctrl+C') self.calibrationFileOpenAction.triggered.connect( self.onCalibnationFileOpen) self.calibrationFileOpenAction.setStatusTip('Open calibration image') #직접 찍은 아이덴티피케이션 이미지의 스펙트럼을 보여주는 fig self.selfSpectrumCanvas = FigureCanvas(Figure(figsize=(13, 5))) self.selfSpectrumCanvas.figure.clear() self.peakNumberSlider = QSlider(Qt.Horizontal, self) self.peakNumberSlider.setValue(self.peakNumber) self.peakNumberSlider.setRange(1, 100) self.peakDistanceSlider = QSlider(Qt.Horizontal, self) self.peakDistanceSlider.setValue(self.peakDistance) self.peakNumberSlider.setRange(1, 10) self.peakThresholdSlider = QSlider(Qt.Horizontal, self) self.peakThresholdSlider.setValue(int(self.peakThreshold * 100)) self.peakNumberSlider.setRange(1, 100) self.peakNumberLabel = QLabel(f'Number of Peak = {self.peakNumber}') self.peakDistanceLabel = QLabel( f'Distance between Peak = {self.peakDistance}') self.peakThresholdLabel = QLabel( f'Threshold of peak = {self.peakThreshold}') self.peakNumberSlider.valueChanged.connect( self.onPeakNumberValueChanged) self.peakDistanceSlider.valueChanged.connect( self.onPeakDistanceValueChanged) self.peakThresholdSlider.valueChanged.connect( self.onPeakThresholdValueChanged) self.selfPeakControl = QWidget() self.peakControlLayout = QVBoxLayout() self.peakControlLayout.addWidget(self.peakNumberLabel) self.peakControlLayout.addWidget(self.peakNumberSlider) self.peakControlLayout.addWidget(self.peakDistanceLabel) self.peakControlLayout.addWidget(self.peakDistanceSlider) self.peakControlLayout.addWidget(self.peakThresholdLabel) self.peakControlLayout.addWidget(self.peakThresholdSlider) self.selfPeakControl.setLayout(self.peakControlLayout) # 직접 찍은 아이덴티피케이션 이미지를 보여주는 fig self.selfImageCanvas = FigureCanvas(Figure(figsize=(5, 2))) self.selfImageCanvas.mpl_connect("button_press_event", self.onPressAtImage) self.selfImageCanvas.mpl_connect("motion_notify_event", self.onMoveAtImage) self.selfImageCanvas.mpl_connect("button_release_event", self.onReleaseAtImage) self.selfSpectrumCanvas.mpl_connect('scroll_event', self.onScrollAtSelfSpectrum) self.selfSpectrumCanvas.mpl_connect('pick_event', self.onPickPeakAtSelfSpectrum) self.selfSpectrumCanvas.mpl_connect("button_press_event", self.onPressAtSelfSpectrum) self.selfSpectrumCanvas.mpl_connect("motion_notify_event", self.onMoveAtSelfSpectrum) self.selfSpectrumCanvas.mpl_connect("button_release_event", self.onReleaseAtSelfSpectrum) self.selfSpectrumGaussFitCanvas = FigureCanvas(Figure(figsize=(7, 7))) self.gaussFitWidget = QWidget() self.gaussFitLayout = QVBoxLayout() self.gaussFitButton = QPushButton('&Yes') self.gaussFitButton.clicked.connect(self.onGaussFitButtonClicked) self.FWHMSlider = QSlider(Qt.Horizontal, self) self.FWHMSlider.setValue(self.selfFWHM * 10) self.FWHMSlider.setRange(1, 100) self.FWHMLabel = QLabel(f'FHWM for comp image = {self.selfFWHM}') self.FWHMSlider.valueChanged.connect(self.onFWHMChanged) self.gaussFitLayout.addWidget(self.selfSpectrumGaussFitCanvas) self.gaussFitLayout.addWidget(self.FWHMSlider) self.gaussFitLayout.addWidget(self.FWHMLabel) self.gaussFitLayout.addWidget(self.gaussFitButton) self.gaussFitWidget.setLayout(self.gaussFitLayout) self.NeonArcButton = QPushButton('&Neon') self.NeonArcButton.clicked.connect(self.neonSpectrumDraw) self.OpenArcButton = QPushButton('&Open') self.standardSpectrumButtonLayout = QVBoxLayout() self.standardSpectrumButtonLayout.addWidget(self.NeonArcButton) self.standardSpectrumButtonLayout.addWidget(self.OpenArcButton) self.standardSpectrumButton = QWidget() self.standardSpectrumButton.setLayout( self.standardSpectrumButtonLayout) #비교할 아이덴티피케이션의 스펙트럼을 보여주는 fig self.standardSpectrumCanvas = FigureCanvas(Figure(figsize=(13, 5))) self.standardSpectrumCanvas.mpl_connect( 'scroll_event', self.onScrollAtStandardSpectrum) self.standardSpectrumCanvas.mpl_connect( 'pick_event', self.onPickPeakAtStandardSpectrum) self.standardSpectrumCanvas.mpl_connect('button_press_event', self.onPressAtStandardSpectrum) self.wavelengthPixelTable = QTableView() self.wavelengthPixelModel = tableModel(self.wavelengthPixelList) self.wavelengthPixelTable.setModel(self.wavelengthPixelModel) self.wavelengthPixelTable.setSelectionBehavior(QTableView.SelectRows) self.wavelengthPixelTable.doubleClicked.connect( self.onWavelengthPixelTableDoubleClicked) self.gaussButton = QPushButton('&GuassFit') self.gaussButton.clicked.connect(self.selfSpectrumDrawWithGauss) self.matchButton = QPushButton('&Match') self.matchButton.clicked.connect(self.onMatch) self.abortButton = QPushButton('&Abort') self.abortButton.clicked.connect(self.onAbort) self.exportButton = QPushButton('&Export') self.exportButton.clicked.connect(self.onExport) self.importButton = QPushButton('&Import') self.importButton.clicked.connect(self.onImport) self.tableMatchingButtons = QWidget() self.tableMatchingButtonLayout = QVBoxLayout() self.tableMatchingButtonLayout.addWidget(self.matchButton) self.tableMatchingButtonLayout.addWidget(self.abortButton) self.tableMatchingButtonLayout.addWidget(self.exportButton) self.tableMatchingButtonLayout.addWidget(self.importButton) self.tableMatchingButtons.setLayout(self.tableMatchingButtonLayout) self.setCentralWidget(self.mainWidget) menubar = self.menuBar() menubar.setNativeMenuBar(False) filemenu = menubar.addMenu('&File') #&는 File을 Alt F로 실행하게 해준다 filemenu.addAction(self.calibrationFileOpenAction) self.splitter = QSplitter(Qt.Horizontal) self.tables = QWidget() self.tableLayout = QVBoxLayout() self.tableLayout.addWidget(self.gaussButton) self.tableLayout.addWidget(self.wavelengthPixelTable) self.tableLayout.addWidget(self.tableMatchingButtons) self.tables.setLayout(self.tableLayout) self.splitter.addWidget(self.tables) self.spectrums = QWidget() self.spectrumsLayout = QVBoxLayout() self.spectrumsLayout.addWidget(self.selfSpectrumCanvas) self.spectrumsLayout.addWidget(self.standardSpectrumCanvas) self.spectrums.setLayout(self.spectrumsLayout) self.splitter.addWidget(self.spectrums) self.reidentificationBtn = QPushButton('&Reidentification') self.reidentificationBtn.clicked.connect(self.onReidentification) self.layout.addWidget(self.splitter, 1, 0, 3, 1) self.layout.addWidget(self.selfImageCanvas, 1, 1, 1, 1) self.layout.addWidget(self.selfPeakControl, 2, 1, 1, 1) self.layout.addWidget(self.standardSpectrumButton, 3, 1) self.layout.addWidget(self.reidentificationBtn, 4, 0, 1, -1) self.mainWidget.setLayout(self.layout) self.setCentralWidget(self.mainWidget) self.resize(1500, 800) self.center() ''' 칼리브레이션 파일(comp 파일)을 열고 Identification에 사용될 Y방향(Wavelength에 수직한 방향) 구간을 결정하는 메소드 ''' def onCalibnationFileOpen(self): filePath = QFileDialog.getOpenFileName( self, 'Open calibration file', './Spectroscopy_Example/20181023/combine/')[0] hdr, data = openFitData(filePath) self.selfImageCanvas.figure.clear() self.selfImageAx = self.selfImageCanvas.figure.add_subplot(111) zimshow(self.selfImageAx, data) self.selfImageCanvas.draw() self.imageWidth = int(data.shape[1]) self.selfData = data ''' 칼리브레이션파일 스펙트럼에서 Peak을 찾는 과정에 관여하는 3가지 initial value(number of peaks, distance btw peaks, threshol of peaks)를 조정해서 적절히 Peak을 찾을 수 있게 하는 메소드 Slider의 값을 받아서 canvas에 적용한다. ''' def onPeakNumberValueChanged(self, val): self.peakNumber = val self.peakNumberLabel.setText(f'Number of Peak = {self.peakNumber}') self.selfSpectrumDraw( ymin=self.selfImageY[0], ymax=self.selfImageY[1], data=self.selfData, args=[self.peakDistance, self.peakThreshold, self.peakNumber]) def onPeakDistanceValueChanged(self, val): self.peakDistance = val self.peakDistanceLabel.setText( f'Distance between Peak = {self.peakDistance}') self.selfSpectrumDraw( ymin=self.selfImageY[0], ymax=self.selfImageY[1], data=self.selfData, args=[self.peakDistance, self.peakThreshold, self.peakNumber]) def onPeakThresholdValueChanged(self, val): self.peakThreshold = val / 100 self.peakThresholdLabel.setText( f'Threshold of peak = {self.peakThreshold}') self.selfSpectrumDraw( ymin=self.selfImageY[0], ymax=self.selfImageY[1], data=self.selfData, args=[self.peakDistance, self.peakThreshold, self.peakNumber]) ''' 칼리브레이션 스펙트럼 그래프를 확대/축소하는 메소드. 스크롤을 내리면 마우스 위치를 중심으로 xlim이 4/5배가 되고, 스크롤을 올리면 마우스 위치를 중심으로 xlim이 5/4배가 된다. ''' def onScrollAtSelfSpectrum(self, event): xmin, xmax = self.selfSpectrumAx.get_xlim() xnow = event.xdata if (event.button == 'up'): xsize = int((xmax - xmin) * 0.40) xmin = xnow - xsize xmax = xnow + xsize self.selfSpectrumAx.set_xlim(xmin, xmax) self.selfSpectrumAx.figure.canvas.draw() elif (event.button == 'down'): xsize = int((xmax - xmin) * 0.625) xmin = xnow - xsize xmax = xnow + xsize self.selfSpectrumAx.set_xlim(xmin, xmax) self.selfSpectrumAx.figure.canvas.draw() ''' 칼리브레이션 스펙트럼 그래프에서 pickPeak 메소드를 통해 생성된 peakPicker를 움직이고 그 값을 table에 저장하는 메소드 두 가지 방식으로 peakPicker를 움직일 수 있다. 1. Drag and Drop : peakPicker를 클릭한 채로 끌어서 움직일 수 있고 마우스를 놓으면 위치가 고정된다. peak 근처 peakDistance 픽셀에서는 자동으로 peak에 붙고 이때 색깔이 연두색으로 바뀐다. 마우스가 이동할때 그 픽셀값이 저장된다. 2. Double Click : selfSpectrum의 Peak의 text를 더블클릭하면 peakPicker 그 text로 이동하고 그 픽셀값이 저장된다. ''' def onPickPeakAtSelfSpectrum(self, event): if self.isMatchFinished: return if self.isPicked: return if (event.mouseevent.dblclick and event.artist != self.peakPicker): val = round(float(event.artist.get_text()), 4) self.wavelengthPixelList.loc[self.wavelengthPixelList.Wavelength == self.currentPickedPeakWavelength, 'Pixel'] = val self.peakPicker.remove() self.peakPicker = self.selfSpectrumAx.axvline( val, color='green', picker=True, pickradius=self.pickDistance) self.selfSpectrumAx.figure.canvas.draw() self.onChangedList() return if not event.mouseevent.button == 1: return self.isPicked = True def onMoveAtSelfSpectrum(self, event): if not event.inaxes: return if event.inaxes != self.selfSpectrumAx: return if not self.isPicked: return self.peakPicker.remove() dist = np.min(np.abs(self.selfPeakPixs - event.xdata)) val = self.selfPeakPixs[np.argmin( np.abs(self.selfPeakPixs - event.xdata))] if (dist < self.peakDistance): self.peakPicker = self.selfSpectrumAx.axvline( val, color='green', picker=True, pickradius=self.pickDistance) self.wavelengthPixelList.loc[self.wavelengthPixelList.Wavelength == self.currentPickedPeakWavelength, 'Pixel'] = val else: self.peakPicker = self.selfSpectrumAx.axvline( event.xdata, color='blue', picker=True, pickradius=self.pickDistance) self.wavelengthPixelList.loc[self.wavelengthPixelList.Wavelength == self.currentPickedPeakWavelength, 'Pixel'] = int(event.xdata) self.selfSpectrumAx.figure.canvas.draw() self.onChangedList() def onReleaseAtSelfSpectrum(self, event): if not event.inaxes: return if event.inaxes != self.selfSpectrumAx: return if not self.isPicked: return self.isPicked = False ''' 칼리브레이션 스펙트럼 그래프에서 우클릭을 하면 pickPeak과정을 취소하는 메소드 우클릭을 하면 self.onPickDisable을 호출한다. ''' def onPressAtSelfSpectrum(self, event): if (event.button == 3): self.onPickDisable() ''' 스탠다드 스펙트럼 그래프를 확대/축소하는 메소드. 스크롤을 내리면 마우스 위치를 중심으로 xlim이 4/5배가 되고, 스크롤을 올리면 마우스 위치를 중심으로 xlim이 5/4배가 된다. ''' def onScrollAtStandardSpectrum(self, event): xmin, xmax = self.standardSpectrumAx.get_xlim() xnow = event.xdata if (event.button == 'up'): xsize = int((xmax - xmin) * 0.40) xmin = xnow - xsize xmax = xnow + xsize self.standardSpectrumAx.set_xlim(xmin, xmax) self.standardSpectrumAx.figure.canvas.draw() elif (event.button == 'down'): xsize = int((xmax - xmin) * 0.625) xmin = xnow - xsize xmax = xnow + xsize self.standardSpectrumAx.set_xlim(xmin, xmax) self.standardSpectrumAx.figure.canvas.draw() ''' pickPeak을 시작하기 위한 조건을 나타낸 메소드들. standard spectrum 그래프에서 peak wavelength text를 더블클릭하거나 왼쪽 테이블에서 wavelength를 더블클릭하면 그 wavelength에 맞는 pickPeak이 실행된다. ''' def onPickPeakAtStandardSpectrum(self, event): if (event.mouseevent.dblclick): self.pickPeak(event.artist.get_position()[0], self.standardSpectrum, event.mouseevent) def onWavelengthPixelTableDoubleClicked(self, index): row = index.row() wavelength = self.standardPeakWavelengths[row] self.pickPeak(wavelength, self.standardSpectrum) ''' peak wavelength에 맞는 peak Pixel을 찾기 위한 메소드 선택된 wavelength와 그 peak의 axvline를 파란색으로 강조해서 보여주고 칼리브레이션 스펙트럼 그래프의 중간이나 그래프상의 같은 위치에 움직일수 있는 peakPicker를 생성해 해당 wavelength에 맞는 peak Pixel을 찾을 수 있도록 한다. ''' def pickPeak(self, waveNow, spectrum, mouse=None): if not self.isMatchFinished: return wavelength = spectrum[0] flux = spectrum[1] fluxNow = flux[np.where(wavelength == waveNow)][0] self.pickedPeak = self.standardSpectrumAx.axvline(waveNow, color='blue') if (fluxNow + max(flux) / 2.85 > max(flux)): self.pickedText = self.standardSpectrumAx.text( waveNow, fluxNow + max(flux) / 2000, waveNow, c='blue', bbox=dict(facecolor='white', ec='none')) else: self.pickedText = self.standardSpectrumAx.text( waveNow, fluxNow + max(flux) / 2.85, waveNow, ha='center', va='center', rotation=90, clip_on=True, c='blue', bbox=dict(facecolor='white', ec='none')) if (mouse is None): xshift = [(self.selfSpectrumAx.get_xlim()[1] - self.selfSpectrumAx.get_xlim()[0]) / 2 + self.selfSpectrumAx.get_xlim()[0], 0] else: xshift = self.selfSpectrumAx.transData.inverted().transform( (mouse.x, 0)) self.peakPicker = self.selfSpectrumAx.axvline( xshift[0], color='blue', picker=True, pickradius=self.pickDistance) self.selfSpectrumAx.figure.canvas.draw() self.standardSpectrumAx.figure.canvas.draw() self.currentPickedPeakWavelength = waveNow self.isMatchFinished = False ''' pickPeak을 완료하기 위한 메소드 매치가 완료되었으면(onMatch) 해당 내용을 리스트에 저장하고 강제종료시(onAbort) 저장하지 않는다. 테이블 아래있는 버튼 (Match, Abort)을 클릭하거나 selfSpectrumFigure 위에서 키(m on Match, a on Abort)를 누르면 완료된다. ''' def keyPressEvent(self, event): if self.isMatchFinished: return elif event.key() == Qt.Key_M: self.onMatch() elif event.key() == Qt.Key_A: self.onAatch() def onMatch(self): print('match') self.onPickDisable() self.onChangedList() def onAbort(self): self.wavelengthPixelList.loc[self.wavelengthPixelList.Wavelength == self.currentPickedPeakWavelength, 'Pixel'] = 0 self.onPickDisable() self.onChangedList() def onExport(self): path = QFileDialog.getSaveFileName( self, 'Choose save file location and name ', './', "CSV files (*.csv)")[0] self.wavelengthPixelList.to_csv(path, index=False) def onImport(self): file = QFileDialog.getOpenFileName(self, 'Choose match file', './', "CSV files (*.csv)")[0] self.wavelengthPixelList = pd.read_csv(file) self.onChangedList() def onPressAtStandardSpectrum(self, event): if (event.button == 3): self.onPickDisable() def onPickDisable(self): self.pickedPeak.remove() self.pickedText.remove() self.peakPicker.remove() self.selfSpectrumAx.figure.canvas.draw() self.standardSpectrumAx.figure.canvas.draw() self.isMatchFinished = True ''' onCalibrationOpen 메소드로 열린 comp 이미지 파일에서 사용할 이미지의 y 축 범위를 찾는 메소드. 마우스 클릭후 끌어서 범위를 결정하면 selfSpectrumDraw에서 ''' def onPressAtImage(self, event): if not event.inaxes: return if event.inaxes != self.selfImageAx: return self.rect = Rectangle((0, 0), 1, 1, alpha=0.5) self.selfImageAx.add_patch(self.rect) self.x0 = event.xdata self.y0 = event.ydata self.isPressed = True def onMoveAtImage(self, event): if not event.inaxes: return if event.inaxes != self.selfImageAx: return if not self.isPressed: return self.x1 = event.xdata self.y1 = event.ydata self.rect.set_width(self.imageWidth) self.rect.set_height(self.y1 - self.y0) self.rect.set_xy((0, self.y0)) self.selfImageAx.figure.canvas.draw() def onReleaseAtImage(self, event): if not event.inaxes: return if event.inaxes != self.selfImageAx: return if not self.isPressed: return y = int(self.rect.get_y()) height = int(self.rect.get_height()) self.rect.remove() self.selfImageAx.figure.canvas.draw() if (height < 0): height = 0 - height self.selfImageY = np.array([y, y + height]) self.selfSpectrumDraw( ymin=y, ymax=y + height, data=self.selfData, args=[self.peakDistance, self.peakThreshold, self.peakNumber]) self.isPressed = False def selfSpectrumDrawWithGauss(self): self.selfSpectrumGaussShow(self.selfSpectrum, self.selfPeakPixs) def selfSpectrumDraw(self, ymin, ymax, data, args): MINSEP_PK = args[0] # minimum separation of peaks MINAMP_PK = args[ 1] # fraction of minimum amplitude (wrt maximum) to regard as peak NMAX_PK = args[2] self.selfSpectrumCanvas.figure.clear() self.selfSpectrumAx = self.selfSpectrumCanvas.figure.add_subplot(111) identify = np.average(data[ymin:ymax, :], axis=0) ground = np.median(identify[0:200]) max_intens = np.max(identify) peakPixs = peak_local_max(identify, indices=True, num_peaks=NMAX_PK, min_distance=MINSEP_PK, threshold_abs=max_intens * MINAMP_PK + ground) newPeakPixs = [] for peakPix in peakPixs: newPeakPixs.append(peakPix[0]) peakPixs = newPeakPixs self.selfPeakPixs = np.array(peakPixs) self.selfSpectrum = np.array(identify) for i in peakPixs: self.selfSpectrumAx.axvline(i, identify[i] / max(identify) + 0.0003, identify[i] / max(identify) + 0.2, color='c') if (identify[i] + max(identify) / 2.85 > max(identify)): self.selfSpectrumAx.text(i, identify[i] + max(identify) / 2000, str(i), clip_on=False, picker=self.pickDistance) else: self.selfSpectrumAx.text(i, identify[i] + max(identify) / 2.85, str(i), ha='center', va='center', rotation=90, clip_on=True, picker=self.pickDistance) self.selfSpectrumAx.plot(identify, color='r') self.selfSpectrumAx.set_xlim(0, len(identify)) self.selfSpectrumAx.set_ylim(0, ) self.selfSpectrumAx.figure.canvas.draw() # 가우스핏을 통해 peak의 정확한 픽셀값을 찾는다. # 값이 제일 큰 3개의 peak 스펙트럼과 그 가우스핏을 예시로 보여주고 특히 FWHM값을 모르거나 추측해야 할 경우 # FWHM값을 변경하면서 가우스핏이 제대로 되었는지 def selfSpectrumGaussShow(self, identify, peakPixs): self.gaussFitWidget.show() self.gaussFitWidget.raise_() self.selfSpectrumGaussDraw(identify, peakPixs) #Todo 이거 잘 빼는 방법(바닥값에 맞게 잘 빼는 방법)을 찾아보자. def selfSpectrumGaussDraw(self, identify, peakPixs): iterations = 3 fitter = LevMarLSQFitter() self.selfSpectrumGaussFitCanvas.figure.clear() identify = identify - np.median(identify[0:100]) ##여기! for i in np.arange(iterations): a = int(iterations / 5) if iterations % 5 != 0: a = a + 1 ax = self.selfSpectrumGaussFitCanvas.figure.add_subplot( a, 5, i + 1) peakPix = peakPixs[-i] xs = np.arange(peakPix - int(self.selfFWHM) * 5, peakPix + int(self.selfFWHM) * 5 + 1) g_init = Gaussian1D(amplitude=identify[peakPix], mean=peakPix, stddev=self.selfFWHM * gaussian_fwhm_to_sigma, bounds={ 'amplitude': (0, 2 * identify[peakPix]), 'mean': (peakPix - self.selfFWHM, peakPix + self.selfFWHM), 'stddev': (0, self.selfFWHM) }) ax.set_ylim(0, max(identify) * 1.1) fitted = fitter(g_init, xs, identify[xs]) ax.set_xlim(peakPix - fitted.stddev / gaussian_fwhm_to_sigma * 2, peakPix + fitted.stddev / gaussian_fwhm_to_sigma * 2) ax.plot(xs, identify[xs], 'b') xss = np.arange(peakPix - self.selfFWHM * 5, peakPix + self.selfFWHM * 5 + 1, 0.01) ax.plot(xss, fitted(xss), 'r--') ax.figure.canvas.draw() def selfSpectrumGaussFit(self, identify, peakPixs): self.selfSpectrumCanvas.figure.clear() self.selfSpectrumAx = self.selfSpectrumCanvas.figure.add_subplot(111) fitter = LevMarLSQFitter() sortedPeakPixs = np.sort(peakPixs) ground = np.median(identify[0:100]) identify_fit = identify - ground ## 여기도! peak_gauss = [] i = 0 x_identify = np.arange(len(identify_fit)) for peakPix in peakPixs: g_init = Gaussian1D(amplitude=identify_fit[peakPix], mean=peakPix, stddev=self.selfFWHM * gaussian_fwhm_to_sigma, bounds={ 'amplitude': (identify_fit[peakPix], 2 * identify_fit[peakPix]), 'mean': (peakPix - self.selfFWHM, peakPix + self.selfFWHM), 'stddev': (0, self.selfFWHM) }) fitted = fitter(g_init, x_identify, identify_fit) xss = np.arange(peakPix - int(self.selfFWHM) * 3, peakPix + int(self.selfFWHM) * 3 + 1, 0.01) self.selfSpectrumAx.plot(xss, fitted(xss) + ground, 'royalblue') peak_gauss.append(fitted.mean.value) identify_fit = identify_fit - fitted(np.arange(len(identify))) ''' while i < len(sortedPeakPixs)-1: peakPix = sortedPeakPixs[i] try: peakPix2 = sortedPeakPixs[i + 1] except: peakPix2 = int(peakPix + self.selfFWHM * 10) xs = np.arange(peakPix - int(self.selfFWHM) * 3, peakPix2 + int(self.selfFWHM) * 3 + 1) g_init = Gaussian1D(amplitude=identify_fit[peakPix], mean=peakPix, stddev=self.selfFWHM * gaussian_fwhm_to_sigma, bounds={'amplitude': (0, 2 * identify_fit[peakPix]), 'mean': (peakPix - self.selfFWHM, peakPix + self.selfFWHM), 'stddev': (0, self.selfFWHM)} )+\ Gaussian1D(amplitude=identify_fit[peakPix2], mean=peakPix2, stddev=self.selfFWHM * gaussian_fwhm_to_sigma, bounds={'amplitude': (0, 2 * identify_fit[peakPix2]), 'mean': (peakPix2 - self.selfFWHM, peakPix2 + self.selfFWHM), 'stddev': (0, self.selfFWHM)} ) fitted = fitter(g_init, xs, identify_fit[xs]) # fit 한 두 값이 mean 차이가 시그마의 합의 3배 보다 크면 1D fit if (fitted.mean_1.value - fitted.mean_0.value > 3* ( fitted.stddev_1 + fitted.stddev_0) ) : xs = np.arange(peakPix - int(self.selfFWHM) * 3, peakPix + int(self.selfFWHM) * 3 + 1) g_init = Gaussian1D(amplitude=identify_fit[peakPix], mean=peakPix, stddev=self.selfFWHM * gaussian_fwhm_to_sigma, bounds={'amplitude': (0, 2 * identify_fit[peakPix]), 'mean': (peakPix - self.selfFWHM, peakPix + self.selfFWHM), 'stddev': (0, self.selfFWHM)} ) fitted = fitter(g_init, xs, identify_fit[xs]) xss = np.arange(peakPix - int(self.selfFWHM) * 3, peakPix + int(self.selfFWHM) * 3 + 1, 0.01) self.selfSpectrumAx.plot(np.arange(len(identify)), fitted(np.arange(len(identify))) + ground, 'royalblue') peak_gauss.append(fitted.mean.value) i+=1 else: peak_gauss.append(fitted.mean_0.value) peak_gauss.append(fitted.mean_1.value) xss = np.arange(peakPix - int(self.selfFWHM) * 3, peakPix2 + int(self.selfFWHM) * 3 + 1, 0.01) self.selfSpectrumAx.plot(xss, fitted(xss) + ground, 'yellowgreen') i += 2 print(len(peak_gauss)) print(len(sortedPeakPixs)) ''' peak_gauss = np.round(peak_gauss, 4) for i, j in zip(peakPixs, peak_gauss): self.selfSpectrumAx.axvline(j, identify[i] / max(identify) + 0.0003, identify[i] / max(identify) + 0.2, color='c') if (identify[i] + max(identify) / 2.85 > max(identify)): self.selfSpectrumAx.text(j, identify[i] + max(identify) / 2000, str(j), clip_on=False, picker=self.pickDistance) else: self.selfSpectrumAx.text(j, identify[i] + max(identify) / 2.85, str(j), ha='center', va='center', rotation=90, clip_on=True, picker=self.pickDistance) self.selfSpectrumAx.plot(identify, 'r--') self.selfSpectrumAx.set_xlim(0, len(identify)) self.selfSpectrumAx.set_ylim(0, ) self.selfPeakPixs = np.array(peak_gauss) self.selfSpectrumAx.figure.canvas.draw() self.gaussFitWidget.close() def onFWHMChanged(self, val): self.selfFWHM = val / 10 self.FWHMLabel.setText(f'FHWM for comp image = {self.selfFWHM}') self.selfSpectrumGaussDraw(self.selfSpectrum, self.selfPeakPixs) def onGaussFitButtonClicked(self): self.selfSpectrumGaussFit(self.selfSpectrum, self.selfPeakPixs) def neonSpectrumDraw(self): filePath = './NeonArcSpectrum.fit' hdr, data = openFitData(filePath) self.standardSpectrumDraw(data=data, arc='Neon') def standardSpectrumDraw(self, data, arc, peaks=[]): wavelength = data[0] flux = data[1] self.standardSpectrumCanvas.figure.clear() self.standardSpectrumAx = self.standardSpectrumCanvas.figure.add_subplot( 111) if (arc == 'Neon'): peaks = [ 5330.8000, 5400.5620, 5764.4180, 5852.4878, 5944.8342, 6029.9971, 6074.3377, 6096.1630, 6143.0623, 6163.5939, 6217.2813, 6266.4950, 6304.7892, 6334.4279, 6382.9914, 6402.2460, 6506.5279, 6532.8824, 6598.9529, 6717.0428, 6929.4680, 7032.4127, 7173.9390, 7245.1670, 7438.8990, 7488.8720, 7535.7750, 8082.4580, 8377.6070 ] for i in peaks: self.standardSpectrumAx.axvline( i, flux[np.where(i == wavelength)][0] / max(flux) + 0.003, flux[np.where(i == wavelength)] / max(flux) + 0.2, color='c') if (flux[np.where(i == wavelength)][0] + max(flux) / 2.85 > max(flux)): self.standardSpectrumAx.text( i, flux[np.where(i == wavelength)][0] + max(flux) / 2000, str(i), clip_on=False, picker=self.pickDistance) else: self.standardSpectrumAx.text( i, flux[np.where(i == wavelength)][0] + max(flux) / 2.85, str(i), ha='center', va='center', rotation=90, clip_on=True, picker=self.pickDistance) self.standardSpectrumAx.plot(wavelength, flux, 'r--') self.standardSpectrumAx.set_ylim(0, ) self.standardSpectrumAx.set_xlim(min(wavelength), max(wavelength)) self.standardPeakWavelengths = np.array(peaks) self.standardSpectrum = data self.matchedPeakPixs = np.zeros( (self.standardPeakWavelengths.shape[0])) matchInfo = np.column_stack( (self.standardPeakWavelengths, self.matchedPeakPixs)) self.wavelengthPixelList = pd.DataFrame( matchInfo, columns=['Wavelength', 'Pixel']) self.onChangedList() self.standardSpectrumAx.figure.canvas.draw() def onReidentification(self): self.reidentificationWidget.setReidentifier( matchList=self.wavelengthPixelList, flux=self.selfData, fitMethod='linear', FWHM=self.selfFWHM) self.reidentificationWidget.show() self.reidentificationWidget.raise_() def onChangedList(self): self.wavelengthPixelModel = tableModel(self.wavelengthPixelList) self.wavelengthPixelTable.setModel(self.wavelengthPixelModel) def onButtonClicked(self, status): self.bottonSinal.emit(status) def center(self): qr = self.frameGeometry() cp = QDesktopWidget().availableGeometry().center() qr.moveCenter(cp) self.move(qr.topLeft())
class cropWidget(QWidget): cropDoneSignal = pyqtSignal(cropInfo) def __init__(self, currentFileLocation = ''): super().__init__() self.filename = currentFileLocation self.cropInfo = cropInfo() self.isPressed = False self.cropCheckWidget = cropCheckWidget(self.cropInfo) self.initUI() def initUI(self): self.hbox = QHBoxLayout() self.fig = plt.Figure() self.canvas = FigureCanvas(self.fig) self.ax = self.fig.add_subplot(111) self.canvas.mpl_connect("button_press_event", self.on_press) self.canvas.mpl_connect("motion_notify_event", self.on_move) self.canvas.mpl_connect("button_release_event", self.on_release) self.hbox.addWidget(self.canvas) self.setLayout(self.hbox) if (self.filename !=''): self.data = fits.open(Path(self.filename))[0].data zimshow(self.ax, self.data) self.canvas.draw() def setFileName(self, fileName): self.filename = fileName self.data = fits.open(Path(self.filename))[0].data zimshow(self.ax, self.data) self.canvas.draw() def on_press(self, event): if not event.inaxes : return if event.inaxes != self.ax: return self.rect = Rectangle((0, 0), 1, 1, alpha=0.5) self.ax.add_patch(self.rect) self.x0 = event.xdata self.y0 = event.ydata self.isPressed = True def on_move(self, event): if not event.inaxes : return if event.inaxes != self.ax: return if not self.isPressed : return self.x1 = event.xdata self.y1 = event.ydata self.rect.set_width(self.x1 - self.x0) self.rect.set_height(self.y1 - self.y0) self.rect.set_xy((self.x0, self.y0)) self.ax.figure.canvas.draw() def on_release(self, event): if not event.inaxes : return if event.inaxes != self.ax: return if not self.isPressed: return x = int(self.rect.get_x()) y = int(self.rect.get_y()) width = int(self.rect.get_width()) height = int(self.rect.get_height()) x0 = x x1 = x + width y0 = y y1 = y + height if (x0 > x1): x0, x1 = x1, x0 if (y0 > y1): y0, y1 = y1, y0 self.cropInfo.x0 = x0 self.cropInfo.x1 = x1 self.cropInfo.y0 = y0 self.cropInfo.y1 = y1 self.cropInfo.filename = self.filename self.cropDoneSignal.emit(self.cropInfo) self.rect.remove() self.ax.figure.canvas.draw() self.isPressed = False self.cropCheckWidget.setCropInfo(self.cropInfo) self.cropCheckWidget.show() self.cropCheckWidget.raise_()
class CustomToolbar(NavToolbar): toolitems = NavToolbar.toolitems + ( (None, None, None, None), ("ROI", "Select ROI", "selection", "_on_custom_select"), ) def __init__(self, plotCanvas): # create the default toolbar NavToolbar.__init__(self, plotCanvas) self.selector = RectSelector( self.canvas.figure.axes[0], self.onSelect, button=[1, 3], minspanx=5, minspany=5 # don't use middle button ) self.selector.set_active(True) self.ax = self.canvas.figure.axes[0] self.roi = None self.fixedSize = False if wx.Platform == "__WXMAC__": self.to_draw = Rectangle( (0, 0), 0, 1, visible=False, facecolor="yellow", edgecolor="black", alpha=0.5, fill=True ) self.ax.add_patch(self.to_draw) self.background = None def _init_toolbar(self): self._parent = self.canvas.GetParent() self.wx_ids = {} for text, tooltip_text, image_file, callback in self.toolitems: if text is None: self.AddSeparator() continue self.wx_ids[text] = wx.NewId() try: bitmap = _load_bitmap(image_file + ".png") except IOError: bitmap = wx.Bitmap(image_file + ".png") if text in ["Pan", "Zoom", "ROI"]: self.AddCheckTool(self.wx_ids[text], bitmap, shortHelp=text, longHelp=tooltip_text) else: self.AddSimpleTool(self.wx_ids[text], bitmap, text, tooltip_text) bind(self, wx.EVT_TOOL, getattr(self, callback), id=self.wx_ids[text]) self.ToggleTool(self.wx_ids["ROI"], True) self.Realize() def _set_markers(self): self.canvas.parentFrame.set_markers() def _update_view(self): NavToolbar._update_view(self) self._set_markers() # MacOS needs a forced draw to update plot if wx.Platform == "__WXMAC__": self.canvas.draw() def draw(self): self._set_markers() NavToolbar.draw(self) # MacOS needs a forced draw to update plot if wx.Platform == "__WXMAC__": self.canvas.draw() def zoom(self, ev): if wx.Platform == "__WXMAC__": self.ToggleTool(self.wx_ids["Zoom"], self.GetToolState(self.wx_ids["Zoom"])) NavToolbar.zoom(self, ev) def pan(self, ev): if wx.Platform == "__WXMAC__": self.ToggleTool(self.wx_ids["Pan"], self.GetToolState(self.wx_ids["Pan"])) NavToolbar.pan(self, ev) def press_zoom(self, ev): if wx.Platform == "__WXMAC__": self.update_background() self.to_draw.set_visible(True) NavToolbar.press_zoom(self, ev) def release_zoom(self, ev): if wx.Platform == "__WXMAC__": self.to_draw.set_visible(False) NavToolbar.release_zoom(self, ev) def draw_rubberband(self, event, x0, y0, x1, y1): # XOR does not work on MacOS ... if wx.Platform != "__WXMAC__": NavToolbar.draw_rubberband(self, event, x0, y0, x1, y1) else: if self.background is not None: self.canvas.restore_region(self.background) c0, c1 = self.ax.transData.inverted().transform([[x0, y0], [x1, y1]]) l, b = c0 r, t = c1 self.to_draw.set_bounds(l, b, r - l, t - b) self.ax.draw_artist(self.to_draw) self.canvas.blit(self.ax.bbox) def update_background(self): """force an update of the background""" self.background = self.canvas.copy_from_bbox(self.ax.bbox) # Turn on selection # TODO: Proper handling of states, actual functionality. def _on_custom_select(self, evt): # for id in ['Zoom','Pan']: # self.ToggleTool(self.wx_ids[id], False) # print('Select ROI: %s' % (self.GetToolState(self.wx_ids['ROI']))) # self.ToggleTool(self.wx_ids['ROI'], # self.GetToolState(self.wx_ids['ROI']) ) self.toggle_selector() # print('Select ROI: %s' % (self.GetToolState(self.wx_ids['ROI']))) def onSelect(self, eclick, erelease): "eclick and erelease are matplotlib events at press and release" # print(' startposition : (%f, %f)' % (eclick.xdata, eclick.ydata)) # print(' endposition : (%f, %f)' % (erelease.xdata, erelease.ydata)) # print(' used button : ', eclick.button) self.updateROI( min(eclick.xdata, erelease.xdata), min(eclick.ydata, erelease.ydata), abs(eclick.xdata - erelease.xdata), abs(eclick.ydata - erelease.ydata), ) if self.canvas.parentFrame.fixedNumberCB.IsChecked(): # We are working in the fixed-number mode # We need to find new roi for this center point # The handler will call the update ROI function for us. self.canvas.parentFrame.handleROIforN() def updateROI(self, x, y, w, h): if self.roi is None: # print('upd ROI:', x, y, w, h) self.roi = Rectangle((x, y), w, h, ls="solid", lw=2, color="r", fill=False, zorder=5) self.canvas.figure.axes[0].add_patch(self.roi) else: self.roi.set_bounds(x, y, w, h) self.updateCanvas() def toggle_selector(self): self.selector.set_active(not self.selector.active) def onFixedSize(self, ev): self.fixedSize = ev.IsChecked() self.updateCanvas() def onWidthChange(self, ev): if self.roi: x = self.roi.get_x() w = self.roi.get_width() nw = ev.GetValue() dw = {"C": (w - nw) / 2, "L": 0, "R": w - nw}[self.canvas.parentFrame.anchorRB.GetStringSelection()[0]] self.roi.set_x(x + dw) self.roi.set_width(nw) self.updateCanvas() def onHeightChange(self, ev): if self.roi: y = self.roi.get_y() h = self.roi.get_height() nh = ev.GetValue() dh = {"C": (h - nh) / 2, "B": 0, "T": h - nh}[self.canvas.parentFrame.anchorRB.GetStringSelection()[-1]] self.roi.set_y(y + dh) self.roi.set_height(nh) self.updateCanvas() def updateCanvas(self, redraw=True): if self.roi: self.canvas.parentFrame.showROI( self.roi.get_x(), self.roi.get_y(), self.roi.get_width(), self.roi.get_height() ) self.canvas.parentFrame.setWH(self.roi.get_width(), self.roi.get_height()) if self.fixedSize: self.selector.setSize(self.roi.get_width(), self.roi.get_height()) else: self.selector.setSize() if redraw: self.draw()
class ComboBox(LineEdit): """ A ComboxBox, upon clicking button, drops down a list of items to choose. Items can be edited also. """ def __init__( self, width, height, text_list, edit_notify = None, selection_notify = None, **kwargs ): if len(text_list) > 0: text = text_list[0] else: text = '' super(ComboBox,self).__init__( width, height, text, self._on_edit_notify, **kwargs) self._text_list = text_list self._edit_notify = edit_notify self._selection_notify = selection_notify if edit_notify and not callable(edit_notify): raise RuntimeError('edit_notify must be a callable function') if selection_notify and not callable(selection_notify): raise RuntimeError('selection_notify must be a callable function') #--------------------------------------------------------------------- # additional items # # selection axes for showing the possible selections # a rectangle to highlight the selection row # a button to show the selection drop down self._select_axes = None self._select_highlight = None # just a rectangle self._select_posx = None self._select_entries = [] self._cb_state = ComboState.IDLE self._n_lines = 5 self._mouse_motion_cid = None self._ignore_edit_notify = False def _render(self, fig, x, y): super(ComboBox, self)._render(fig, x, y) self._render_dropdown_button(fig) self._render_dropdown_axis(fig, x, y) def _render_dropdown_axis(self, fig, x, y): W, H = fig.get_size_inches() h = self._n_lines * self._height y -= h x /= W y /= H # create the other gui assets but keep them hidden # selection axes, same width, by 10 times in height w = self._width / W h /= H ax = fig.add_axes([x, y, w, h], xticks=[], yticks=[]) ax.set_xlim([0, self._width]) ax.set_ylim([0, self._n_lines * self._height]) ax.set_axis_bgcolor('white') ax.set_visible(False) ax.set_zorder(1000) self._select_axes = ax def _render_dropdown_button(self, fig): w, h = 0.25, 0.125 hw = w / 2.0 hh = h / 2.0 x = self._width - w - 0.02 y = (self._height - h) / 2.0 self._select_posx = x + hw # Three point polygon: # # 2 O-----O 3 # \ / # \ / # O # 1 # points = [ [ x + hw, y ], [ x, y + h], [ x + w, y + h], ] points = np.array(points) patch = Polygon(points, closed = True, ec = 'black', fc = 'black') self._axes.add_patch(patch) def _cb_change_state(self, new_state): if self._cb_state == new_state: raise RuntimeError("Already in state %s" % new_state) if self._cb_state == ComboState.IDLE: if new_state == ComboState.DROP_SELECT: self._select_axes.set_visible(True) x = self._pad_left y = ((self._n_lines - 1) * self._height) #-------------------------------------------------------------- # create highlight if self._select_highlight is None: self._select_highlight = Rectangle( (0,y - self._height / 2.0), self._width, self._height, ec = self._hl_color, fc = self._hl_color ) self._select_axes.add_patch(self._select_highlight) else: self._select_highlight.set_visible(True) # delete existing text objects for t in self._select_entries: t.remove() del t self._select_entries = [] for t in self._text_list: txt = self._select_axes.text( x,y,t, ha = 'left', va = 'center') y -= self._height self._select_entries.append(txt) self._mouse_motion_cid = self._select_axes.figure.canvas.mpl_connect( 'motion_notify_event', self._on_mouse_motion ) self.canvas().draw() else: self._unhandled_state(new_state) elif self._cb_state == ComboState.DROP_SELECT: if new_state == ComboState.IDLE: self._select_axes.set_visible(False) self.canvas().draw() else: self._unhandled_state(new_state) else: self._unhandled_state(new_state) self._cb_state = new_state def _unhandled_state(self, new_state): if _DEV: print("unhandled %s --> %s" % (self._cb_state, new_state)) def _on_mouse_down(self, event): if event.inaxes not in [self._axes, self._select_axes]: self._ignore_edit_notify = True if self._cb_state != ComboState.IDLE: self._cb_change_state(ComboState.IDLE) super(ComboBox, self)._on_mouse_down(event) return x, y = event.xdata, event.ydata if x is None or y is None: super(ComboBox, self)._on_mouse_down(event) return if self._cb_state == ComboState.IDLE: cx = self._select_posx d = np.sqrt( (x - cx) ** 2 ) if d <= 0.16: self._cb_change_state(ComboState.DROP_SELECT) else: super(ComboBox, self)._on_mouse_down(event) elif self._cb_state == ComboState.DROP_SELECT: y = self._select_highlight.get_y() idx = self._find_text_entry(y) selection = self._text_list[idx] self._ignore_edit_notify = True self.text(selection) if self._selection_notify: self._selection_notify(idx, selection) self._cb_change_state(ComboState.IDLE) elif _DEV: print("on_mouse_down(): unhandled %s" % self._cb_state) def _on_mouse_motion(self, event): if event.inaxes != self._select_axes: return x, y = event.xdata, event.ydata if x is None or y is None: return if self._cb_state == ComboState.DROP_SELECT: idx = self._find_text_entry(y) _, y = self._select_entries[idx].get_position() self._select_highlight.set_y(y - self._height / 2.0) self.canvas().draw() def _on_key_press(self, event): if event.key == 'escape' and self._cb_state == ComboState.DROP_SELECT: self._cb_change_state(ComboState.IDLE) self._ignore_edit_notify = False super(ComboBox, self)._on_key_press(event) def _find_text_entry(self, y): # find nearest text dist = [] for txt in self._select_entries: _, ydata = txt.get_position() d = np.abs(ydata - y) dist.append(d) return np.argmin(dist) def _on_edit_notify(self, text): if self._ignore_edit_notify: self._ignore_edit_notify = False return # add to the list self._text_list.append(text) if self._edit_notify: self._edit_notify(text)