class LeftGraphBottom(wx.Panel): def __init__(self, parent, statusbar): wx.Panel.__init__(self, parent) self.statusbar = statusbar """ An polygon editor. Key-bindings 't' toggle vertex markers on and off. When vertex markers are on, you can move them, delete them 'd' delete the vertex under point 'i' insert a vertex at point. You must be within epsilon of the line connecting two existing vertices """ self.fig = Figure((4.0, 3.0)) self.canvas = FigCanvas(self, -1, self.fig) self.ax = self.fig.add_subplot(111) """ subplots_adjust(bottom=0.14): permet d'ajuster la taille du canevas en prenant en compte la legende sinon la legende est rognee""" self.fig.subplots_adjust(bottom=0.20) self.ax.set_ylabel("DW", fontdict=font) self.ax.set_xlabel("Depth ($\AA$)", fontdict=font) self.toolbar = NavigationToolbar(self.canvas) self.toolbar.Hide() self.fig.patch.set_facecolor(colorBackgroundGraph) self._ind = None # the active vert self.poly = [] self.line = [] self.showverts = True self.epsilon = 5 # max pixel distance to count as a vertex hit self.new_coord = {'indice': 0, 'x': 0, 'y': 0} self.modelpv = False xs = [-1] ys = [-1] poly = Polygon(list(zip(xs, ys)), ls='solid', fill=False, closed=False, animated=True) self.ax.set_xlim([0, 1]) self.ax.set_ylim([0, 1]) self.c_dw = "" self.l_dw = "" self.canvas.mpl_connect('draw_event', self.draw_callback) self.canvas.mpl_connect('button_press_event', self.button_press_callback) self.canvas.mpl_connect('button_release_event', self.button_release_callback) self.canvas.mpl_connect('motion_notify_event', self.motion_notify_callback) self.canvas.mpl_connect('scroll_event', self.scroll_callback) self.canvas.mpl_connect('motion_notify_event', self.on_update_coordinate) mastersizer = wx.BoxSizer(wx.VERTICAL) mastersizer.Add(self.canvas, 1, wx.ALL | wx.EXPAND) mastersizer.Add(self.toolbar, 0, wx.ALL) pub.subscribe(self.draw_c, pubsub_draw_graph) pub.subscribe(self.OnDrawGraph, pubsub_Draw_DW) pub.subscribe(self.scale_manual, pubsub_Update_Scale_DW) pub.subscribe(self.on_color, pubsub_Graph_change_color_style) self.on_color() self.draw_c(poly, xs, ys) self.SetSizer(mastersizer) self.Fit() def on_color(self): a = P4Rm() self.c_dw = a.DefaultDict['c_dw'] self.l_dw = a.DefaultDict['l_dw'] self.c_bkg = a.DefaultDict['c_graph_background'] def OnDrawGraph(self, b=None): a = P4Rm() self.modelpv = a.modelPv self.ax.clear() if a.AllDataDict['damaged_depth'] == 0: self.ax.text(0.5, 0.5, "No Damage", size=30, rotation=0., ha="center", va="center", bbox=dict( boxstyle="round", ec='red', fc=self.c_dw, )) x_dwp = [-1] y_dwp = [-1] xs = [-1] ys = [-1] self.ax.set_xticklabels([]) self.ax.set_yticklabels([]) self.ax.set_xlim([0, 1]) self.ax.set_ylim([0, 1]) else: if b != 2: x_dwp = a.ParamDict['x_dwp'] y_dwp = a.ParamDict['DW_shifted'] xs = deepcopy(a.ParamDict['depth']) ys = deepcopy(a.ParamDict['DW_i']) P4Rm.DragDrop_DW_x = x_dwp P4Rm.DragDrop_DW_y = y_dwp ymin = min(ys) - min(ys) * 10 / 100 ymax = max(ys) + max(ys) * 10 / 100 self.ax.set_ylim([ymin, ymax]) if a.ParamDict['x_dwp'] != "": self.ax.set_xlim( [a.ParamDict['depth'][-1], a.ParamDict['depth'][0]]) elif b == 2: x_dwp = [-1] y_dwp = [-1] xs = [-1] ys = [-1] self.ax.set_xlim([0, 1]) self.ax.set_ylim([0, 1]) poly = Polygon(list(zip(x_dwp, y_dwp)), lw=0, ls='solid', color=self.c_dw, fill=False, closed=False, animated=True) if self.modelpv is True: P4Rm.ParamDict['dwp_pv_backup'] = a.ParamDict['dwp'] self.draw_c(poly, xs, ys) def draw_c(self, data, x, y): self.ax.plot(x, y, color=self.c_dw, lw=2., ls='solid') self.ax.set_ylabel("DW", fontdict=font) self.ax.set_xlabel("Depth ($\AA$)", fontdict=font) if LooseVersion(matplotlib_vers) < LooseVersion("2.0.0"): self.ax.set_axis_bgcolor(self.c_bkg) else: self.ax.set_facecolor(self.c_bkg) self.poly = data xs, ys = zip(*self.poly.xy) self.line = Line2D(xs, ys, lw=0, ls='solid', color=self.c_dw, marker='.', ms=32, markerfacecolor=self.c_dw, markeredgecolor='k', mew=1.0) self.ax.add_line(self.line) self.ax.add_patch(self.poly) self.canvas.SetCursor(Cursor(wx.CURSOR_HAND)) self.canvas.draw() def draw_callback(self, event): self.background = self.canvas.copy_from_bbox(self.ax.bbox) self.ax.draw_artist(self.poly) self.ax.draw_artist(self.line) self.canvas.blit(self.ax.bbox) def get_ind_under_point(self, event): 'get the index of the vertex under point if within epsilon tolerance' # display coords xy = np.asarray(self.poly.xy) xyt = self.poly.get_transform().transform(xy) xt, yt = xyt[:, 0], xyt[:, 1] d = np.sqrt((xt - event.x)**2 + (yt - event.y)**2) indseq = np.nonzero(np.equal(d, np.amin(d)))[0] ind = indseq[0] if d[ind] >= self.epsilon: ind = None return ind def button_press_callback(self, event): 'whenever a mouse button is pressed' a = P4Rm() val = a.xrd_graph_loaded if self.canvas.HasCapture(): self.canvas.ReleaseMouse() if not self.showverts: return if event.inaxes is None: return if event.button != 1: return if val == 1: self._ind = self.get_ind_under_point(event) self.new_coord['indice'] = self._ind def button_release_callback(self, event): 'whenever a mouse button is released' a = P4Rm() val = a.xrd_graph_loaded if self.canvas.HasCapture(): self.canvas.ReleaseMouse() else: if not self.showverts: return if event.button != 1: return if self.new_coord['indice'] is not None and val == 1: a = P4Rm() temp_1 = self.new_coord['y'] temp_2 = self.new_coord['x'] P4Rm.DragDrop_DW_y[self.new_coord['indice']] = temp_1 P4Rm.DragDrop_DW_x[self.new_coord['indice']] = temp_2 if a.AllDataDict['model'] == 0: temp = self.new_coord['y'] P4Rm.DragDrop_DW_y[self.new_coord['indice']] = temp temp = [ dw * scale for dw, scale in zip( a.DragDrop_DW_y, a.ParamDict['scale_dw']) ] temp = [float(format(value, '.8f')) for value in temp] temp2 = np.concatenate([temp, [a.ParamDict['dw_out']]]) P4Rm.ParamDict['dwp'] = deepcopy(temp2) P4Rm.ParamDictbackup['dwp'] = deepcopy(temp2) elif a.AllDataDict['model'] == 1: temp = self.new_coord['y'] P4Rm.DragDrop_DW_y[self.new_coord['indice']] = temp temp = [ dw * scale for dw, scale in zip( a.DragDrop_DW_y, a.ParamDict['scale_dw']) ] temp = [float(format(value, '.8f')) for value in temp] temp2 = np.concatenate([[a.ParamDict['dw_out'][0]], temp, [a.ParamDict['dw_out'][1]]]) P4Rm.ParamDict['dwp'] = deepcopy(temp2) P4Rm.ParamDictbackup['dwp'] = deepcopy(temp2) elif a.AllDataDict['model'] == 2: t_temp = a.ParamDict['depth'] + a.ParamDict['z'] t = t_temp[0] dwp_temp = range(7) dwp_temp[0] = a.DragDrop_DW_y[0] dwp_temp[1] = 1 - a.DragDrop_DW_x[0] / t dwp_temp[2] = 2 * (-1 + a.ParamDict['dwp'][1] + a.DragDrop_DW_x[1] / t) dwp_temp[3] = 2 * (1 - a.ParamDict['dwp'][1] - 1 * a.DragDrop_DW_x[2] / t) dwp_temp[4] = a.ParamDict['dwp'][4] dwp_temp[5] = a.ParamDict['dwp'][5] dwp_temp[6] = a.DragDrop_DW_y[3] P4Rm.ParamDict['dwp'] = deepcopy(dwp_temp) P4Rm.ParamDictbackup['dwp'] = deepcopy(dwp_temp) P4Rm.ParamDict['dwp_pv'] = deepcopy(dwp_temp) pub.sendMessage(pubsub_Update_Fit_Live) self._ind = None def scroll_callback(self, event): if not event.inaxes: return a = P4Rm() if event.key == 'u' and event.button == 'up': temp = a.ParamDict['DW_multiplication'] + 0.01 P4Rm.ParamDict['DW_multiplication'] = temp elif event.key == 'u' and event.button == 'down': temp = a.ParamDict['DW_multiplication'] - 0.01 P4Rm.ParamDict['DW_multiplication'] = temp P4Rm.ParamDict['dwp'] = multiply(a.ParamDictbackup['dwp'], a.ParamDict['DW_multiplication']) pub.sendMessage(pubsub_Re_Read_field_paramters_panel, event=event) def scale_manual(self, event, val=None): a = P4Rm() if val is not None: P4Rm.ParamDict['DW_multiplication'] = val P4Rm.ParamDict['dwp'] = multiply(a.ParamDict['dwp'], a.ParamDict['DW_multiplication']) pub.sendMessage(pubsub_Re_Read_field_paramters_panel, event=event) def motion_notify_callback(self, event): 'on mouse movement' a = P4Rm() if a.AllDataDict['damaged_depth'] == 0: return if not self.showverts: return if self._ind is None: return if event.inaxes is None: return if event.button != 1: return if self.modelpv is True: if self._ind == 0: y = event.ydata x = event.xdata elif self._ind == 1 or self._ind == 2: y = a.DragDrop_DW_y[self.new_coord['indice']] x = event.xdata else: x = a.DragDrop_DW_x[self.new_coord['indice']] y = event.ydata else: y = event.ydata x = a.DragDrop_DW_x[self.new_coord['indice']] self.new_coord['x'] = x self.new_coord['y'] = y self.poly.xy[self._ind] = x, y self.line.set_data(zip(*self.poly.xy)) self.canvas.restore_region(self.background) self.ax.draw_artist(self.poly) self.ax.draw_artist(self.line) self.canvas.blit(self.ax.bbox) def on_update_coordinate(self, event): if event.inaxes is None: self.statusbar.SetStatusText(u"", 1) self.statusbar.SetStatusText(u"", 2) else: a = P4Rm() if not a.AllDataDict['damaged_depth'] == 0: x, y = event.xdata, event.ydata xfloat = round(float(x), 2) yfloat = round(float(y), 2) self.statusbar.SetStatusText(u"x = " + str(xfloat), 1) self.statusbar.SetStatusText(u"y = " + str(yfloat), 2) xy = np.asarray(self.poly.xy) xyt = self.poly.get_transform().transform(xy) xt, yt = xyt[:, 0], xyt[:, 1] d = np.sqrt((xt - event.x)**2 + (yt - event.y)**2) indseq = np.nonzero(np.equal(d, np.amin(d)))[0] ind = indseq[0] if d[ind] >= self.epsilon: self.canvas.SetCursor(Cursor(wx.CURSOR_ARROW)) elif d[ind] <= self.epsilon: self.canvas.SetCursor(Cursor(wx.CURSOR_HAND))
class GenericPlotItemPanel(wx.Panel): """ plot on a PlotPanel one curve """ def __init__(self, parent, value, pression, theName, liste_item=None, kind="GASES", xlegend="ppmv", edit=False, layerstyle=False, layer=None, yInPressions=True, tskin=None, tickSize=10): self.theName = theName self.theParent = parent self.xlegend = xlegend self.edit = edit self.kind = kind self.yInPressions = yInPressions self.layer = layer self.layerstyle = layerstyle self.tickSize = tickSize self.pression = pression self.value = value self.myLayeritem = None self.tskin = [] self.ytskin = [] if tskin: self.tskin.append(tskin) wx.Panel.__init__(self, parent, style=wx.BORDER_SIMPLE) # define object for matplotlib self.fig = Figure() self.canvas = FigureCanvas(self, -1, self.fig) self.canvas.mpl_connect('motion_notify_event', self.onMouseMotion) self.text = wx.StaticText(self, -1, label="") self.sizer = wx.BoxSizer(wx.VERTICAL) self.sizer.Add(self.canvas, 1, wx.LEFT | wx.GROW, 1) self.tlb = ToolBar(self.canvas) self.sizer.Add(self.tlb, 0, wx.GROW) self.tlb.Realize() self.SetSizer(self.sizer) self.text = wx.StaticText(self, -1, label="") self.sizer.Add(self.text) self.Fit() self.onInsert = True self.myCurves = [] self.OnPlot() self.valueHistory = [] self.valueHistoryRedo = [] def onResize(self, event): print "event resize", str(event) def onMouseMotion(self, event): """ set text when moving mousse """ if event.inaxes: xdata = event.xdata ydata = event.ydata xstr = "%0.4g" % xdata ystr = "%0.4g" % ydata value = str(self.axes.get_ylabel()) + "=" + ystr + \ " " + str(self.axes.get_xlabel()) + "=" + xstr self.text.SetLabel(value) def OnPlot(self): """ effectively perform the graphics """ self.SetTickSize(self.tickSize) self.fig.clear() self.axes = self.fig.add_subplot(1, 1, 1) self.x = self.value[::1] if self.yInPressions: self.axes.set_yscale("log") self.axes.set_yticks( (0.00005, 0.0001, 0.0002, 0.0005, 0.001, 0.002, 0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 25, 50, 100, 200, 300, 500, 1000)) label = ('5e-5', '1e-4', '2e-4', '5e-4', '1e-3', '2e-3', '5e-3', '0.01', '0.02', '0.05', '0.1', '0.2', '0.5', '1', '2', '5', '10', '25', '50', '100', '200', '300', '500', '1000') self.axes.set_yticklabels(label) self.axes.set_ylabel('pressure (hPa)') self.axes.set_ylim((self.pression[-1] + 150, self.pression[0])) else: self.axes.set_ylim(self.value.shape[0] + 2, 1) self.axes.set_ylabel('level') if self.kind == "GASES": if self.yInPressions: self.y = self.pression[::1] else: self.y = numpy.arange(1, self.value.shape[0] + 1, 1) else: if self.yInPressions: self.y = self.layer[::1] else: self.y = numpy.arange(1.5, self.value.shape[0], 1) if not self.layerstyle: self.data = Data(self.x, self.y, theName=self.theName, theColor=itemColor[self.theName], theMarker=itemMarker[self.theName]) else: if self.yInPressions: self.myLayeritem = layeritem.Layeritem( layeritem=self.x, pression=self.pression[::1]) else: self.myLayeritem = layeritem.Layeritem( layeritem=self.x, pression=numpy.arange(1, self.value.shape[0] + 1, 1)) (self.xlayeritem, self.ylayeritem) = (self.myLayeritem.computeLayerLine( layers=self.y)) self.data = Data(self.xlayeritem, self.ylayeritem, theName=self.theName, theColor=itemColor[self.theName], theMarker=itemMarker[self.theName]) self.axes.set_xlabel(self.xlegend) self.SetXlimits(self.theName) self.axes.grid(True, axis='both') self.myChannelList = [] self.myChannelList.append(self.data) if self.theName == "T": if len(self.tskin) > 0: if self.yInPressions: self.ytskin.append(self.pression[-1] + 50) else: self.ytskin.append(self.value.shape[0] + 1) datatskin = Data(self.tskin, self.ytskin, theName='TSKIN', theColor="red", theMarker="*") self.myChannelList.append(datatskin) if wx.Platform == '__WXMAC__': self.Update() def SetTickSize(self, size): matplotlib.rc('xtick', labelsize=size) matplotlib.rc('ytick', labelsize=size) def ConnectCanvasEVT_POINT(self, methode): self.cid = self.fig.canvas.mpl_connect("button_press_event", methode) def DisconnectCanvasEVT_POINT(self): self.fig.canvas.mpl_disconnect(self.cid) def SetXlimits(self, theName=None, xmin=None, xmax=None): """ set x limits """ if xmin is not None and xmax is not None: self.axes.set_xlim((xmin, xmax)) else: if axesDef[self.theName]["xlimits"] is not None: self.axes.set_xlim(axesDef[self.theName]["xlimits"]) self.axes.set_xscale(axesDef[self.theName]["xscale"]) def Update(self): """ erase the curve if necessary and redraw """ if len(self.myCurves) == 1: if len(self.axes.lines) == 1: self.axes.lines.remove(self.axes.lines[0]) self.myCurves.pop() for data in self.myChannelList: c = self.axes.plot(data.x, data.y, color=data.color, marker=data.marker) self.myCurves.append(c) self.fig.canvas.draw_idle() def UpdateData(self, dataX): self.x = dataX self.data.setChanged(True) if not self.layerstyle: self.data.myUpdate(self.x, self.y) else: (self.xlayeritem, self.ylayeritem) = (self.myLayeritem.computeLayerLine( layeritem=dataX)) self.data.myUpdate(self.xlayeritem, self.ylayeritem) self.Update() def OnRedo(self): if self.valueHistoryRedo != []: if not self.layerstyle: X = numpy.zeros(self.x.shape[0]) + self.x self.valueHistory.append(X) X = self.valueHistoryRedo.pop() self.x = numpy.zeros(X.shape[0]) + X self.data.myUpdate(self.x, self.y) else: X = numpy.zeros(self.xlayeritem.shape[0]) + self.xlayeritem self.valueHistory.append(X) X = self.valueHistoryRedo.pop() self.xlayeritem = numpy.zeros(X.shape[0]) + X self.x = self.myLayeritem.getLayeritem(self.xlayeritem) self.myLayeritem.update(self.xlayeritem, self.ylayeritem) self.data.myUpdate(self.xlayeritem, self.ylayeritem) self.Update() def OnUndo(self): if self.valueHistory != []: if not self.layerstyle: X = numpy.zeros(self.x.shape[0]) + self.x self.valueHistoryRedo.append(X) X = self.valueHistory.pop() self.x = numpy.zeros(X.shape[0]) + X self.data.myUpdate(self.x, self.y) else: X = numpy.zeros(self.xlayeritem.shape[0]) + self.xlayeritem self.valueHistoryRedo.append(X) X = self.valueHistory.pop() self.xlayeritem = numpy.zeros(X.shape[0]) + X self.x = self.myLayeritem.getLayeritem(self.xlayeritem) self.data.myUpdate(self.xlayeritem, self.ylayeritem) self.Update() def OnPoint(self, e): """ OnPoint Methods """ if (e.button == 1) or (e.dblclick): if self.canvas.HasCapture(): self.canvas.ReleaseMouse() return (False) if e.xdata is None or e.ydata is None: if self.canvas.HasCapture(): self.canvas.ReleaseMouse() if self.HasCapture(): self.ReleaseMouse() return False if (e.ydata < self.y.min() or e.ydata > self.y.max()): if self.canvas.HasCapture(): self.canvas.ReleaseMouse() return (False) # self.tlb.release_zoom(e) if not self.layerstyle: y = numpy.zeros(self.x.shape[0]) + self.x self.valueHistory.append(y) mini = 1000 for index in range(self.y.shape[0]): dist = abs(self.y[index] - e.ydata) if dist < mini: imin = index mini = dist if self.kind != "GASES" and not self.onInsert: self.x[imin] = 0 else: self.x[imin] = e.xdata self.data.setChanged(True) self.data.myUpdate(self.x, self.y) self.Update() else: y = numpy.zeros(self.xlayeritem.shape[0]) + self.xlayeritem self.valueHistory.append(y) mini = 1000 for index in range(self.ylayeritem.shape[0]): dist = self.ylayeritem[index] - e.ydata if dist < mini and dist > 0: imin = index mini = dist if not self.onInsert: self.xlayeritem[imin] = 0 # we have 2 points to move and its depends if imin is odd if imin % 2 != 0: if imin != self.xlayeritem.shape[0]: self.xlayeritem[imin + 1] = 0 else: if imin != 0: self.xlayeritem[imin - 1] = 0 else: self.xlayeritem[imin] = e.xdata # we have 2 points to move and its depends if imini is odd if imin % 2 != 0: if imin != self.xlayeritem.shape[0]: self.xlayeritem[imin + 1] = e.xdata else: if imin != 0: self.xlayeritem[imin - 1] = e.xdata self.data.setChanged(True) self.data.myUpdate(self.xlayeritem, self.ylayeritem) self.x = self.myLayeritem.getLayeritem(self.xlayeritem) self.Update() if self.canvas.HasCapture(): self.canvas.ReleaseMouse() if self.HasCapture(): self.ReleaseMouse() return True def GetItem(self): """ get value from curve (=data) and return a profile for the item """ myX = self.data.getX() if self.layerstyle: layerX = self.myLayeritem.getLayeritem(myX) return layerX else: return myX
class PlotPanel(wx.Panel): def __init__(self, parent, statusbar): wx.Panel.__init__(self, parent, -1, size=(50, 50)) self.statusbar = statusbar self.plot_handl = None self.sizer = wx.BoxSizer(wx.VERTICAL) self.SetSizer(self.sizer) self.figure = Figure(None, dpi=300) self.ax = self.figure.add_axes([0, 0, 1, 1], frameon=False, projection=ccrs.PlateCarree()) self.canvas = PlotFigureCanvas(self, -1, self.figure) self.toolbar = PlotToolbar(self.canvas, self) self.sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW) self.sizer.Add(self.toolbar, 0, wx.EXPAND) # Clear artifacts self.clear_artifacts() # Bind events self.figure.canvas.mpl_connect('button_release_event', self.onclick) self.ax.callbacks.connect('xlim_changed', self.on_xlims_change) self.ax.callbacks.connect('ylim_changed', self.on_ylims_change) # setup empty list of meridians and parallels self.meridians_w = {} self.meridians_b = {} self.parallels_w = {} self.parallels_b = {} self.labels = [] self.nlons = NUM_GRID_LINES self.nlats = NUM_GRID_LINES self.lons = [] self.lats = [] self.latll = -90 self.latur = 90 self.lonll = -180 self.lonur = 180 self.motion_display_font = FontProperties() self.motion_display_font.set_size(MOTION_DISPLAY_FONT_SIZE) self.motion_display_font.set_family('monospace') self.grid_label_font = FontProperties() self.grid_label_font.set_size(GRID_LABEL_FONT_SIZE) self.grid_label_font.set_family('monospace') self.map = None self.motion_display = None self._rc_zoomed = False self.cursor_coordinates_enabled = True self.coordinate_grid_enabled = True self.grid_labels_enabled = True def set_cursor_coordinates_enabled(self, enabled): self.cursor_coordinates_enabled = enabled # apply immediately if not enabled: if self.motion_display: self.motion_display.remove() self.figure.canvas.draw() self.motion_display = None def set_coordinate_grid_enabled(self, enabled): self.coordinate_grid_enabled = enabled def set_grid_labels_enabled(self, enabled): self.grid_labels_enabled = enabled def on_xlims_change(axes): pass def on_ylims_change(axes): pass def on_mouse_move(self, event): if not self.map: return lon, lat = self.map(event.xdata, event.ydata, inverse=True) lonll = max(self.lonll, self.map.llcrnrlon) lonur = min(self.lonur, self.map.urcrnrlon) latll = max(self.latll, self.map.llcrnrlat) latur = min(self.latur, self.map.urcrnrlat) if lat >= latll and lat <= latur and \ lon >= lonll and lon <= lonur and \ self.map.projection == 'cyl' and \ self.cursor_coordinates_enabled: if self.motion_display is None: # setup coordinate display for mouse motion self.motion_display = self.ax.annotate( s="(NaN, NaN)", xy=(0.995, 0.995), xycoords='axes fraction', fontproperties=self.motion_display_font, verticalalignment='top', horizontalalignment='right', bbox=dict(facecolor='white', alpha=1.0, pad=0.2, lw=0.2)) display_lon = self.wrap_lon_at_day_boundary(lon) self.motion_display.set_text('(%+10.5f,%+10.5f)' % (display_lon, lat)) self.ax.draw_artist(self.motion_display) self.canvas.blit(self.motion_display.get_window_extent()) else: if self.motion_display: self.motion_display.remove() self.figure.canvas.draw() self.motion_display = None def wrap_lon_at_day_boundary(self, lon): wrapped_lon = lon while wrapped_lon < -180: wrapped_lon += 360.0 while wrapped_lon > 180: wrapped_lon -= 360.0 return wrapped_lon def do_dynamic_update(self): if not self.map: return # update meridians self._plot_meridians_and_parallels() self.figure.canvas.draw() def set_right_click_zoomed(self): self._rc_zoomed = True def onclick(self, event): if event is None or event.button != 3 or not event.inaxes: return if self._rc_zoomed: self._rc_zoomed = False return self.click_event = event menu = wx.Menu() menu.Append(wx.ID_ANY, "Add Point Here", "Adds a point where the mouse was clicked on the map", wx.ITEM_NORMAL) self.Bind(wx.EVT_MENU, self.callback, id=wx.ID_ANY) if self.canvas.HasCapture(): self.canvas.ReleaseMouse() wx.CallAfter(self.PopupMenu, menu) def callback(self, event): self.map.plot(self.click_event.xdata, self.click_event.ydata, 'r.', markersize=1.) self.figure.canvas.draw() def clear_artifacts(self): for artifact in ['artists', 'lines', 'patches']: while getattr(self.ax, 'artists') != []: getattr(self.ax, 'artists')[0].remove() self.artifacts = {'artists': [], 'lines': [], 'patches': []} def updatePlot(self, attrs): self.statusbar.SetStatusText('Plotting... (Please Be Patient)') self.ax.clear() self.plot(attrs) self.statusbar.SetStatusText('Ready') def plot(self, param): # Create Map if not param: self.ax.stock_img() self.figure.canvas.draw() # # Create a really simple plot to fill the void on app startup. # self.map = Basemap(ax=self.ax, resolution=RESOLUTION_MAP['Crude'], # ellps='WGS84', suppress_ticks=True) # self.map.bluemarble() # self._plot_meridians_and_parallels() # # add the motion display to the list of texts for the new axes # if self.motion_display: # self.ax.texts.append(self.motion_display) # self.figure.canvas.draw() return # projection_name = param['Projection'] # projection_key = revlookup(PROJECTIONS, 'name', projection_name) # resolution = RESOLUTION_MAP[param['Resolution']] # kwargs = {'projection': projection_key, # 'ellps': 'WGS84', # 'suppress_ticks': True, # 'ax': self.ax, # 'resolution': resolution} # for p in PROJECTION_PARAMS[projection_key].keys(): # if 'rspherex' == p: # kwargs['rsphere'] = (param[OPTIONS['rspherex']['name']], # param[OPTIONS['rspherey']['name']]) # elif 'rspherey' == p: # pass # else: # kwargs[p] = param[OPTIONS[p]['name']] # self.map = Basemap(**kwargs) # # Draw Circle # self.draw_range_circle(param["longitude"], # param["geodetic_latitude"], # param["range"], # color='r', # alpha=0.5) # # Draw Blue Marble Texture # if param.get('Blue Marble', False): # self.map.bluemarble() # else: # self.map.fillcontinents(color='coral', lake_color='aqua') # # Draw Coastlines, borders, lines # if param.get('Coastlines', False): # self.map.drawcoastlines() # if param.get('State Borders', False): # self.map.drawstates() # if param.get('Country Borders', False): # self.map.drawcountries() # self._plot_meridians_and_parallels() # # add the motion display to the list of texts for the new axes # if self.motion_display: # self.ax.texts.append(self.motion_display) # self.figure.canvas.draw() def draw_great_circle(self, x1, y1, x2, y2, linewidth=.5, color='r'): self.map.drawgreatcircle(x1, y1, x2, y2, linewidth, color) def draw_text(self, text, x, y, xycoords='data', color='white', size='xx-small'): self.ax.add_artist( AnnotationBbox(TextArea(text, minimumdescent=False, textprops={ 'color': color, 'size': size })(x, y), xycoords='data', frameon=False)) def draw_image(self, image, x, y, zoom=.1, alpha=.7, xycoords='data'): if isinstance(image, PyEmbeddedImage): png = pyembeddedimage_to_png(image) elif isinstance(image, basestring): png = read_png(image) else: png = image self.ax.add_artist( AnnotationBbox(OffsetImage(png, zoom=zoom, alpha=alpha), (x, y), xycoords=xycoords, frameon=False)) def draw_range_circle(self, lat, lon, radius, color='r', alpha=.5): # FIXME Use the real radius of the earth at this lat/lon self.map.tissot(lon, lat, 180. / pi * radius / 6370., 256, facecolor=color, alpha=alpha) def _plot_meridians_and_parallels(self): """ Plots meridians and parallels appropriate for the current zoom level """ # if grid is off and labels are off wipe lines if not self.coordinate_grid_enabled and not self.grid_labels_enabled: self._label_grid([], []) self._clear_meridians_and_parallels() return # FIXME: Get this working for other projections if self.map.projection != 'cyl': self._clear_meridians_and_parallels() # FIXME: Make it an option to turn on/off white or black lines if self.coordinate_grid_enabled: lats = np.linspace(-90, 90, 10) self.parallels_b = self.map.drawparallels( lats, labels=[True, False, False, False], fontsize=GRID_LABEL_FONT_SIZE, linewidth=0.2, dashes=[], color='black') self.parallels_w = self.map.drawparallels( lats, labels=[False, False, False, False], linewidth=0.2, dashes=[1, 1], color='white') lons = np.linspace(-180, 180, 10) self.meridians_b = self.map.drawmeridians( lons, labels=[False, False, False, True], fontsize=GRID_LABEL_FONT_SIZE, linewidth=0.2, dashes=[], color='black') self.meridians_w = self.map.drawmeridians( lons, labels=[False, False, False, False], linewidth=0.2, dashes=[1, 1], color='white') self._label_grid(lons, lats) return # get the corners of the viewport self.lonll, self.latll, delta_lon, delta_lat = self.ax.viewLim.bounds self.latur = self.latll + delta_lat self.lonur = self.lonll + delta_lon # translate to lat/lon (if needed) self.lonll, self.latll = self.map(self.lonll, self.latll, inverse=True) self.lonur, self.latur = self.map(self.lonur, self.latur, inverse=True) if not self._zoom_is_valid(): return # get the range of lat/lon of the current viewport lat_range = self.latur - self.latll lon_range = self.lonur - self.lonll # get the estimated number of parallels and meridians to plot nlons = round(NUM_GRID_LINES * 360.0 / lon_range) nlats = round(NUM_GRID_LINES * 180.0 / lat_range) found = False for kv in LAT_GRID_LOOKUP: if nlats <= kv[0]: self.nlats = kv[1] found = True break if not found: self.nlats = self._scale_grid_by_twos(nlats, self.nlats) found = False for kv in LON_GRID_LOOKUP: if nlons <= kv[0]: self.nlons = kv[1] found = True break if not found: self.nlons = self._scale_grid_by_twos(nlons, self.nlons) # return if we've got too few to plot if self.nlons <= 1 or self.nlats <= 1: return # calculate the actual meridian coordinates lons = np.linspace(-180, 180.0, self.nlons) lons = np.concatenate((lons - 360.0, lons, lons + 360.0)) lats = np.linspace(-90.0, 90.0, self.nlats) # filter them to speed plotting lon_condition = lons >= self.lonll lons = np.extract(lon_condition, lons) lon_condition = lons <= self.lonur lons = np.extract(lon_condition, lons) lat_condition = lats >= self.latll lats = np.extract(lat_condition, lats) lat_condition = lats <= self.latur lats = np.extract(lat_condition, lats) # plot labels for each one self._label_grid(lons, lats) self.lats = lats self.lons = lons # plot new parallels/meridians # these are made up of a solid black line, and a dashed white line self._clear_meridians_and_parallels() # FIXME: Make it an option to turn on/off white or black lines if self.coordinate_grid_enabled: self.parallels_b = self.map.drawparallels( lats, labels=[False, False, False, False], linewidth=0.2, dashes=[], color='black') self.parallels_w = self.map.drawparallels( lats, labels=[False, False, False, False], linewidth=0.2, dashes=[1, 1], color='white') self.meridians_b = self.map.drawmeridians( lons, labels=[False, False, False, False], linewidth=0.2, dashes=[], color='black') self.meridians_w = self.map.drawmeridians( lons, labels=[False, False, False, False], linewidth=0.2, dashes=[1, 1], color='white') def _label_grid(self, lons, lats): """ Plots labels on grid lines. """ degree_sign = u'\N{DEGREE SIGN}' for label in self.labels: if label in self.ax.texts: label.remove() self.labels = [] if self.grid_labels_enabled: # peg the corners on the projection boundaries lonll = max(self.lonll, self.map.llcrnrlon) lonur = min(self.lonur, self.map.urcrnrlon) latll = max(self.latll, self.map.llcrnrlat) latur = min(self.latur, self.map.urcrnrlat) for lat in lats: if lat > latur or lat < latll: continue if lat >= 0: NS = 'N' else: NS = 'S' (d, m, sd) = self._to_dms(lat) self.labels.append( self.ax.text(lonll, lat, "%02d%s%02d'%05.2f\"%s" % (abs(d), degree_sign, m, sd, NS), fontproperties=self.grid_label_font, verticalalignment='center', horizontalalignment='left', bbox=dict(facecolor='white', alpha=0.5, pad=0.2, lw=0.2))) for lon in lons: if lon > lonur or lon < lonll: continue # normalize display_lon = self.wrap_lon_at_day_boundary(lon) if display_lon >= 0: NS = 'E' else: NS = 'W' (d, m, sd) = self._to_dms(display_lon) self.labels.append( self.ax.text(lon, latll, "%03d%s%02d'%05.2f\"%s" % (abs(d), degree_sign, m, sd, NS), fontproperties=self.grid_label_font, rotation='vertical', verticalalignment='bottom', horizontalalignment='center', bbox=dict(facecolor='white', alpha=0.5, pad=0.2, lw=0.2))) def _to_dms(self, deg): """ Converts degrees to degrees minutes seconds """ d = int(deg) md = abs(deg - d) * 60.0 m = int(md) sd = (md - m) * 60.0 return [d, m, sd] def _scale_grid_by_twos(self, n_target, n_current): """ Determines number of grid lines to plot via hopping by powers of 2 """ half_n_current = int(n_current / 2.0) + 1 twice_n_current = round(n_current * 2) - 1 if n_target < n_current and n_target > 1: while n_target < n_current: n_current = half_n_current half_n_current = int(n_current / 2.0) + 1 if n_current < 3: break else: while n_target >= twice_n_current: n_current = twice_n_current twice_n_current = round(n_current * 2) - 1 # should be an odd number at this point, but make sure if n_current % 2 == 0: n_current += 1 return n_current def _zoom_is_valid(self): """ Checks if zoom level is valid """ # check if corners are valid try: delta_lon = self.lonur - self.lonll delta_lat = self.latur - self.latll except: return False return True def _clear_meridians_and_parallels(self): """ Clears currently plotted meridians and parallels """ # clear old meridians and parallels # these are made up of a solid black line, and a dashed white line for par in self.parallels_w: for item in self.parallels_w[par]: if len(item) > 0: if item[0] in self.ax.lines: item[0].remove() self.parallels_w.clear() for par in self.parallels_b: for item in self.parallels_b[par]: if len(item) > 0: if item[0] in self.ax.lines: item[0].remove() self.parallels_b.clear() for mer in self.meridians_w: for item in self.meridians_w[mer]: if len(item) > 0: if item[0] in self.ax.lines: item[0].remove() self.meridians_w.clear() for mer in self.meridians_b: for item in self.meridians_b[mer]: if len(item) > 0: if item[0] in self.ax.lines: item[0].remove() self.meridians_b.clear()
class PlotPanel(wx.Panel): zoom_levels = [100.0, 110.0, 125.0, 150.0, 200.0, 250.0, 300.0, 400.0] dose_contour_levels = [ 10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 95.0, 98 - 0, 100.0, 102.0 ] def __init__(self, parent): wx.Panel.__init__(self, parent) self.is_closeable = False self.parent = parent self.active_plan = None self.plot_mouse_action = None pub.subscribe(self.on_patient_loaded, "patient.loaded.ini") pub.subscribe(self.set_active_image, "plot.image.active_id") pub.subscribe(self.voi_changed, "voi.selection_changed") pub.subscribe(self.plan_changed, "plan.active.changed") pub.subscribe(self.plan_field_changed, "plan.field.selection_changed") pub.subscribe(self.plan_dose_changed, "plan.dose.active_changed") pub.subscribe(self.plan_dose_removed, "plan.dose.removed") pub.subscribe(self.plan_let_added, "plan.let.added") pub.subscribe(self.plan_let_removed, "plan.let.removed") pub.subscribe(self.target_dose_changed, "plan.dose.target_dose_changed") self.plotmode = "Transversal" def __del__(self): pub.unsubscribe(self.on_patient_loaded, "patient.loaded.ini") pub.unsubscribe(self.set_active_image, "plot.image.active_id") pub.unsubscribe(self.voi_changed, "voi.selection_changed") pub.unsubscribe(self.plan_changed, "plan.active.changed") pub.unsubscribe(self.plan_field_changed, "plan.field.selection_changed") pub.unsubscribe(self.plan_dose_changed, "plan.dose.active_changed") pub.unsubscribe(self.plan_dose_removed, "plan.dose.removed") pub.unsubscribe(self.plan_let_added, "plan.let.added") pub.unsubscribe(self.plan_let_removed, "plan.let.removed") pub.unsubscribe(self.target_dose_changed, "plan.dose.target_dose_changed") def target_dose_changed(self, msg): self.Draw() def Init(self): self.plotutil = PlotUtil() self.figure = Figure(None, 100) self.canvas = FigureCanvasWxAgg(self, -1, self.figure) # ~ self.canvas.SetDoubleBuffered(True) self.clear() self.plotutil.set_draw_in_gui(True) self.figure.set_frameon(True) rect = self.figure.patch rect.set_facecolor('black') def plan_dose_changed(self, msg): if msg.data["plan"] is self.active_plan: self.plotutil.set_dose(msg.data["dose"].get_dosecube()) self.Draw() def plan_field_changed(self, msg): self.Draw() def plan_dose_removed(self, msg): if msg.data["plan"] is self.active_plan: self.plotutil.set_dose(None) self.Draw() def plan_let_added(self, msg): if msg.data["plan"] is self.active_plan: self.plotutil.set_let(msg.data["let"]) self.Draw() def plan_let_removed(self, msg): if msg.data["plan"] is self.active_plan: self.plotutil.set_let(None) self.Draw() def plan_changed(self, msg): self.active_plan = msg.data if self.active_plan is None: self.plotutil.set_plan(None) self.plotutil.set_dose(None) self.plotutil.set_let(None) else: self.plotutil.set_plan(self.active_plan) doseobj = self.active_plan.get_dose() if doseobj is not None: self.plotutil.set_dose(doseobj.get_dosecube()) else: self.plotutil.set_dose(None) self.plotutil.set_let(self.active_plan.get_let()) self.Draw() def set_toolbar(self, toolbar): id = wx.NewId() selector = wx.Choice(toolbar, id) selector.Append("Transversal") selector.Append("Sagital") selector.Append("Coronal") idx = selector.FindString(self.plotmode) selector.Select(idx) toolbar.AddControl(selector) wx.EVT_CHOICE(selector, id, self.plot_mode_changed) id = wx.NewId() self.zoom_in_btn = toolbar.AddLabelTool( id, '', wx.Bitmap(get_resource_path('zoom_in.png'))) wx.EVT_MENU(toolbar, id, self.zoom_in) id = wx.NewId() self.zoom_out_btn = toolbar.AddLabelTool( id, '', wx.Bitmap(get_resource_path('zoom_out.png'))) wx.EVT_MENU(toolbar, id, self.zoom_out) def zoom_buttons_visible(self): zoom_idx = self.zoom_levels.index(self.plotutil.get_zoom()) self.zoom_in_btn.Enable(True) self.zoom_out_btn.Enable(True) if len(self.zoom_levels) == zoom_idx + 1: self.zoom_in_btn.Enable(False) if zoom_idx == 0: self.zoom_out_btn.Enable(False) def zoom_in(self, evt): zoom_idx = self.zoom_levels.index(self.plotutil.get_zoom()) zoom_idx += 1 if len(self.zoom_levels) > zoom_idx: zoom = self.zoom_levels[zoom_idx] self.plotutil.set_zoom(zoom) self.zoom_buttons_visible() self.Draw() def zoom_out(self, evt): zoom_idx = self.zoom_levels.index(self.plotutil.get_zoom()) zoom_idx -= 1 if zoom_idx >= 0: zoom = self.zoom_levels[zoom_idx] self.plotutil.set_zoom(zoom) self.zoom_buttons_visible() self.Draw() def plot_mode_changed(self, evt): self.plotmode = evt.GetString() self.plotutil.set_plot_plan(self.plotmode) self.image_idx = int(self.plotutil.get_images_count() / 2) self.clear() self.Draw() def clear(self): self.figure.clear() self.subplot = self.figure.add_subplot(111) self.plotutil.set_figure(self.subplot) def voi_changed(self, msg): voi = msg.data if voi.is_selected(): self.plotutil.add_voi(voi.voxelplan_voi) else: self.plotutil.remove_voi(voi.voxelplan_voi) self.Draw() def set_active_image(self, msg): if self.plotmode == "Transversal": self.image_idx = msg.data self.Draw() def on_patient_loaded(self, msg): self.data = msg.data ctx = self.data.get_images().get_voxelplan() self.plotutil.set_ct(ctx) self.image_idx = int(ctx.dimz / 2) self.setSize() self.bind_keys() def get_figure(self): return self.figure def bind_keys(self): self.Bind(wx.EVT_MOUSEWHEEL, self.on_mouse_wheel) self.Bind(wx.EVT_ENTER_WINDOW, self.on_mouse_enter) self.Bind(wx.EVT_LEAVE_WINDOW, self.on_mouse_leave) self.Bind(wx.EVT_SIZE, self.on_size) self.canvas.Bind(wx.EVT_KEY_DOWN, self.on_key_down) self.canvas.mpl_connect('motion_notify_event', self.on_mouse_move_plot) self.canvas.mpl_connect('button_press_event', self.on_mouse_press_plot) self.canvas.mpl_connect('button_release_event', self.on_mouse_action_ended) self.canvas.mpl_connect('figure_leave_event', self.on_mouse_action_ended) def on_mouse_press_plot(self, evt): if evt.button is 3: pos = evt.guiEvent.GetPosition() standard = True if hasattr(self.plotutil, "contrast_bar"): bar = self.plotutil.contrast_bar if evt.inaxes is bar.ax: menu = self.right_click_contrast() standard = False if hasattr(self.plotutil, "dose_bar"): bar = self.plotutil.dose_bar if evt.inaxes is bar.ax: menu = self.right_click_dose() standard = False if standard: menu = self.normal_right_click_menu() wx.CallAfter(self.show_menu, menu) if self.canvas.HasCapture(): self.canvas.ReleaseMouse() elif evt.button is 1: if hasattr(self.plotutil, "contrast_bar"): bar = self.plotutil.contrast_bar if evt.inaxes is bar.ax: if evt.ydata >= 0.50: self.plot_mouse_action = "contrast_top" else: self.plot_mouse_action = "contrast_bottom" if hasattr(self.plotutil, "dose_bar"): bar = self.plotutil.dose_bar if evt.inaxes is bar.ax: if evt.ydata >= 0.50: self.plot_mouse_action = "dose_top" else: self.plot_mouse_action = "dose_bottom" if hasattr(self.plotutil, "let_bar"): bar = self.plotutil.let_bar if evt.inaxes is bar.ax: if evt.ydata >= 0.50: self.plot_mouse_action = "let_top" else: self.plot_mouse_action = "let_bottom" self.mouse_pos_ini = [evt.x, evt.y] evt.guiEvent.Skip() def show_menu(self, menu): self.PopupMenu(menu) def on_mouse_action_ended(self, evt): self.plot_mouse_action = None def on_mouse_move_plot(self, evt): pos = [evt.x, evt.y] if self.plot_mouse_action is not None: step = [ pos[0] - self.mouse_pos_ini[0], pos[1] - self.mouse_pos_ini[1] ] if self.plot_mouse_action == "contrast_top": contrast = self.plotutil.get_contrast() stepsize = np.log(contrast[1] - contrast[0]) contrast[1] -= stepsize * step[1] self.plotutil.set_contrast(contrast) elif self.plot_mouse_action == "contrast_bottom": contrast = self.plotutil.get_contrast() stepsize = np.log(contrast[1] - contrast[0]) contrast[0] -= stepsize * step[1] self.plotutil.set_contrast(contrast) elif self.plot_mouse_action == "dose_top": dose = self.plotutil.get_min_max_dose() dose[1] -= 0.30 * step[1] self.plotutil.set_dose_min_max(dose) elif self.plot_mouse_action == "dose_bottom": dose = self.plotutil.get_min_max_dose() dose[0] -= 0.30 * step[1] self.plotutil.set_dose_min_max(dose) elif self.plot_mouse_action == "let_top": let = self.plotutil.get_min_max_let() let[1] -= 0.30 * step[1] self.plotutil.set_let_min_max(let) elif self.plot_mouse_action == "let_bottom": let = self.plotutil.get_min_max_let() let[0] -= 0.30 * step[1] self.plotutil.set_let_min_max(let) self.Draw() elif evt.button == 1 and evt.inaxes is self.plotutil.fig_ct.axes: if not None in self.mouse_pos_ini: step = [ pos[0] - self.mouse_pos_ini[0], pos[1] - self.mouse_pos_ini[1] ] if self.plotutil.move_center(step): self.Draw() self.mouse_pos_ini = [evt.x, evt.y] if hasattr(self.plotutil, "fig_ct") and evt.inaxes is self.plotutil.fig_ct.axes: point = self.plotutil.pixel_to_pos( [round(evt.xdata), round(evt.ydata)]) text = "X: %.2f mm Y: %.2f mm / X: %d px Y: %d px" % ( point[1][0], point[1][1], point[0][0], point[0][1]) pub.sendMessage("statusbar.update", {"number": 1, "text": text}) dim = self.data.get_image_dimensions() if self.plotmode == "Transversal": pos = [round(evt.xdata), round(evt.ydata), self.image_idx] elif self.plotmode == "Sagital": pos = [ dim[0] - round(evt.xdata), self.image_idx, dim[2] - round(evt.ydata) ] elif self.plotmode == "Coronal": pos = [ self.image_idx, dim[1] - round(evt.xdata), dim[2] - round(evt.ydata) ] try: ct_value = self.data.get_image_cube()[pos[2], pos[1], pos[0]] text = "Value: %.1f" % (ct_value) plan = self.active_plan if plan is not None: dose = plan.get_dose_cube() if dose is not None: dose_value = dose[pos[2], pos[1], pos[0]] target_dose = plan.get_dose().get_dose() if not target_dose == 0.0: dose_value *= target_dose / 1000 text += " / Dose: %.1f Gy" % (float(dose_value)) else: dose_value /= 10 text += " / Dose: %.1f %%" % (float(dose_value)) let = plan.get_let_cube() if let is not None: let_value = let[pos[2], pos[1], pos[0]] text += " / LET: %.1f kev/um" % (let_value) except IndexError as e: pass pub.sendMessage("statusbar.update", {"number": 2, "text": text}) def normal_right_click_menu(self): menu = wx.Menu() voi_menu = wx.Menu() for voi in self.data.get_vois(): id = wx.NewId() item = voi_menu.AppendCheckItem(id, voi.get_name()) if voi.is_selected(): item.Check() wx.EVT_MENU(self, id, self.menu_voi_selected) if voi_menu.GetMenuItemCount() > 0: menu.AppendSubMenu(voi_menu, "Vois") view_menu = wx.Menu() active_plan = self.active_plan if active_plan is not None: dose = active_plan.get_dose() dose_type_menu = wx.Menu() if dose is not None: id = wx.NewId() item = view_menu.AppendCheckItem(id, "View Dose") if self.plotutil.get_dose() is not None: item.Check() wx.EVT_MENU(self, id, self.toogle_dose) id = wx.NewId() item = dose_type_menu.Append(id, "Color wash") wx.EVT_MENU(self, id, self.change_dose_to_colorwash) id = wx.NewId() item = dose_type_menu.Append(id, "Contour") wx.EVT_MENU(self, id, self.change_dose_to_contour) menu.AppendSubMenu(dose_type_menu, "Dose Visalization") if self.plotutil.get_dose_plot() == "contour": dose_contour_menu = wx.Menu() for level in self.dose_contour_levels: id = wx.NewId() item = dose_contour_menu.AppendCheckItem( id, "%d %%" % level) for contour in self.plotutil.get_dose_contours(): if contour["doselevel"] == level: item.Check() wx.EVT_MENU(self, id, self.toogle_dose_contour) menu.AppendSubMenu(dose_contour_menu, "Dose Contour levels") let = active_plan.get_let() if let is not None: id = wx.NewId() item = view_menu.AppendCheckItem(id, "View LET") if self.plotutil.get_let() is not None: item.Check() wx.EVT_MENU(self, id, self.toogle_let) if view_menu.GetMenuItemCount() > 0: menu.AppendSubMenu(view_menu, "View") field_menu = wx.Menu() for field in active_plan.get_fields(): id = wx.NewId() item = field_menu.AppendCheckItem(id, field.get_name()) if field.is_selected(): item.Check() wx.EVT_MENU(self, id, self.menu_field_selected) if field_menu.GetMenuItemCount() > 0: menu.AppendSubMenu(field_menu, "Fields") jump_menu = wx.Menu() id = wx.NewId() item = jump_menu.Append(id, "First") wx.EVT_MENU(self, id, self.jump_to_first) id = wx.NewId() item = jump_menu.Append(id, "Middle") wx.EVT_MENU(self, id, self.jump_to_middle) id = wx.NewId() item = jump_menu.Append(id, "Last") wx.EVT_MENU(self, id, self.jump_to_last) menu.AppendSubMenu(jump_menu, "Jump To") return menu def right_click_dose(self): menu = wx.Menu() id = wx.NewId() item = menu.Append(id, "Reset") wx.EVT_MENU(menu, id, self.reset_dose_range) colormap_menu = wx.Menu() id = wx.NewId() colormap_menu.Append(id, "Continuous") wx.EVT_MENU(colormap_menu, id, self.set_colormap_dose) id = wx.NewId() colormap_menu.Append(id, "Discrete") wx.EVT_MENU(colormap_menu, id, self.set_colormap_dose) item = menu.AppendSubMenu(colormap_menu, "Colorscale") scale_menu = wx.Menu() id = wx.NewId() scale_menu.Append(id, "Auto") wx.EVT_MENU(scale_menu, id, self.set_dose_scale) if self.active_plan.get_dose().get_dose() > 0.0: id = wx.NewId() scale_menu.Append(id, "Absolute") wx.EVT_MENU(scale_menu, id, self.set_dose_scale) id = wx.NewId() scale_menu.Append(id, "Relative") wx.EVT_MENU(scale_menu, id, self.set_dose_scale) item = menu.AppendSubMenu(scale_menu, "Scale") return menu def right_click_contrast(self): menu = wx.Menu() id = wx.NewId() item = menu.Append(id, "Reset") wx.EVT_MENU(menu, id, self.reset_contrast) return menu def set_colormap_dose(self, evt): colormap = plt.get_cmap(None) name = evt.GetEventObject().GetLabel(evt.GetId()) if name == "Discrete": colormap = cmap_discretize(colormap, 10) self.plotutil.set_colormap_dose(colormap) self.Draw() def set_dose_scale(self, evt): scale = {"auto": "auto", "absolute": "abs", "relative": "rel"} name = evt.GetEventObject().GetLabel(evt.GetId()) self.plotutil.set_dose_axis(scale[name.lower()]) self.Draw() def reset_dose_range(self, evt): self.plotutil.set_dose_min_max(0, 100) self.Draw() def reset_contrast(self, evt): contrast = [-100, 400] self.plotutil.set_contrast(contrast) self.Draw() def jump_to_first(self, evt): self.image_idx = 0 self.Draw() def jump_to_middle(self, evt): self.image_idx = self.plotutil.get_images_count() / 2 self.Draw() def jump_to_last(self, evt): self.image_idx = self.plotutil.get_images_count() - 1 self.Draw() def toogle_dose_contour(self, evt): value = float(evt.GetEventObject().GetLabel(evt.GetId()).split()[0]) if evt.IsChecked(): self.plotutil.add_dose_contour({"doselevel": value, "color": "b"}) else: for contour in self.plotutil.get_dose_contours(): if contour["doselevel"] == value: self.plotutil.remove_dose_contour(contour) self.Draw() def toogle_dose(self, evt): if self.plotutil.get_dose() is None: self.plotutil.set_dose(self.active_plan.get_dose().get_dosecube()) else: self.plotutil.set_dose(None) self.Draw() def toogle_let(self, evt): if self.plotutil.get_let() is None: self.plotutil.set_let(self.active_plan.get_let()) else: self.plotutil.set_let(None) self.Draw() def menu_voi_selected(self, evt): name = evt.GetEventObject().GetLabel(evt.GetId()) name = name.replace("__", "_") voi = self.data.get_vois().get_voi_by_name(name) if not voi is None: voi.toogle_selected() def menu_field_selected(self, evt): name = evt.GetEventObject().GetLabel(evt.GetId()) field = self.active_plan.get_fields().get_field_by_name(name) field.toogle_selected(self.active_plan) def change_dose_to_colorwash(self, evt): self.plotutil.set_dose_plot("colorwash") self.Draw() def change_dose_to_contour(self, evt): self.plotutil.set_dose_plot("contour") self.Draw() def on_size(self, evt): """Refresh the view when the size of the panel changes.""" self.setSize() def on_mouse_wheel(self, evt): delta = evt.GetWheelDelta() rot = evt.GetWheelRotation() rot = rot / delta if evt.ControlDown(): if (rot >= 1): self.zoom_in(None) elif (rot < 1): self.zoom_out(None) return n_images = self.data.get_images().get_voxelplan().dimz if n_images: if (rot >= 1): if (self.image_idx > 0): self.image_idx -= 1 self.Draw() if (rot <= -1): if (self.image_idx < self.plotutil.get_images_count() - 1): self.image_idx += 1 self.Draw() def on_key_down(self, evt): prevkey = [wx.WXK_UP, wx.WXK_PAGEUP] nextkey = [wx.WXK_DOWN, wx.WXK_PAGEDOWN] code = evt.GetKeyCode() if code in prevkey: if (self.image_idx > 0): self.image_idx -= 1 self.Draw() elif code in nextkey: if (self.image_idx < self.plotutil.get_images_count() - 1): self.image_idx += 1 self.Draw() def on_mouse_enter(self, evt): """Set a flag when the cursor enters the window.""" self.mouse_in_window = True def on_mouse_leave(self, evt): """Set a flag when the cursor leaves the window.""" self.mouse_in_window = False def setSize(self): size = self.parent.GetClientSize() size[1] = size[1] - 40 size[0] = size[0] - 5 pixels = tuple(size) self.canvas.SetSize(pixels) self.figure.set_size_inches( float(pixels[0]) / self.figure.get_dpi(), float(pixels[1]) / self.figure.get_dpi()) self.Draw() def Draw(self): self.plotutil.plot(self.image_idx) self.figure.subplots_adjust(left=0, bottom=0, right=1, top=1, wspace=None, hspace=None) # self.figure.tight_layout(pad=0.0) if hasattr(self.plotutil, "dose_bar"): bar = self.plotutil.dose_bar bar.ax.yaxis.label.set_color('white') bar.ax.tick_params(axis='y', colors='white', labelsize=8) if hasattr(self.plotutil, "let_bar"): bar = self.plotutil.let_bar bar.ax.yaxis.label.set_color('white') bar.ax.yaxis.label.set_color('white') bar.ax.tick_params(axis='y', colors='white', labelsize=8) if hasattr(self.plotutil, "contrast_bar"): bar = self.plotutil.contrast_bar bar.ax.yaxis.label.set_color('white') bar.ax.yaxis.set_label_position('left') [t.set_color("white") for t in bar.ax.yaxis.get_ticklabels()] [t.set_size(8) for t in bar.ax.yaxis.get_ticklabels()] # bar.ax.tick_params(axis='y', colors='white',labelsize=8,labelleft=True,labelright=False) self.canvas.draw()
class PlotPanel(wx.Panel): ''' Base class for the plotting in GenX - all the basic functionallity should be implemented in this class. The plots should be derived from this class. These classes should implement an update method to update the plots. ''' def __init__(self, parent, id = -1, color = None, dpi = None , style = wx.NO_FULL_REPAINT_ON_RESIZE|wx.EXPAND|wx.ALL , config = None, config_name = '', **kwargs): wx.Panel.__init__(self,parent, id = id, style = style, **kwargs) self.parent = parent self.callback_window = self self.config = config self.config_name = config_name self.figure = Figure(None,dpi) self.canvas = FigureCanvasWxAgg(self, -1, self.figure) self.canvas.SetExtraStyle(wx.EXPAND) self.SetColor(color) self.Bind(wx.EVT_IDLE, self._onIdle) self.Bind(wx.EVT_SIZE, self._onSize) self._resizeflag = True self.print_size = (15./2.54, 12./2.54) #self._SetSize() # Flags and bindings for zooming self.zoom = False self.zooming = False self.scale = 'linear' self.autoscale = True self.canvas.Bind(wx.EVT_LEFT_DOWN, self.OnLeftMouseButtonDown) self.canvas.Bind(wx.EVT_LEFT_UP, self.OnLeftMouseButtonUp) self.canvas.Bind(wx.EVT_MOTION, self.OnMouseMove) self.canvas.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDblClick) self.canvas.Bind(wx.EVT_RIGHT_UP, self.OnContextMenu) cursor = wx.StockCursor(wx.CURSOR_CROSS) self.canvas.SetCursor(cursor) self.old_scale_state = True self.ax = None # Init printout stuff self.fig_printer = FigurePrinter(self) # Create the drawing bitmap self.bitmap =wx.EmptyBitmap(1, 1) # DEBUG_MSG("__init__() - bitmap w:%d h:%d" % (w,h), 2, self) self._isDrawn = False def SetColor(self, rgbtuple=None): ''' Set the figure and canvas color to be the same ''' if not rgbtuple: rgbtuple = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE).Get() col = [c/255. for c in rgbtuple] self.figure.set_facecolor(col) self.figure.set_edgecolor(col) self.canvas.SetBackgroundColour(wx.Colour(*rgbtuple)) def _onSize(self, evt): self._resizeflag = True self._SetSize() #self.canvas.draw(repaint = False) def _onIdle(self, evt): if self._resizeflag: self._resizeflag = False self._SetSize() #self.canvas.gui_repaint(drawDC = wx.PaintDC(self)) def _SetSize(self, pixels = None): ''' This method can be called to force the Plot to be a desired size which defaults to the ClientSize of the Panel. ''' if not pixels: pixels = self.GetClientSize() self.canvas.SetSize(pixels) #self.figure.set_size_inches(pixels[0]/self.figure.get_dpi() #, pixels[1]/self.figure.get_dpi()) def ReadConfig(self): '''ReadConfig(self) --> None Reads in the config file ''' bool_items = ['zoom', 'autoscale'] bool_func = [self.SetZoom, self.SetAutoScale] if not self.config: return vals = [] for index in range(len(bool_items)): try: val = self.config.get_boolean(self.config_name,\ bool_items[index]) except io.OptionError as e: print('Could not locate option %s.%s'\ %(self.config_name, bool_items[index])) vals.append(None) else: vals.append(val) try: scale = self.config.get(self.config_name, 'y scale') string_sucess = True except io.OptionError as e: string_sucess = False print('Could not locate option %s.%s'\ %(self.config_name, 'scale')) else: self.SetYScale(scale) # This is done due to that the zoom and autoscale has to read # before any commands are issued in order not to overwrite # the config [bool_func[i](vals[i]) for i in range(len(vals)) if vals[i]] def WriteConfig(self): '''WriteConfig(self) --> None Writes the current settings to the config file ''' if self.config: self.config.set(self.config_name, 'zoom', self.GetZoom()) self.config.set(self.config_name, 'autoscale', self.GetAutoScale()) self.config.set(self.config_name, 'y scale', self.GetYScale()) def SetZoom(self, active = False): ''' set the zoomstate ''' #if not self.zoom_sel: #self.zoom_sel = RectangleSelector(self.ax,\ # self.box_select_callback, drawtype='box',useblit=False) #print help(self.zoom_sel.ignore) if active: #self.zoom_sel.ignore = lambda x: False self.zoom = True cursor = wx.StockCursor(wx.CURSOR_MAGNIFIER) self.canvas.SetCursor(cursor) if self.callback_window: evt = state_changed(zoomstate = True,\ yscale = self.GetYScale(), autoscale = self.autoscale) wx.PostEvent(self.callback_window, evt) if self.ax: #self.ax.set_autoscale_on(False) self.old_scale_state = self.GetAutoScale() self.SetAutoScale(False) else: #self.zoom_sel.ignore = lambda x: True self.zoom = False cursor = wx.StockCursor(wx.CURSOR_CROSS) self.canvas.SetCursor(cursor) if self.callback_window: evt = state_changed(zoomstate = False,\ yscale = self.GetYScale(), autoscale = self.autoscale) wx.PostEvent(self.callback_window, evt) if self.ax: #self.ax.set_autoscale_on(self.autoscale) self.SetAutoScale(self.old_scale_state) self.WriteConfig() def GetZoom(self): '''GetZoom(self) --> state [bool] Returns the zoom state of the plot panel. True = zoom active False = zoom inactive ''' return self.zoom def SetAutoScale(self, state): '''SetAutoScale(self, state) --> None Sets autoscale of the main axes wheter or not it should autoscale when plotting ''' #self.ax.set_autoscale_on(state) self.autoscale = state self.WriteConfig() evt = state_changed(zoomstate = self.GetZoom(),\ yscale = self.GetYScale(), autoscale = self.autoscale) wx.PostEvent(self.callback_window, evt) def GetAutoScale(self): '''GetAutoScale(self) --> state [bool] Returns the autoscale state, true if the plots is automatically scaled for each plot command. ''' return self.autoscale def AutoScale(self, force = False): '''AutoScale(self) --> None A log safe way to autoscale the plots - the ordinary axis tight does not work for negative log data. This works! ''' if not (self.autoscale or force): return # If nothing is plotted no autoscale use defaults... if sum([len(line.get_ydata()) > 0 for line in self.ax.lines]) == 0: self.ax.set_xlim(0, 1) self.ax.set_ylim(1e-3, 1.0) return if self.scale == 'log': #print 'log scaling' # Find the lowest possible value of all the y-values that are #greater than zero. check so that y data contain data before min # is applied tmp = [line.get_ydata().compress(line.get_ydata() > 0.0).min()\ for line in self.ax.lines if array(line.get_ydata() > 0.0).sum() > 0] if len(tmp) > 0: ymin = min(tmp) else: ymin = 1e-3 tmp = [line.get_ydata().compress(line.get_ydata() > 0.0).max()\ for line in self.ax.lines if array(line.get_ydata() > 0.0).sum() > 0] if len(tmp) > 0: ymax = max(tmp) else: ymax = 1 else: ymin = min([array(line.get_ydata()).min()\ for line in self.ax.lines if len(line.get_ydata()) > 0]) ymax = max([array(line.get_ydata()).max()\ for line in self.ax.lines if len(line.get_ydata()) > 0]) tmp = [array(line.get_xdata()).min()\ for line in self.ax.lines if len(line.get_ydata()) > 0] if len(tmp) > 0: xmin = min(tmp) else: xmin = 0 tmp = [array(line.get_xdata()).max()\ for line in self.ax.lines if len(line.get_ydata()) > 0] if len(tmp) > 0: xmax = max(tmp) else: xmax = 1 # Set the limits #print 'Autoscaling to: ', ymin, ymax self.ax.set_xlim(xmin, xmax) self.ax.set_ylim(ymin*(1-sign(ymin)*0.05), ymax*(1+sign(ymax)*0.05)) #self.ax.set_yscale(self.scale) self.flush_plot() def SetYScale(self, scalestring): ''' SetYScale(self, scalestring) --> None Sets the y-scale of the main plotting axes. Currently accepts 'log' or 'lin'. ''' if self.ax: if scalestring == 'log': self.scale = 'log' self.AutoScale(force = True) try: self.ax.set_yscale('log') except OverflowError: self.AutoScale(force = True) elif scalestring == 'linear' or scalestring == 'lin': self.scale = 'linear' self.ax.set_yscale('linear') self.AutoScale(force = True) else: raise ValueError('Not allowed scaling') self.flush_plot() evt = state_changed(zoomstate = self.GetZoom(),\ yscale = self.GetYScale(), autoscale = self.autoscale) wx.PostEvent(self.callback_window, evt) self.WriteConfig() def GetYScale(self): '''GetYScale(self) --> String Returns the current y-scale in use. Currently the string 'log' or 'linear'. If the axes does not exist it returns None. ''' if self.ax: return self.ax.get_yscale() else: return None def CopyToClipboard(self, event = None): '''CopyToClipboard(self, event) --> None Copy the plot to the clipboard. ''' self.canvas.Copy_to_Clipboard(event = event) def PrintSetup(self, event = None): '''PrintSetup(self) --> None Sets up the printer. Creates a dialog box ''' self.fig_printer.pageSetup() def PrintPreview(self, event = None): '''PrintPreview(self) --> None Prints a preview on screen. ''' self.fig_printer.previewFigure(self.figure) def Print(self, event= None): '''Print(self) --> None Print the figure. ''' self.fig_printer.printFigure(self.figure) def SetCallbackWindow(self, window): '''SetCallbackWindow(self, window) --> None Sets the callback window that should recieve the events from picking. ''' self.callback_window = window def OnLeftDblClick(self, event): if self.ax and self.zoom: tmp = self.GetAutoScale() self.SetAutoScale(True) self.AutoScale() self.SetAutoScale(tmp) #self.AutoScale() #self.flush_plot() #self.ax.set_autoscale_on(False) def OnLeftMouseButtonDown(self, event): self.start_pos = event.GetPosition() #print 'Left Mouse button pressed ', self.ax.transData.inverse_xy_tup(self.start_pos) class Point: pass p = Point() p.x, p.y = self.start_pos size = self.canvas.GetClientSize() p.y = (size.height - p.y) if self.zoom and self.ax: if mat_ver > zoom_ver: in_axes = self.ax.in_axes(p) else: in_axes = self.ax.in_axes(*self.start_pos) if in_axes: self.zooming = True self.cur_rect = None self.canvas.CaptureMouse() else: self.zooming = False elif self.ax: size = self.canvas.GetClientSize() if mat_ver > zoom_ver: xy = self.ax.transData.inverted().transform(\ array([self.start_pos[0], size.height-self.start_pos[1]])\ [newaxis,:]) x, y = xy[0,0], xy[0,1] else: x, y = self.ax.transData.inverse_xy_tup(\ (self.start_pos[0], size.height - self.start_pos[1])) if self.callback_window: evt = plot_position(text = '(%.3e, %.3e)'%(x, y)) wx.PostEvent(self.callback_window, evt) #print x,y def OnMouseMove(self, event): if self.zooming and event.Dragging() and event.LeftIsDown(): self.cur_pos = event.GetPosition() #print 'Mouse Move ', self.ax.transData.inverse_xy_tup(self.cur_pos) class Point: pass p = Point() p.x, p.y = self.cur_pos size = self.canvas.GetClientSize() p.y = (size.height - p.y) if mat_ver > zoom_ver: in_axes = self.ax.in_axes(p) else: in_axes = self.ax.in_axes(*self.start_pos) if in_axes: new_rect = (min(self.start_pos[0], self.cur_pos[0]), min(self.start_pos[1], self.cur_pos[1]), abs(self.cur_pos[0] - self.start_pos[0]), abs(self.cur_pos[1] - self.start_pos[1])) self._DrawAndErase(new_rect, self.cur_rect) self.cur_rect = new_rect #event.Skip() def OnLeftMouseButtonUp(self, event): if self.canvas.HasCapture(): #print 'Left Mouse button up' self.canvas.ReleaseMouse() if self.zooming and self.cur_rect: # Note: The coordinte system for matplotlib have a different # direction of the y-axis and a different origin! size = self.canvas.GetClientSize() if mat_ver > zoom_ver: start = self.ax.transData.inverted().transform(\ array([self.start_pos[0], size.height-self.start_pos[1]])[newaxis,:]) end = self.ax.transData.inverted().transform(\ array([self.cur_pos[0], size.height-self.cur_pos[1]])[newaxis, :]) xend, yend = end[0,0], end[0,1] xstart, ystart = start[0,0], start[0,1] else: xstart, ystart = self.ax.transData.inverse_xy_tup(\ (self.start_pos[0], size.height-self.start_pos[1])) xend, yend = self.ax.transData.inverse_xy_tup(\ (self.cur_pos[0], size.height-self.cur_pos[1])) #print xstart, xend #print ystart, yend self.ax.set_xlim(min(xstart,xend), max(xstart,xend)) self.ax.set_ylim(min(ystart,yend), max(ystart,yend)) self.flush_plot() self.zooming = False def _DrawAndErase(self, box_to_draw, box_to_erase = None): '''_DrawAndErase(self, box_to_draw, box_to_erase = None) --> None ''' dc = wx.ClientDC(self.canvas) # dc.BeginDrawing() dc.SetPen(wx.Pen(wx.WHITE, 1, wx.DOT)) dc.SetBrush(wx.TRANSPARENT_BRUSH) dc.SetLogicalFunction(wx.XOR) if box_to_erase: dc.DrawRectangle(*box_to_erase) dc.DrawRectangle(*box_to_draw) # dc.EndDrawing() def OnContextMenu(self, event): '''OnContextMenu(self, event) --> None Callback to show the popmenu for the plot which allows various settings to be made. ''' menu = wx.Menu() zoomID = wx.NewId() menu.AppendCheckItem(zoomID, "Zoom") menu.Check(zoomID, self.GetZoom()) def OnZoom(event): self.SetZoom(not self.GetZoom()) self.Bind(wx.EVT_MENU, OnZoom, id = zoomID) zoomallID = wx.NewId() menu.Append(zoomallID, 'Zoom All') def zoomall(event): tmp = self.GetAutoScale() self.SetAutoScale(True) self.AutoScale() self.SetAutoScale(tmp) #self.flush_plot() self.Bind(wx.EVT_MENU, zoomall, id = zoomallID) copyID = wx.NewId() menu.Append(copyID, "Copy") def copy(event): self.CopyToClipboard() self.Bind(wx.EVT_MENU, copy, id = copyID) yscalemenu = wx.Menu() logID = wx.NewId() linID = wx.NewId() yscalemenu.AppendRadioItem(logID, "log") yscalemenu.AppendRadioItem(linID, "linear") menu.AppendMenu(-1, "y-scale", yscalemenu) if self.GetYScale() == 'log': yscalemenu.Check(logID, True) else: yscalemenu.Check(linID, True) def yscale_log(event): if self.ax: self.SetYScale('log') self.AutoScale() self.flush_plot() def yscale_lin(event): if self.ax: self.SetYScale('lin') self.AutoScale() self.flush_plot() self.Bind(wx.EVT_MENU, yscale_log, id = logID) self.Bind(wx.EVT_MENU, yscale_lin, id = linID) autoscaleID = wx.NewId() menu.AppendCheckItem(autoscaleID, "Autoscale") menu.Check(autoscaleID, self.GetAutoScale()) def OnAutoScale(event): self.SetAutoScale(not self.GetAutoScale()) self.Bind(wx.EVT_MENU, OnAutoScale, id = autoscaleID) # Time to show the menu self.PopupMenu(menu) menu.Destroy() def flush_plot(self): #self._SetSize() #self.canvas.gui_repaint(drawDC = wx.PaintDC(self)) #self.ax.set_yscale(self.scale) self.canvas.draw() def update(self, data): pass