class BoundaryClick(object): """ bry = BoundaryClick(x=[], y=[], beta=None, ax=gca(), **gridgen_options) If x, y and beta are not given, and interactive polygon creation session is started. The initial boundary is sketched out by clicking the points (in a counterclockwise manner, usually starting in the upper lefthand corner of the boundary). At this point the verticies are marked by orange circles. Switch to editing mode by hitting return. This changes the vertecies to black. At this point, beta values may be created to define the corners of the grid (see below). Current data are always available in bry.x, bry.y (combined in p.verts) and bry.beta attributes. Key commands: enter : switch to grid editing mode t : toggle visibility of verticies d : delete a vertex i : insert a vertex at a point on the polygon line p : define vertex as beta=1 (a Positive turn, marked with green triangle) m : define vertex as beta=1 (a Negative turn, marked with red triangle) z : define vertex as beta=0 (no corner, marked with green triangle) G : generate grid from the current boundary using gridgen T : toggle visability of the current grid R : remove the gridlines from the figure Methods: bry.dump(bry_file) Write the current boundary informtion (bry.x, bry.y, bry.beta) to a cPickle file bry_file. bry.load(bry_file) Read in boundary informtion (x, y, beta) from the cPickle file bry_file. bry.remove_grid() Remove gridlines from axes. """ _showverts = True _showbetas = True _showgrid = True _epsilon = 5 # max pixel distance to count as a vertex hit def _update_beta_lines(self): """Update the m/pline by finding the (x,y) points where self.beta== -/+ 1""" x = self._line.get_xdata() y = self._line.get_ydata() xp = [x[n] for n in range(len(x)) if self.beta[n]==1] yp = [y[n] for n in range(len(y)) if self.beta[n]==1] self._pline.set_data(xp, yp) xm = [x[n] for n in range(len(x)) if self.beta[n]==-1] ym = [y[n] for n in range(len(y)) if self.beta[n]==-1] self._mline.set_data(xm, ym) xz = [x[n] for n in range(len(x)) if self.beta[n]==0] yz = [y[n] for n in range(len(y)) if self.beta[n]==0] self._zline.set_data(xz, yz) if len(x)-1 < self.gridgen_options['ul_idx']: self.gridgen_options['ul_idx'] = len(x)-1 xs = x[self.gridgen_options['ul_idx']] ys = y[self.gridgen_options['ul_idx']] self._sline.set_data(xs, ys) def remove_grid(self): """Remove a generated grid from the BoundaryClick figure""" if hasattr(self, '_gridlines'): for line in self._gridlines: self._ax.lines.remove(line) delattr(self, '_gridlines') def _init_boundary_interactor(self): """Send polyclick thread to boundary interactor""" # Get rid old mpl connections. self._ax.figure.canvas.mpl_disconnect(self._key_id) self._ax.figure.canvas.mpl_disconnect(self._click_id) # Shade the selected region in a polygon self._poly = Polygon(self.verts, alpha=0.1, fc='k', animated=True) self._ax.add_patch(self._poly) # change line to animated # self._line.set_animated(True) # self._line.set_markerfacecolor('k') self._line.set_marker('None') # Link in the two lines that will show the beta values # pline for positive turns, mline for negative (minus) turns. self._pline = Line2D([], [], marker='^', ms=12, mfc='g', animated=True, lw=0) self._mline = Line2D([], [], marker='v', ms=12, mfc='r', animated=True, lw=0) self._zline = Line2D([], [], marker='o', mfc='k', animated=True, lw=0) self._sline = Line2D([], [], marker='s', mfc='k', animated=True, lw=0) self._update_beta_lines() self._ax.add_artist(self._pline) self._ax.add_artist(self._mline) self._ax.add_artist(self._zline) self._ax.add_artist(self._sline) # get the canvas and connect the callback events cid = self._poly.add_callback(self._poly_changed) self._ind = None # the active vert self._canvas.mpl_connect('draw_event', self._draw_callback) self._canvas.mpl_connect('button_press_event', self._button_press_callback) self._canvas.mpl_connect('key_press_event', self._key_press_callback) self._canvas.mpl_connect('button_release_event', self._button_release_callback) self._canvas.mpl_connect('motion_notify_event', self._motion_notify_callback) def _on_return(self, event): if event.key in ('enter', None): self._init_boundary_interactor() def _on_click(self, event): self.x.append(event.xdata) self.y.append(event.ydata) self.beta.append(0) self._line.set_data(self.x, self.y) self._background = self._canvas.copy_from_bbox(self._ax.bbox) self._ax.draw_artist(self._line) self._canvas.blit(self._ax.bbox) 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._pline) self._ax.draw_artist(self._mline) self._ax.draw_artist(self._zline) self._ax.draw_artist(self._sline) self._ax.draw_artist(self._line) self._canvas.blit(self._ax.bbox) def _poly_changed(self, poly): 'this method is called whenever the polygon object is called' # only copy the artist props to the line (except visibility) vis = self._line.get_visible() Artist.update_from(self._line, poly) self._line.set_visible(vis) # don't use the poly visibility state def _get_ind_under_point(self, event): 'get the index of the vertex under point if within epsilon tolerance' try: x, y = zip(*self._poly.xy) # display coords xt, yt = self._poly.get_transform().numerix_x_y(x, y) d = sqrt((xt-event.x)**2 + (yt-event.y)**2) indseq = nonzero(equal(d, amin(d))) ind = indseq[0] if d[ind]>=self._epsilon: ind = None return ind except: # display coords xy = asarray(self._poly.xy) xyt = self._poly.get_transform().transform(xy) xt, yt = xyt[:, 0], xyt[:, 1] d = sqrt((xt-event.x)**2 + (yt-event.y)**2) indseq = nonzero(equal(d, 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' # if not self._showverts: return if event.inaxes==None: return if event.button != 1: return self._ind = self._get_ind_under_point(event) def _button_release_callback(self, event): 'whenever a mouse button is released' # if not self._showverts: return if event.button != 1: return self._ind = None def _key_press_callback(self, event): 'whenever a key is pressed' if not event.inaxes: return if event.key=='shift': return if event.key=='t': self._showbetas = not self._showbetas self._pline.set_visible(self._showbetas) self._mline.set_visible(self._showbetas) self._zline.set_visible(self._showbetas) self._sline.set_visible(self._showbetas) elif event.key=='d': ind = self._get_ind_under_point(event) if ind is not None: self._poly.xy = [tup for i,tup in enumerate(self._poly.xy) if i!=ind] self._line.set_data(zip(*self._poly.xy)) self.beta = [beta for i,beta in enumerate(self.beta) if i!=ind] elif event.key=='p': ind = self._get_ind_under_point(event) if ind is not None: self.beta[ind] = 1.0 elif event.key=='m': ind = self._get_ind_under_point(event) if ind is not None: self.beta[ind] = -1.0 elif event.key=='z': ind = self._get_ind_under_point(event) if ind is not None: self.beta[ind] = 0.0 elif event.key=='s': ind = self._get_ind_under_point(event) if ind is not None: self.gridgen_options['ul_idx'] = ind elif event.key=='i': xys = self._poly.get_transform().seq_xy_tups(self._poly.xy) p = event.x, event.y # display coords for i in range(len(xys)-1): s0 = xys[i] s1 = xys[i+1] d = dist_point_to_segment(p, s0, s1) if d<=self._epsilon: self._poly.xy.insert(i+1, (event.xdata, event.ydata)) self._line.set_data(zip(*self._poly.xy)) self.beta.insert(i+1, 0) break s0 = xys[-1] s1 = xys[0] d = dist_point_to_segment(p, s0, s1) if d<=self._epsilon: self._poly.xy.append((event.xdata, event.ydata)) self._line.set_data(zip(*self._poly.xy)) self.beta.append(0) elif event.key=='G' or event.key == '1': options = copy.deepcopy(self.gridgen_options) shp = options.pop('shp') self.grd = pyroms.gridgen(self.x, self.y, self.beta, shp, **options) self.remove_grid() self._showgrid = True gridlineprops = {'linestyle':'-', 'color':'k', 'lw':0.2} self._gridlines = [] for line in self._ax._get_lines(*(self.grd.x, self.grd.y), **gridlineprops): self._ax.add_line(line) self._gridlines.append(line) for line in self._ax._get_lines(*(self.grd.x.T, self.grd.y.T), **gridlineprops): self._ax.add_line(line) self._gridlines.append(line) elif event.key=='R': self.remove_grid() elif event.key=='T' or event.key == '2': self._showgrid = not self._showgrid if hasattr(self, '_gridlines'): for line in self._gridlines: line.set_visible(self._showgrid) self._update_beta_lines() self._draw_callback(event) self._canvas.draw() def _motion_notify_callback(self, event): 'on mouse movement' # if not self._showverts: return if self._ind is None: return if event.inaxes is None: return if event.button != 1: return x,y = event.xdata, event.ydata self._poly.xy[self._ind] = x,y x, y = zip(*self._poly.xy) self._line.set_data(x, y) self._update_beta_lines() self._canvas.restore_region(self._background) self._ax.draw_artist(self._poly) self._ax.draw_artist(self._pline) self._ax.draw_artist(self._mline) self._ax.draw_artist(self._zline) self._ax.draw_artist(self._sline) self._ax.draw_artist(self._line) self._canvas.blit(self._ax.bbox) def __init__(self, x=[], y=[], beta=None, ax=None, **gridgen_options): if isinstance(x, str): x, y, beta = load(x) if ax is None: ax = pl.gca() self._ax = ax # Set default gridgen option, and copy over specified options. self.gridgen_options = {'ul_idx': 0, 'shp': (32, 32), 'verbose': True} for key, value in gridgen_options.iteritems(): self.gridgen_options[key] = gridgen_options[key] x = list(x); y = list(y) assert len(x)==len(y), 'arrays must be equal length' if beta is None: self.beta = [0 for xi in x] else: assert len(x)==len(beta), 'beta must have same length as x and y' self.beta = list(beta) self._line = pl.Line2D(x, y, marker='o', markerfacecolor='orange', animated=True) self._ax.add_line(self._line) self._canvas = self._line.figure.canvas self._key_id = self._ax.figure.canvas.mpl_connect('key_press_event', self._on_return) self._click_id = self._ax.figure.canvas.mpl_connect('button_press_event', self._on_click) if len(x) > 0: self._init_boundary_interactor() def dump(self, bry_file): f = open(bry_file, 'wb') bry_dict = {'x': self.x, 'y': self.y, 'beta': self.beta} cPickle.dump(bry_dict, f, protocol=-1) f.close() def load(self, bry_file): bry_dict = load(bry_file) x = bry_dict['x'] y = bry_dict['y'] beta = bry_dict['beta'] self._line.set_data(x, y) if hasattr(self, '_poly'): self._poly.xy = zip(x, y) self._update_beta_lines() self._draw_callback(None) self._canvas.draw() def _get_verts(self): return zip(self.x, self.y) verts = property(_get_verts) def get_xdata(self): return self._line.get_xdata() x = property(get_xdata) def get_ydata(self): return self._line.get_ydata() y = property(get_ydata)
class MaskDrawer(object): """An interactive polygon mask drawer on an image. Parameters ---------- ax : matplotlib plot axis Inpimg : 2d numpy array Input image to overlay for drawing mask Mask : Boolean numpy array same size of inpimg A Mask which will be used as initial mask and updated upon confirmation max_ds : float Max pixel distance to count as a vertex hit. PolyAtStart : List of vertices A list of square vertices to draw the initial polygon 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 max_ds of the line connecting two existing vertices 'n' : Invert the region selected by polynomial for masking 'c' : Confirm the polygon and update the mask """ showverts = True epsilon = 5 # max pixel distance to count as a vertex hit def __init__(self, ax, Inpimg, Mask, max_ds=10,PolyAtStart = [(50,50),(100,50),(100,100),(50,100)]): self.showverts = True self.max_ds = max_ds self.Mask = Mask self.img = Inpimg self.maskinvert = False # imshow the image self.imgplot = ax.imshow(np.ma.filled(np.ma.array(self.img,mask=self.Mask,fill_value=np.nan)), cmap=cm.gray) self.poly = Polygon(PolyAtStart, animated=True, fc='y', ec='none', alpha=0.5) ax.add_patch(self.poly) ax.set_clip_on(False) ax.set_title("Click and drag a point to move it; " "'i' to insert; 'd' to delete.\n" "'n' to invert the region for masking, 'c' to confirm & apply the mask.") self.ax = ax x, y = zip(*self.poly.xy) self.line = Line2D(x, y, color='none', marker='o', mfc='r', alpha=0.7, animated=True) # self._update_line() self.ax.add_line(self.line) self.poly.add_callback(self.poly_changed) self._ind = None # the active vert canvas = self.poly.figure.canvas canvas.mpl_connect('draw_event', self.draw_callback) canvas.mpl_connect('button_press_event', self.button_press_callback) canvas.mpl_connect('button_release_event', self.button_release_callback) canvas.mpl_connect('key_press_event', self.key_press_callback) canvas.mpl_connect('motion_notify_event', self.motion_notify_callback) self.canvas = canvas 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 poly_changed(self, poly): 'this method is called whenever the polygon object is called' # only copy the artist props to the line (except visibility) vis = self.line.get_visible() Artist.update_from(self.line, poly) self.line.set_visible(vis) # don't use the poly visibility state 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' if not self.showverts: return if event.inaxes==None: return if event.button != 1: return self._ind = self.get_ind_under_point(event) def button_release_callback(self, event): 'whenever a mouse button is released' if not self.showverts: return if event.button != 1: return self._ind = None def key_press_callback(self, event): 'whenever a key is pressed' if not event.inaxes: return if event.key=='t': self.showverts = not self.showverts self.line.set_visible(self.showverts) if not self.showverts: self._ind = None elif event.key=='d': ind = self.get_ind_under_point(event) if ind is not None: self.poly.xy = [tup for i,tup in enumerate(self.poly.xy) if i!=ind] self.line.set_data(zip(*self.poly.xy)) elif event.key=='i': xys = self.poly.get_transform().transform(self.poly.xy) p = event.x, event.y # display coords for i in range(len(xys)-1): s0 = xys[i] s1 = xys[i+1] d = dist_point_to_segment(p, s0, s1) if d<=self.epsilon: self.poly.xy = np.array( list(self.poly.xy[:i]) + [(event.xdata, event.ydata)] + list(self.poly.xy[i:])) self.line.set_data(zip(*self.poly.xy)) break elif event.key=='n': """ Flips the region inside out of the polygon to be masked """ print('Inverting the mask region') self.maskinvert = not self.maskinvert elif event.key=='c': """ Confirm the drawn polynomial shape and add update the mask """ self.UpdateMask() #Update the imshowed image with new mask self.imgplot.set_data(np.ma.filled(np.ma.array(self.img,mask=self.Mask,fill_value=np.nan))) self.imgplot.figure.canvas.draw() self.canvas.draw() def UpdateMask(self): """ Updates the maks array with points insied the polygon """ print('Updating the original Mask..') Path = self.poly.get_path() h, w = self.Mask.shape y, x = np.mgrid[:h,:w] XYpoints = np.transpose((x.ravel(), y.ravel())) NewMask = Path.contains_points(XYpoints) if self.maskinvert : NewMask = ~NewMask # Combine the two mask by taing an elemet wise or self.Mask = self.Mask | NewMask.reshape((h,w)) def motion_notify_callback(self, event): 'on mouse movement' if not self.showverts: return if self._ind is None: return if event.inaxes is None: return if event.button != 1: return x,y = event.xdata, event.ydata 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)
class NDViewer(QWidget): """ This is a widget for visulazation of hight dimentional space. This is similar to GGobi http://www.ggobi.org/publications/. """ selection_changed = pyqtSignal() def __init__(self , parent=None , settings = None, show_tour = True, show_select_tools = False, ): super(NDViewer, self).__init__(parent) self.settings = settings self.show_tour = show_tour self.show_select_tools = show_select_tools self.plot_parameters = ParamWidget(PlotParameters).to_dict() mainLayout = QVBoxLayout() self.setLayout(mainLayout) h = QHBoxLayout() mainLayout.addLayout(h) self.widgetProjection = QWidget() v = QVBoxLayout() h.addLayout(v) self.scrollArea = QScrollArea() self.scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.scrollArea.setWidget( self.widgetProjection ) self.scrollArea.setMinimumWidth(180) v.addWidget( self.scrollArea ) if show_tour: self.randButton = QPushButton( QIcon(':/roll.png') , 'Random') v.addWidget(self.randButton) self.randButton.clicked.connect(self.randomPosition) self.startRandTourButton = QPushButton( QIcon(':/helicoper_and_roll.png') , 'Random tour', checkable = True) v.addWidget(self.startRandTourButton) self.startRandTourButton.clicked.connect(self.startStopTour) self.timerRandTour = QTimer(interval = self.plot_parameters['refresh_interval']) self.timerRandTour.timeout.connect(self.stepRandTour) self.startOptimizedTourButton = QPushButton( QIcon(':/helicoper_and_magic.png') , 'Optimized tour', checkable = True) v.addWidget(self.startOptimizedTourButton) self.startOptimizedTourButton.clicked.connect(self.startStopTour) self.connect(self.startOptimizedTourButton, SIGNAL('clicked()') , self.startStopTour) self.timerOptimizedTour = QTimer(interval = self.plot_parameters['refresh_interval']) self.timerOptimizedTour.timeout.connect(self.stepOptimizedTour) but = QPushButton( QIcon(':/configure.png') , 'Configure') v.addWidget(but) but.clicked.connect(self.openConfigure) if show_select_tools: h2 = QHBoxLayout() groupbox = QGroupBox( 'Selection mode') groupbox.setLayout(h2) v.addWidget(groupbox) icons = [ ['pickone' , ':/color-picker.png'], ['lasso' , ':/lasso.png'], ['contour' , ':/polygon-editor.png'], ] self.selectButton = { } for name, icon in icons: but = QPushButton(QIcon(icon),'', checkable = True, autoExclusive = True) h2.addWidget(but) but.clicked.connect(self.changeSelectMode) self.selectButton[name] = but self.clearSelectBut = QPushButton(QIcon(':/view-refresh.png'),'Clear selection') v.addWidget(self.clearSelectBut) self.clearSelectBut.clicked.connect(self.clearSelection) self.canvas = SimpleCanvas() self.ax = self.canvas.fig.add_axes([0.02, 0.02, .96, .96]) h.addWidget(self.canvas,2) self.canvas.setMinimumWidth(180) self.ax_circle = None self.create_axe_circle() self.tour_running = False self.dim = 0 # self.spins = [ ] # spin widget list self.toBeDisconnected = [ ] # manage mpl_connect and disconnect self.selectMode = None # actual mode self.epsilon = 4. # for pickle event self.poly = None # for contour self.selectionLine = None self.selection_changed.connect(self.redrawSelection) ## draw and redraw ## def change_dim(self, ndim): self.projection = np.zeros( (ndim, 2)) self.projection[0,0] = 1 self.projection[1,1] = 1 #spinwidgets self.widgetProjection = QWidget() self.widgetProjection.updateGeometry() g = QGridLayout() self.widgetProjection.setLayout(g) self.spins = [ ] for i in range(ndim): d1 = QDoubleSpinBox(value = self.projection[i,0]) d2 = QDoubleSpinBox(value = self.projection[i,1]) g.addWidget( QLabel('dim {}'.format(i)), i, 0 ) g.addWidget( d1, i, 1 ) g.addWidget( d2, i, 2 ) for d in [d1, d2]: d.valueChanged.connect(self.spinsChanged) d.setSingleStep(0.05) d.setRange(-1.,1.) self.spins.append( [d1, d2] ) self.scrollArea.setWidget( self.widgetProjection ) self.scrollArea.update() self.dim = ndim def change_point(self, data, data_labels = None, colors = None, subset = None): """ data = dim 0 elements dim 1 dimension data_labels = vector of cluster for colors """ if data.shape[1] != self.dim : self.change_dim(data.shape[1]) self.data = data self.actualSelection = np.zeros(data.shape[0], dtype = bool) if data_labels is None: data_labels = np.zeros( data.shape[0], dtype = 'i') self.data_labels = data_labels self.all_labels = np.unique(self.data_labels) if colors is None: colors = [ 'c' , 'g' , 'r' , 'b' , 'k' , 'm' , 'y']*100 self.colors = colors if subset is None: subset = { } for c in self.all_labels: ind = self.data_labels ==c subset[c] = ind self.subset = subset self.fullRedraw() self.refreshSpins() def fullRedraw(self): self.ax.clear() for c in self.all_labels: ind = self.subset[c] proj = np.dot( self.data[ind,:], self.projection ) self.ax.plot( proj[:,0], proj[:,1], #proj[ind,0] , proj[ind,1], linestyle = 'None', marker = '.', color = self.colors[c], picker=self.epsilon) self.redraw() def redraw(self): if not(self.plot_parameters['autoscale']): self.ax.set_xlim( self.plot_parameters['xlim'] ) self.ax.set_ylim( self.plot_parameters['ylim'] ) self.canvas.draw() def spinsChanged(self,value): for i in range(self.projection.shape[0]): self.projection[i,0] =self.spins[i][0].value() self.projection[i,1] =self.spins[i][1].value() if self.plot_parameters['force_orthonormality']: m = np.sqrt(np.sum(self.projection**2, axis=0)) m = m[np.newaxis, :] self.projection /= m self.refreshSpins() self.fullRedraw() def refreshSpins(self): for i in range(self.projection.shape[0]): d1, d2 = self.spins[i] for d in [d1,d2]: d.valueChanged.disconnect(self.spinsChanged) d1.setValue(self.projection[i,0]) d2.setValue(self.projection[i,1]) for d in [d1,d2]: d.valueChanged.connect(self.spinsChanged) if self.plot_parameters['display_circle']: self.refreshCircleRadius() def refreshCircleRadius(self): for l in self.radiusLines: self.ax_circle.lines.remove(l) self.radiusLines = [ ] for i in range(self.projection.shape[0]): l, = self.ax_circle.plot([0,self.projection[i,0]] , [0 , self.projection[i,1]] , color = 'g') self.radiusLines.append(l) self.canvas.draw() def create_axe_circle(self): if self.plot_parameters['display_circle']: if self.ax_circle is None: ax= self.canvas.fig.add_axes([0.04, 0.04, .1, .1]) else: ax = self.ax_circle ax.clear() ax.set_xticks([ ]) ax.set_yticks([ ]) circle = Circle((0,0) , radius = 1. , facecolor = 'w') ax.add_patch(circle) ax.set_xlim([-1.02,1.02]) ax.set_ylim([-1.02,1.02]) self.ax_circle = ax self.canvas.draw() self.radiusLines = [ ] else: if self.ax_circle is not None: self.canvas.fig.delaxes(self.ax_circle) self.ax_circle = None ## config ## def openConfigure(self): dia = ParamDialog(PlotParameters, settings = self.settings, settingskey = 'ndviewer/options' ,title = 'Plot parameters',) dia.update(self.plot_parameters) if dia.exec_(): self.plot_parameters = dia.to_dict() self.timerRandTour.setInterval(self.plot_parameters['refresh_interval']) self.timerOptimizedTour.setInterval(self.plot_parameters['refresh_interval']) self.create_axe_circle() self.fullRedraw() ## random and tour tour ## def randomPosition(self): ndim = self.projection.shape[0] self.projection = np.random.rand(ndim,2)*2-1. if self.plot_parameters['force_orthonormality']: m = np.sqrt(np.sum(self.projection**2, axis=0)) self.projection /= m self.refreshSpins( ) self.fullRedraw( ) def startStopTour(self): if self.sender() == self.startRandTourButton: but = self.startRandTourButton mode = 'rand' self.startOptimizedTourButton.setChecked(False) elif self.sender() == self.startOptimizedTourButton: but = self.startOptimizedTourButton mode = 'optimized' self.startRandTourButton.setChecked(False) start = but.isChecked() if start: if self.show_select_tools: for name, but in self.selectButton.iteritems(): but.setChecked(False) but.setEnabled(False) self.clearSelectBut.setEnabled(False) self.selectMode = None self.clearSelection() if mode == 'rand': self.timerOptimizedTour.stop() self.timerRandTour.start() self.actualStep = self.plot_parameters['nsteps'] +1 elif mode == 'optimized': self.timerRandTour.stop() self.timerOptimizedTour.start() self.tour_running = True else: if self.show_select_tools: for name, but in self.selectButton.iteritems(): but.setEnabled(True) self.clearSelectBut.setEnabled(True) self.changeSelectMode() self.timerRandTour.stop() self.timerOptimizedTour.stop() self.tour_running = False self.refreshSpins( ) self.fullRedraw( ) def stepRandTour(self): nsteps = self.plot_parameters['nsteps'] ndim = self.projection.shape[0] if self.actualStep >= nsteps: # random for next etap nextEtap = np.random.rand(ndim,2)*2-1. self.allSteps = np.empty( (ndim , 2 , nsteps)) for i in range(ndim): for j in range(2): self.allSteps[i,j , : ] = np.linspace(self.projection[i,j] , nextEtap[i,j] , nsteps) if self.plot_parameters['force_orthonormality']: m = np.sqrt(np.sum(self.allSteps**2, axis=0)) m = m[np.newaxis, : , :] self.allSteps /= m self.actualStep = 0 self.projection = self.allSteps[:,: , self.actualStep] self.actualStep += 1 self.refreshSpins( ) self.fullRedraw( ) def stepOptimizedTour(self): actual_lda = ComputeIndexLda(self.projection, self.data, self.data_labels) nloop = 1 ndim = self.projection.shape[0] for i in range(nloop): delta = (np.random.rand(ndim, 2)*2-1)/20. new_proj = self.projection + delta # normalize orthonormality m = np.sqrt(np.sum(new_proj**2, axis=0)) m = m[np.newaxis, :] new_proj /= m new_lda = ComputeIndexLda(new_proj, self.data, self.data_labels) if new_lda >=actual_lda: actual_lda = new_lda self.projection = new_proj self.refreshSpins() self.fullRedraw() ## selections ## def changeSelection(self, new_selection, emit_signal = False): self.actualSelection = new_selection if emit_signal: self.selection_changed.emit() if not self.tour_running: self.redrawSelection() def changeSelectMode(self): self.selectMode = None for name, but in self.selectButton.iteritems(): if but.isChecked(): self.selectMode = name for e in self.toBeDisconnected: self.canvas.mpl_disconnect(e) self.toBeDisconnected = [ ] self.clearSelection() def clearSelection(self): self.clearArtistSelection() if self.selectMode =='pickone': cid = self.canvas.mpl_connect('pick_event', self.onPick) self.toBeDisconnected.append(cid) elif self.selectMode =='contour': cid1 = self.canvas.mpl_connect('button_press_event', self.pressContour) cid2 = self.canvas.mpl_connect('button_release_event', self.releaseContour) cid3 = self.canvas.mpl_connect('motion_notify_event', self.motionContour) self.toBeDisconnected += [cid1, cid2, cid3 ] self.poly =None self._ind = None elif self.selectMode =='lasso': cid = self.canvas.mpl_connect('button_press_event', self.startLasso) self.toBeDisconnected.append(cid) self.actualSelection[:] = False self.selection_changed.emit() def clearArtistSelection(self): if self.poly is not None: self.ax.lines.remove(self.line) self.ax.patches.remove(self.poly) self.poly = None self.line = None self.redraw() # should not: if hasattr(self,'lasso'): self.canvas.widgetlock.release(self.lasso) del self.lasso def onPick(self , event): if isinstance(event.artist, Line2D): xdata, ydata = event.artist.get_data() x,y = xdata[event.ind[0]], ydata[event.ind[0]] self.actualSelection[:] = False self.actualSelection[np.argmin( np.sum( (np.dot( self.data, self.projection )-np.array([[ x,y ]]) )**2 , axis=1)) ] = True else: self.actualSelection[:] = False self.selection_changed.emit() def startLasso(self, event): if event.button != 1: return if self.canvas.widgetlock.locked(): # sometimes there is a bug lassostop is not intercepted!!! # so to avoid 2 start self.clearArtistSelection() return if event.inaxes is None: return self.lasso = Lasso(event.inaxes, (event.xdata, event.ydata), self.stopLasso) # acquire a lock on the widget drawing self.canvas.widgetlock(self.lasso) def stopLasso(self, verts): self.actualSelection = points_inside_poly(np.dot( self.data, self.projection ), verts) self.canvas.widgetlock.release(self.lasso) del self.lasso self.selection_changed.emit() def pressContour(self, event): if event.inaxes==None: return if event.button != 1: return # new contour if self.poly is None: self.poly = Polygon( [[event.xdata , event.ydata]] , animated=False , alpha = .3 , color = 'g') self.ax.add_patch(self.poly) self.line, = self.ax.plot([event.xdata] , [event.ydata] , color = 'g', linewidth = 2 , marker = 'o' , markerfacecolor='g', animated=False) self.redraw() return # event near a point 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] self._ind = indseq[0] if d[self._ind]>=self.epsilon: self._ind = None # new point if self._ind is None: self.poly.xy = np.array( list(self.poly.xy) + [[event.xdata , event.ydata]]) self.line.set_xdata( np.array(list(self.line.get_xdata()) + [ event.xdata]) ) self.line.set_ydata( np.array(list(self.line.get_ydata()) + [ event.ydata]) ) self.redraw() self.actualSelection = points_inside_poly(np.dot( self.data, self.projection ), self.poly.xy) self.selection_changed.emit() def releaseContour(self , event): if event.button != 1: return self._ind = None def motionContour(self , event): if self._ind is None: return if event.inaxes is None: return if event.button != 1: return x,y = event.xdata, event.ydata self.poly.xy[self._ind] = x,y self.line.set_data(zip(*self.poly.xy)) self.redraw() self.actualSelection = points_inside_poly(np.dot( self.data, self.projection ), self.poly.xy) self.selection_changed.emit() def redrawSelection(self): if self.selectionLine is not None: if self.selectionLine in self.ax.lines: self.ax.lines.remove(self.selectionLine) if np.sum(self.actualSelection)>1: # for big selection only subset are shown sel = np.zeros(self.data.shape[0], dtype = bool) for c in self.all_labels: ind = self.subset[c] sel[ind] = True sel = sel & self.actualSelection proj = np.dot( self.data[sel, :], self.projection ) else: # for small selection also hideen spike are shown proj = np.dot( self.data[self.actualSelection, :], self.projection ) self.selectionLine, = self.ax.plot(proj[:,0] , proj[:,1], linestyle = 'None', markersize = 10, marker = 'o' , markerfacecolor='m', markeredgecolor='k', alpha = .6, ) self.redraw()
class MaskCreator(object): """An interactive polygon editor. Parameters ---------- poly_xy : list of (float, float) List of (x, y) coordinates used as vertices of the polygon. max_ds : float Max pixel distance to count as a vertex hit. 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 max_ds of the line connecting two existing vertices """ def __init__(self, ax, poly_xy=None, max_ds=10): self.showverts = True self.max_ds = max_ds if poly_xy is None: poly_xy = default_vertices(ax) self.poly = Polygon(poly_xy, animated=True, fc='y', ec='none', alpha=0.4) ax.add_patch(self.poly) ax.set_clip_on(False) ax.set_title("Click and drag a point to move it; " "'i' to insert; 'd' to delete.\n" "Close figure when done.") self.ax = ax x, y = zip(*self.poly.xy) self.line = plt.Line2D(x, y, color='none', marker='o', mfc='r', alpha=0.2, animated=True) self._update_line() self.ax.add_line(self.line) self.poly.add_callback(self.poly_changed) self._ind = None # the active vert canvas = self.poly.figure.canvas canvas.mpl_connect('draw_event', self.draw_callback) canvas.mpl_connect('button_press_event', self.button_press_callback) canvas.mpl_connect('button_release_event', self.button_release_callback) canvas.mpl_connect('key_press_event', self.key_press_callback) canvas.mpl_connect('motion_notify_event', self.motion_notify_callback) self.canvas = canvas def get_mask(self, shape): """Return image mask given by mask creator""" h, w = shape y, x = np.mgrid[:h, :w] points = np.transpose((x.ravel(), y.ravel())) #mask = nxutils.points_inside_poly(points, self.verts) #mask = Path(points).contains_points(self.verts) path = Path(self.verts) mask = path.contains_points(points) return mask.reshape(h, w) def poly_changed(self, poly): 'this method is called whenever the polygon object is called' # only copy the artist props to the line (except visibility) vis = self.line.get_visible() #Artist.update_from(self.line, poly) self.line.set_visible(vis) # don't use the poly visibility state 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 button_press_callback(self, event): 'whenever a mouse button is pressed' ignore = not self.showverts or event.inaxes is None or event.button != 1 if ignore: return self._ind = self.get_ind_under_cursor(event) def button_release_callback(self, event): 'whenever a mouse button is released' ignore = not self.showverts or event.button != 1 if ignore: return self._ind = None def key_press_callback(self, event): 'whenever a key is pressed' if not event.inaxes: return if event.key == 't': self.showverts = not self.showverts self.line.set_visible(self.showverts) if not self.showverts: self._ind = None elif event.key == 'd': ind = self.get_ind_under_cursor(event) if ind is None: return if ind == 0 or ind == self.last_vert_ind: print("Cannot delete root node") return self.poly.xy = [ tup for i, tup in enumerate(self.poly.xy) if i != ind ] self._update_line() elif event.key == 'i': xys = self.poly.get_transform().transform(self.poly.xy) p = event.x, event.y # cursor coords for i in range(len(xys) - 1): s0 = xys[i] s1 = xys[i + 1] d = my_dist_point_to_segment(p, s0, s1) if d <= self.max_ds: self.poly.xy = np.array( list(self.poly.xy[:i + 1]) + [(event.xdata, event.ydata)] + list(self.poly.xy[i + 1:])) self._update_line() break self.canvas.draw() def motion_notify_callback(self, event): 'on mouse movement' ignore = (not self.showverts or event.inaxes is None or event.button != 1 or self._ind is None) if ignore: return x, y = event.xdata, event.ydata if self._ind == 0 or self._ind == self.last_vert_ind: self.poly.xy[0] = x, y self.poly.xy[self.last_vert_ind] = x, y else: self.poly.xy[self._ind] = x, y self._update_line() 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 _update_line(self): # save verts because polygon gets deleted when figure is closed self.verts = self.poly.xy self.last_vert_ind = len(self.poly.xy) - 1 self.line.set_data(zip(*self.poly.xy)) def get_ind_under_cursor(self, event): 'get the index of the vertex under cursor if within max_ds 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.max_ds: ind = None return ind
class NDViewer(QWidget): """ This is a widget for visulazation of hight dimentional space. This is similar to GGobi http://www.ggobi.org/publications/. """ selection_changed = pyqtSignal() def __init__( self, parent=None, settings=None, show_tour=True, show_select_tools=False, ): super(NDViewer, self).__init__(parent) self.settings = settings self.show_tour = show_tour self.show_select_tools = show_select_tools self.plot_parameters = ParamWidget(PlotParameters).to_dict() mainLayout = QVBoxLayout() self.setLayout(mainLayout) h = QHBoxLayout() mainLayout.addLayout(h) self.widgetProjection = QWidget() v = QVBoxLayout() h.addLayout(v) self.scrollArea = QScrollArea() self.scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.scrollArea.setWidget(self.widgetProjection) self.scrollArea.setMinimumWidth(180) v.addWidget(self.scrollArea) if show_tour: self.randButton = QPushButton(QIcon(':/roll.png'), 'Random') v.addWidget(self.randButton) self.randButton.clicked.connect(self.randomPosition) self.startRandTourButton = QPushButton( QIcon(':/helicoper_and_roll.png'), 'Random tour', checkable=True) v.addWidget(self.startRandTourButton) self.startRandTourButton.clicked.connect(self.startStopTour) self.timerRandTour = QTimer( interval=self.plot_parameters['refresh_interval']) self.timerRandTour.timeout.connect(self.stepRandTour) self.startOptimizedTourButton = QPushButton( QIcon(':/helicoper_and_magic.png'), 'Optimized tour', checkable=True) v.addWidget(self.startOptimizedTourButton) self.startOptimizedTourButton.clicked.connect(self.startStopTour) self.connect(self.startOptimizedTourButton, SIGNAL('clicked()'), self.startStopTour) self.timerOptimizedTour = QTimer( interval=self.plot_parameters['refresh_interval']) self.timerOptimizedTour.timeout.connect(self.stepOptimizedTour) but = QPushButton(QIcon(':/configure.png'), 'Configure') v.addWidget(but) but.clicked.connect(self.openConfigure) if show_select_tools: h2 = QHBoxLayout() groupbox = QGroupBox('Selection mode') groupbox.setLayout(h2) v.addWidget(groupbox) icons = [ ['pickone', ':/color-picker.png'], ['lasso', ':/lasso.png'], ['contour', ':/polygon-editor.png'], ] self.selectButton = {} for name, icon in icons: but = QPushButton(QIcon(icon), '', checkable=True, autoExclusive=True) h2.addWidget(but) but.clicked.connect(self.changeSelectMode) self.selectButton[name] = but self.clearSelectBut = QPushButton(QIcon(':/view-refresh.png'), 'Clear selection') v.addWidget(self.clearSelectBut) self.clearSelectBut.clicked.connect(self.clearSelection) self.canvas = SimpleCanvas() self.ax = self.canvas.fig.add_axes([0.02, 0.02, .96, .96]) h.addWidget(self.canvas, 2) self.canvas.setMinimumWidth(180) self.ax_circle = None self.create_axe_circle() self.tour_running = False self.dim = 0 # self.spins = [] # spin widget list self.toBeDisconnected = [] # manage mpl_connect and disconnect self.selectMode = None # actual mode self.epsilon = 4. # for pickle event self.poly = None # for contour self.selectionLine = None self.selection_changed.connect(self.redrawSelection) ## draw and redraw ## def change_dim(self, ndim): self.projection = np.zeros((ndim, 2)) self.projection[0, 0] = 1 self.projection[1, 1] = 1 #spinwidgets self.widgetProjection = QWidget() self.widgetProjection.updateGeometry() g = QGridLayout() self.widgetProjection.setLayout(g) self.spins = [] for i in range(ndim): d1 = QDoubleSpinBox(value=self.projection[i, 0]) d2 = QDoubleSpinBox(value=self.projection[i, 1]) g.addWidget(QLabel('dim {}'.format(i)), i, 0) g.addWidget(d1, i, 1) g.addWidget(d2, i, 2) for d in [d1, d2]: d.valueChanged.connect(self.spinsChanged) d.setSingleStep(0.05) d.setRange(-1., 1.) self.spins.append([d1, d2]) self.scrollArea.setWidget(self.widgetProjection) self.scrollArea.update() self.dim = ndim def change_point(self, data, data_labels=None, colors=None, subset=None): """ data = dim 0 elements dim 1 dimension data_labels = vector of cluster for colors """ if data.shape[1] != self.dim: self.change_dim(data.shape[1]) self.data = data self.actualSelection = np.zeros(data.shape[0], dtype=bool) if data_labels is None: data_labels = np.zeros(data.shape[0], dtype='i') self.data_labels = data_labels self.all_labels = np.unique(self.data_labels) if colors is None: colors = ['c', 'g', 'r', 'b', 'k', 'm', 'y'] * 100 self.colors = colors if subset is None: subset = {} for c in self.all_labels: ind = self.data_labels == c subset[c] = ind self.subset = subset self.fullRedraw() self.refreshSpins() def fullRedraw(self): self.ax.clear() for c in self.all_labels: ind = self.subset[c] proj = np.dot(self.data[ind, :], self.projection) self.ax.plot( proj[:, 0], proj[:, 1], #proj[ind,0] , proj[ind,1], linestyle='None', marker='.', color=self.colors[c], picker=self.epsilon) self.redraw() def redraw(self): if not (self.plot_parameters['autoscale']): self.ax.set_xlim(self.plot_parameters['xlim']) self.ax.set_ylim(self.plot_parameters['ylim']) self.canvas.draw() def spinsChanged(self, value): for i in range(self.projection.shape[0]): self.projection[i, 0] = self.spins[i][0].value() self.projection[i, 1] = self.spins[i][1].value() if self.plot_parameters['force_orthonormality']: m = np.sqrt(np.sum(self.projection**2, axis=0)) m = m[np.newaxis, :] self.projection /= m self.refreshSpins() self.fullRedraw() def refreshSpins(self): for i in range(self.projection.shape[0]): d1, d2 = self.spins[i] for d in [d1, d2]: d.valueChanged.disconnect(self.spinsChanged) d1.setValue(self.projection[i, 0]) d2.setValue(self.projection[i, 1]) for d in [d1, d2]: d.valueChanged.connect(self.spinsChanged) if self.plot_parameters['display_circle']: self.refreshCircleRadius() def refreshCircleRadius(self): for l in self.radiusLines: self.ax_circle.lines.remove(l) self.radiusLines = [] for i in range(self.projection.shape[0]): l, = self.ax_circle.plot([0, self.projection[i, 0]], [0, self.projection[i, 1]], color='g') self.radiusLines.append(l) self.canvas.draw() def create_axe_circle(self): if self.plot_parameters['display_circle']: if self.ax_circle is None: ax = self.canvas.fig.add_axes([0.04, 0.04, .1, .1]) else: ax = self.ax_circle ax.clear() ax.set_xticks([]) ax.set_yticks([]) circle = Circle((0, 0), radius=1., facecolor='w') ax.add_patch(circle) ax.set_xlim([-1.02, 1.02]) ax.set_ylim([-1.02, 1.02]) self.ax_circle = ax self.canvas.draw() self.radiusLines = [] else: if self.ax_circle is not None: self.canvas.fig.delaxes(self.ax_circle) self.ax_circle = None ## config ## def openConfigure(self): dia = ParamDialog( PlotParameters, settings=self.settings, settingskey='ndviewer/options', title='Plot parameters', ) dia.update(self.plot_parameters) if dia.exec_(): self.plot_parameters = dia.to_dict() self.timerRandTour.setInterval( self.plot_parameters['refresh_interval']) self.timerOptimizedTour.setInterval( self.plot_parameters['refresh_interval']) self.create_axe_circle() self.fullRedraw() ## random and tour tour ## def randomPosition(self): ndim = self.projection.shape[0] self.projection = np.random.rand(ndim, 2) * 2 - 1. if self.plot_parameters['force_orthonormality']: m = np.sqrt(np.sum(self.projection**2, axis=0)) self.projection /= m self.refreshSpins() self.fullRedraw() def startStopTour(self): if self.sender() == self.startRandTourButton: but = self.startRandTourButton mode = 'rand' self.startOptimizedTourButton.setChecked(False) elif self.sender() == self.startOptimizedTourButton: but = self.startOptimizedTourButton mode = 'optimized' self.startRandTourButton.setChecked(False) start = but.isChecked() if start: if self.show_select_tools: for name, but in list(self.selectButton.items()): but.setChecked(False) but.setEnabled(False) self.clearSelectBut.setEnabled(False) self.selectMode = None self.clearSelection() if mode == 'rand': self.timerOptimizedTour.stop() self.timerRandTour.start() self.actualStep = self.plot_parameters['nsteps'] + 1 elif mode == 'optimized': self.timerRandTour.stop() self.timerOptimizedTour.start() self.tour_running = True else: if self.show_select_tools: for name, but in list(self.selectButton.items()): but.setEnabled(True) self.clearSelectBut.setEnabled(True) self.changeSelectMode() self.timerRandTour.stop() self.timerOptimizedTour.stop() self.tour_running = False self.refreshSpins() self.fullRedraw() def stepRandTour(self): nsteps = self.plot_parameters['nsteps'] ndim = self.projection.shape[0] if self.actualStep >= nsteps: # random for next etap nextEtap = np.random.rand(ndim, 2) * 2 - 1. self.allSteps = np.empty((ndim, 2, nsteps)) for i in range(ndim): for j in range(2): self.allSteps[i, j, :] = np.linspace(self.projection[i, j], nextEtap[i, j], nsteps) if self.plot_parameters['force_orthonormality']: m = np.sqrt(np.sum(self.allSteps**2, axis=0)) m = m[np.newaxis, :, :] self.allSteps /= m self.actualStep = 0 self.projection = self.allSteps[:, :, self.actualStep] self.actualStep += 1 self.refreshSpins() self.fullRedraw() def stepOptimizedTour(self): actual_lda = ComputeIndexLda(self.projection, self.data, self.data_labels) nloop = 1 ndim = self.projection.shape[0] for i in range(nloop): delta = (np.random.rand(ndim, 2) * 2 - 1) / 20. new_proj = self.projection + delta # normalize orthonormality m = np.sqrt(np.sum(new_proj**2, axis=0)) m = m[np.newaxis, :] new_proj /= m new_lda = ComputeIndexLda(new_proj, self.data, self.data_labels) if new_lda >= actual_lda: actual_lda = new_lda self.projection = new_proj self.refreshSpins() self.fullRedraw() ## selections ## def changeSelection(self, new_selection, emit_signal=False): self.actualSelection = new_selection if emit_signal: self.selection_changed.emit() if not self.tour_running: self.redrawSelection() def changeSelectMode(self): self.selectMode = None for name, but in self.selectButton.items(): if but.isChecked(): self.selectMode = name for e in self.toBeDisconnected: self.canvas.mpl_disconnect(e) self.toBeDisconnected = [] self.clearSelection() def clearSelection(self): self.clearArtistSelection() if self.selectMode == 'pickone': cid = self.canvas.mpl_connect('pick_event', self.onPick) self.toBeDisconnected.append(cid) elif self.selectMode == 'contour': cid1 = self.canvas.mpl_connect('button_press_event', self.pressContour) cid2 = self.canvas.mpl_connect('button_release_event', self.releaseContour) cid3 = self.canvas.mpl_connect('motion_notify_event', self.motionContour) self.toBeDisconnected += [cid1, cid2, cid3] self.poly = None self._ind = None elif self.selectMode == 'lasso': cid = self.canvas.mpl_connect('button_press_event', self.startLasso) self.toBeDisconnected.append(cid) self.actualSelection[:] = False self.selection_changed.emit() def clearArtistSelection(self): if self.poly is not None: self.ax.lines.remove(self.line) self.ax.patches.remove(self.poly) self.poly = None self.line = None self.redraw() # should not: if hasattr(self, 'lasso'): self.canvas.widgetlock.release(self.lasso) del self.lasso def onPick(self, event): if isinstance(event.artist, Line2D): xdata, ydata = event.artist.get_data() x, y = xdata[event.ind[0]], ydata[event.ind[0]] self.actualSelection[:] = False self.actualSelection[np.argmin( np.sum((np.dot(self.data, self.projection) - np.array([[x, y]]))**2, axis=1))] = True else: self.actualSelection[:] = False self.selection_changed.emit() def startLasso(self, event): if event.button != 1: return if self.canvas.widgetlock.locked(): # sometimes there is a bug lassostop is not intercepted!!! # so to avoid 2 start self.clearArtistSelection() return if event.inaxes is None: return self.lasso = Lasso(event.inaxes, (event.xdata, event.ydata), self.stopLasso) # acquire a lock on the widget drawing self.canvas.widgetlock(self.lasso) def stopLasso(self, verts): self.actualSelection = inside_poly(np.dot(self.data, self.projection), verts) self.canvas.widgetlock.release(self.lasso) del self.lasso self.selection_changed.emit() def pressContour(self, event): if event.inaxes == None: return if event.button != 1: return # new contour if self.poly is None: self.poly = Polygon([[event.xdata, event.ydata]], animated=False, alpha=.3, color='g') self.ax.add_patch(self.poly) self.line, = self.ax.plot([event.xdata], [event.ydata], color='g', linewidth=2, marker='o', markerfacecolor='g', animated=False) self.redraw() return # event near a point 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] self._ind = indseq[0] if d[self._ind] >= self.epsilon: self._ind = None # new point if self._ind is None: self.poly.xy = np.array( list(self.poly.xy) + [[event.xdata, event.ydata]]) self.line.set_xdata( np.array(list(self.line.get_xdata()) + [event.xdata])) self.line.set_ydata( np.array(list(self.line.get_ydata()) + [event.ydata])) self.redraw() self.actualSelection = inside_poly(np.dot(self.data, self.projection), self.poly.xy) self.selection_changed.emit() def releaseContour(self, event): if event.button != 1: return self._ind = None def motionContour(self, event): if self._ind is None: return if event.inaxes is None: return if event.button != 1: return x, y = event.xdata, event.ydata self.poly.xy[self._ind] = x, y self.line.set_data(list(zip(*self.poly.xy))) self.redraw() self.actualSelection = inside_poly(np.dot(self.data, self.projection), self.poly.xy) self.selection_changed.emit() def redrawSelection(self): if self.selectionLine is not None: if self.selectionLine in self.ax.lines: self.ax.lines.remove(self.selectionLine) if np.sum(self.actualSelection) > 1: # for big selection only subset are shown sel = np.zeros(self.data.shape[0], dtype=bool) for c in self.all_labels: ind = self.subset[c] sel[ind] = True sel = sel & self.actualSelection proj = np.dot(self.data[sel, :], self.projection) else: # for small selection also hideen spike are shown proj = np.dot(self.data[self.actualSelection, :], self.projection) self.selectionLine, = self.ax.plot( proj[:, 0], proj[:, 1], linestyle='None', markersize=10, marker='o', markerfacecolor='m', markeredgecolor='k', alpha=.6, ) self.redraw()
class PolyClick(object): """ p = PolyClick() # Start interactive polygon generator, or p = PolyClick(verts, ax=gca()) # Initialize polygon based on verts, or p = PolyClick(x, y, ax=gca()) # Initialize polygon based on x and y, or p = PolyClick('verts.pickle', ax=gca()) # Load in verts from pickle file If verts or (x, y) are not given, an interactive polygon creation session is started. Verticies are orange. Switch to editing mode by hitting return. This changes the vertecies to black. The points may be moved with the mouse, or modified with the key commands listed below. Current data are always available in bry.x, bry.y and bry.verts. Key commands: enter : switch to grid editing mode t : toggle visibility of verticies d : delete a vertex i : insert a vertex at a point on the polygon line Methods: p.dump(bry_file='bry.pickle) Write the current boundary informtion (bry.verts) to a cPickle file bry_file. p.load(bry_file='bry.pickle) Read in boundary informtion (verts) from the cPickle file bry_file. Attributes: p.x, p.y : The x and y locations of the polygon verticies. p.verts : The verticies of the polygon (equiv. to zip(x, y)) p.area : the area of the polygon. Positive for a counterclockwise path, negative for a clockwise path. p.centroid : the (x, y) location of the polygon centroid. """ _showverts = True _epsilon = 5 # max pixel distance to count as a vertex hit def _init_boundary_interactor(self): """Send polyclick thread to boundary interactor""" # Get rid old mpl connections. self._ax.figure.canvas.mpl_disconnect(self._key_id) self._ax.figure.canvas.mpl_disconnect(self._click_id) # Shade the selected region in a polygon self._poly = Polygon(self.verts, alpha=0.2, fc='k', animated=True) self._ax.add_patch(self._poly) # modify the line properties self._line.set_markerfacecolor('k') # get the canvas and connect the callback events cid = self._poly.add_callback(self._poly_changed) self._ind = None # the active vert self._canvas.mpl_connect('draw_event', self._draw_callback) self._canvas.mpl_connect('button_press_event', self._button_press_callback) self._canvas.mpl_connect('key_press_event', self._key_press_callback) self._canvas.mpl_connect('button_release_event', self._button_release_callback) self._canvas.mpl_connect('motion_notify_event', self._motion_notify_callback) 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 _poly_changed(self, poly): 'this method is called whenever the polygon object is called' # only copy the artist props to the line (except visibility) vis = self._line.get_visible() Artist.update_from(self._line, poly) self._line.set_visible(vis) # don't use the poly visibility state def _get_ind_under_point(self, event): 'get the index of the vertex under point if within epsilon tolerance' try: x, y = zip(*self._poly.xy) # display coords xt, yt = self._poly.get_transform().numerix_x_y(x, y) d = sqrt((xt - event.x)**2 + (yt - event.y)**2) indseq = nonzero(equal(d, amin(d))) ind = indseq[0] if d[ind] >= self._epsilon: ind = None return ind except: # display coords xy = asarray(self._poly.xy) xyt = self._poly.get_transform().transform(xy) xt, yt = xyt[:, 0], xyt[:, 1] d = sqrt((xt - event.x)**2 + (yt - event.y)**2) indseq = nonzero(equal(d, 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' # if not self._showverts: return if event.inaxes == None: return if event.button != 1: return self._ind = self._get_ind_under_point(event) def _button_release_callback(self, event): 'whenever a mouse button is released' # if not self._showverts: return if event.button != 1: return self._ind = None def _key_press_callback(self, event): 'whenever a key is pressed' if not event.inaxes: return if event.key == 't': self._showverts = not self._showverts elif event.key == 'd': ind = self._get_ind_under_point(event) if ind is not None: self._poly.xy = [ tup for i, tup in enumerate(self._poly.xy) if i != ind ] self._line.set_data(zip(*self._poly.xy)) elif event.key == 'i': xys = self._poly.get_transform().seq_xy_tups(self._poly.xy) p = event.x, event.y # display coords for i in range(len(xys) - 1): s0 = xys[i] s1 = xys[i + 1] d = dist_point_to_segment(p, s0, s1) if d <= self._epsilon: self._poly.xy.insert(i + 1, (event.xdata, event.ydata)) self._line.set_data(zip(*self._poly.xy)) break s0 = xys[-1] s1 = xys[0] d = dist_point_to_segment(p, s0, s1) if d <= self._epsilon: self._poly.xy.append((event.xdata, event.ydata)) self._line.set_data(zip(*self._poly.xy)) self._draw_callback(event) self._canvas.draw() def _motion_notify_callback(self, event): 'on mouse movement' if self._ind is None: return if event.inaxes is None: return if event.button != 1: return x, y = event.xdata, event.ydata self._poly.xy[self._ind] = x, y x, y = zip(*self._poly.xy) self._line.set_data(x, y) 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_return(self, event): if event.key in ('enter', None): self._init_boundary_interactor() def _on_click(self, event): self.x.append(event.xdata) self.y.append(event.ydata) self._line.set_data(self.x, self.y) self._background = self._canvas.copy_from_bbox(self._ax.bbox) self._ax.draw_artist(self._line) self._canvas.blit(self._ax.bbox) def __init__(self, *args, **kwargs): ax = kwargs.pop('ax', None) if ax is None: ax = pl.gca() self._ax = ax assert len( args ) <= 2, 'Input is either verticies (1 arg), or x and y (2 args)' if args is not (): if isinstance(args[0], str): verts = load(x) x, y = zip(*verts) x = list(x) y = list(y) elif len(args) == 1: # assume verticies input verts = args[0] x, y = zip(*verts) x = list(x) y = list(y) else: # x and y inputs x = list(args[0]) y = list(args[1]) assert len(x) == len(y), 'x and y must have the same length.' else: # no input x = [] y = [] self._line = pl.Line2D(x, y, marker='o', markerfacecolor='orange', animated=True) self._ax.add_line(self._line) self._canvas = self._line.figure.canvas self._key_id = self._ax.figure.canvas.mpl_connect( 'key_press_event', self._on_return) self._click_id = self._ax.figure.canvas.mpl_connect( 'button_press_event', self._on_click) if len(x) > 0: self._init_boundary_interactor() def dump(self, poly_file): f = open(bry_file, 'wb') cPickle.dump(self.verts, f, protocol=-1) f.close() def load(self, poly_file): verts = load(bry_file) x, y = zip(*verts) self._line.set_data(x, y) if hasattr(self, '_poly'): self._poly.xy = verts self._draw_callback(None) self._canvas.draw() def inside(self, *args): """docstring for inside""" assert len(args) > 0, 'must provide either verticies or x and y.' if len(args) == 1: # assume verticies input verts = args[0] else: # x and y inputs x = list(args[0]) y = list(args[1]) assert len(x) == len(y), 'x and y must have the same length.' verts = zip(x, y) return self._geom.inside(verts) def get_verts(self): return zip(self.x, self.y) verts = property(get_verts) def get_xdata(self): return self._line.get_xdata() x = property(get_xdata) def get_ydata(self): return self._line.get_ydata() y = property(get_ydata) def _get_geom(self): return Polygeom(self.verts) _geom = property(_get_geom) def _get_area(self): return self._geom.area area = property(_get_area) def _get_centroid(self): return self._geom.centroid centroid = property(_get_centroid)
class PolygonInteractor(QObject): """ 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 """ showverts = True epsilon = 5 # max pixel distance to count as a vertex hit mySignal = pyqtSignal(str) modSignal = pyqtSignal(str) def __init__(self, ax, verts): super().__init__() from matplotlib.patches import Polygon from matplotlib.lines import Line2D # from matplotlib.artist import Artist self.ax = ax self.type = 'Polygon' self.poly = Polygon(list(verts), animated=True, fill=False, closed=True, color='lime') self.ax.add_patch(self.poly) self.canvas = self.poly.figure.canvas x, y = zip(*self.poly.xy) self.line = Line2D(x, y, marker='o', linestyle=None, linewidth=0., markerfacecolor='g', animated=True) self.ax.add_line(self.line) self.cid = self.poly.add_callback(self.poly_changed) self._ind = None # the active vert self.connect() self.aperture = self.poly def connect(self): self.cid_draw = self.canvas.mpl_connect('draw_event', self.draw_callback) self.cid_press = self.canvas.mpl_connect('button_press_event', self.button_press_callback) self.cid_key = self.canvas.mpl_connect('key_press_event', self.key_press_callback) self.cid_release = self.canvas.mpl_connect('button_release_event', self.button_release_callback) self.cid_motion = self.canvas.mpl_connect('motion_notify_event', self.motion_notify_callback) self.canvas.draw_idle() def disconnect(self): self.canvas.mpl_disconnect(self.cid_draw) self.canvas.mpl_disconnect(self.cid_press) self.canvas.mpl_disconnect(self.cid_key) self.canvas.mpl_disconnect(self.cid_release) self.canvas.mpl_disconnect(self.cid_motion) self.poly.remove() self.line.remove() self.canvas.draw_idle() self.aperture = None 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) #self.canvas.udpate() #self.canvas.flush_events() def poly_changed(self, poly): 'this method is called whenever the polygon object is called' # only copy the artist props to the line (except visibility) vis = self.line.get_visible() Artist.update_from(self.line, poly) self.line.set_visible(vis) # don't use the poly visibility state 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.hypot(xt - event.x, yt - event.y) indseq, = np.nonzero(d == d.min()) ind = indseq[0] if d[ind] >= self.epsilon: ind = None return ind def button_press_callback(self, event): 'whenever a mouse button is pressed' if not self.showverts: return if event.inaxes is None: return if event.button != 1: return self._ind = self.get_ind_under_point(event) def button_release_callback(self, event): 'whenever a mouse button is released' if not self.showverts: return if event.button != 1: return self._ind = None def key_press_callback(self, event): 'whenever a key is pressed' if not event.inaxes: return if event.key == 't': self.showverts = not self.showverts self.line.set_visible(self.showverts) if not self.showverts: self._ind = None elif event.key == 'd': ind = self.get_ind_under_point(event) if ind is not None: if len(self.poly.xy) < 5: # the minimum polygon has 4 points since the 1st is repeated as final # Delete polygon #self.disconnect() #self.poly = None #self.line = None self.mySignal.emit('polygon deleted') else: self.poly.xy = [tup for i, tup in enumerate(self.poly.xy) if i != ind] self.line.set_data(zip(*self.poly.xy)) self.mySignal.emit('one vertex of polygon removed') elif event.key == 'i': xys = self.poly.get_transform().transform(self.poly.xy) p = event.x, event.y # display coords for i in range(len(xys) - 1): s0 = xys[i] s1 = xys[i + 1] d = dist_point_to_segment(p, s0, s1) if d <= self.epsilon: self.poly.xy = np.array( list(self.poly.xy[:i+1]) + [(event.xdata, event.ydata)] + list(self.poly.xy[i+1:])) self.line.set_data(zip(*self.poly.xy)) break self.canvas.draw_idle() def motion_notify_callback(self, event): 'on mouse movement' if not self.showverts: return if self._ind is None: return if event.inaxes is None: return if event.button != 1: return x, y = event.xdata, event.ydata self.poly.xy[self._ind] = x, y if self._ind == 0: self.poly.xy[-1] = x, y elif self._ind == len(self.poly.xy) - 1: self.poly.xy[0] = x, y self.updateMarkers() self.canvas.restore_region(self.background) self.ax.draw_artist(self.poly) self.ax.draw_artist(self.line) self.canvas.update() self.canvas.flush_events() # Notify callback self.modSignal.emit('polygon modified') def updateMarkers(self): self.line.set_data(zip(*self.poly.xy))
class MaskCreator(object): """An interactive polygon editor. Parameters ---------- poly_xy : list of (float, float) List of (x, y) coordinates used as vertices of the polygon. max_ds : float Max pixel distance to count as a vertex hit. 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 max_ds of the line connecting two existing vertices """ def __init__(self, ax, poly_xy=None, max_ds=10): self.showverts = True self.max_ds = max_ds if poly_xy is None: poly_xy = default_vertices(ax) self.poly = Polygon(poly_xy, animated=True, fc='y', ec='none', alpha=0.4) ax.add_patch(self.poly) ax.set_clip_on(False) ax.set_title("Click and drag a point to move it; " "'i' to insert; 'd' to delete.\n" "Close figure when done.") self.ax = ax x, y = zip(*self.poly.xy) self.line = plt.Line2D(x, y, color='none', marker='o', mfc='r', alpha=0.2, animated=True) self._update_line() self.ax.add_line(self.line) self.poly.add_callback(self.poly_changed) self._ind = None # the active vert canvas = self.poly.figure.canvas canvas.mpl_connect('draw_event', self.draw_callback) canvas.mpl_connect('button_press_event', self.button_press_callback) canvas.mpl_connect('button_release_event', self.button_release_callback) canvas.mpl_connect('key_press_event', self.key_press_callback) canvas.mpl_connect('motion_notify_event', self.motion_notify_callback) self.canvas = canvas def get_mask(self, shape): """Return image mask given by mask creator""" h, w = shape y, x = np.mgrid[:h, :w] points = np.transpose((x.ravel(), y.ravel())) mask = path.Path.contains_point(points) return mask.reshape(h, w) def poly_changed(self, poly): 'this method is called whenever the polygon object is called' # only copy the artist props to the line (except visibility) vis = self.line.get_visible() #Artist.update_from(self.line, poly) self.line.set_visible(vis) # don't use the poly visibility state 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 button_press_callback(self, event): 'whenever a mouse button is pressed' ignore = not self.showverts or event.inaxes is None or event.button != 1 if ignore: return self._ind = self.get_ind_under_cursor(event) def button_release_callback(self, event): 'whenever a mouse button is released' ignore = not self.showverts or event.button != 1 if ignore: return self._ind = None def key_press_callback(self, event): 'whenever a key is pressed' if not event.inaxes: return if event.key=='t': self.showverts = not self.showverts self.line.set_visible(self.showverts) if not self.showverts: self._ind = None elif event.key=='d': ind = self.get_ind_under_cursor(event) if ind is None: return if ind == 0 or ind == self.last_vert_ind: print "Cannot delete root node" return self.poly.xy = [tup for i,tup in enumerate(self.poly.xy) if i!=ind] self._update_line() elif event.key=='i': xys = self.poly.get_transform().transform(self.poly.xy) p = event.x, event.y # cursor coords for i in range(len(xys)-1): s0 = xys[i] s1 = xys[i+1] d = dist_point_to_segment(p, s0, s1) if d <= self.max_ds: self.poly.xy = np.array( list(self.poly.xy[:i+1]) + [(event.xdata, event.ydata)] + list(self.poly.xy[i+1:])) self._update_line() break self.canvas.draw() def motion_notify_callback(self, event): 'on mouse movement' ignore = (not self.showverts or event.inaxes is None or event.button != 1 or self._ind is None) if ignore: return x,y = event.xdata, event.ydata if self._ind == 0 or self._ind == self.last_vert_ind: self.poly.xy[0] = x,y self.poly.xy[self.last_vert_ind] = x,y else: self.poly.xy[self._ind] = x,y self._update_line() 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 _update_line(self): # save verts because polygon gets deleted when figure is closed self.verts = self.poly.xy self.last_vert_ind = len(self.poly.xy) - 1 self.line.set_data(zip(*self.poly.xy)) def get_ind_under_cursor(self, event): 'get the index of the vertex under cursor if within max_ds 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.max_ds: ind = None return ind
class PolygonInteractor(QtCore.QObject): """ Polygon Interactor Parameters ---------- axtmp : matplotlib axis matplotlib axis pntxy : """ showverts = True epsilon = 5 # max pixel distance to count as a vertex hit polyi_changed = QtCore.pyqtSignal(list) def __init__(self, axtmp, pntxy): QtCore.QObject.__init__(self) self.ax = axtmp self.poly = Polygon([(1, 1)], animated=True) self.ax.add_patch(self.poly) self.canvas = self.poly.figure.canvas self.poly.set_alpha(0.5) self.pntxy = pntxy self.ishist = True self.background = self.canvas.copy_from_bbox(self.ax.bbox) xtmp, ytmp = list(zip(*self.poly.xy)) self.line = Line2D(xtmp, ytmp, marker='o', markerfacecolor='r', color='y', animated=True) self.ax.add_line(self.line) self.poly.add_callback(self.poly_changed) self._ind = None # the active vert 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) def draw_callback(self): """ Draw callback """ self.background = self.canvas.copy_from_bbox(self.ax.bbox) QtWidgets.QApplication.processEvents() self.canvas.restore_region(self.background) self.ax.draw_artist(self.poly) self.ax.draw_artist(self.line) self.canvas.update() def new_poly(self, npoly): """ New Polygon """ self.poly.set_xy(npoly) self.line.set_data(list(zip(*self.poly.xy))) self.canvas.draw() self.update_plots() def poly_changed(self, poly): """ Changed Polygon """ # this method is called whenever the polygon object is called # only copy the artist props to the line (except visibility) vis = self.line.get_visible() Artist.update_from(self.line, poly) self.line.set_visible(vis) # don't use the poly visibility state def get_ind_under_point(self, event): """get the index of vertex under point if within epsilon tolerance""" # display coords xytmp = np.asarray(self.poly.xy) xyt = self.poly.get_transform().transform(xytmp) xtt, ytt = xyt[:, 0], xyt[:, 1] dtt = np.sqrt((xtt - event.x) ** 2 + (ytt - event.y) ** 2) indseq = np.nonzero(np.equal(dtt, np.amin(dtt)))[0] ind = indseq[0] if dtt[ind] >= self.epsilon: ind = None return ind def button_press_callback(self, event): """whenever a mouse button is pressed""" if event.inaxes is None: return if event.button != 1: return self._ind = self.get_ind_under_point(event) if self._ind is None: xys = self.poly.get_transform().transform(self.poly.xy) ptmp = self.poly.get_transform().transform([event.xdata, event.ydata]) # ptmp = event.x, event.y # display coords if len(xys) == 1: self.poly.xy = np.array( [(event.xdata, event.ydata)] + [(event.xdata, event.ydata)]) self.line.set_data(list(zip(*self.poly.xy))) self.canvas.restore_region(self.background) self.ax.draw_artist(self.poly) self.ax.draw_artist(self.line) self.canvas.update() return dmin = -1 imin = -1 for i in range(len(xys) - 1): s0tmp = xys[i] s1tmp = xys[i + 1] dtmp = dist_point_to_segment(ptmp, s0tmp, s1tmp) if dmin == -1: dmin = dtmp imin = i elif dtmp < dmin: dmin = dtmp imin = i i = imin # breakpoint() self.poly.xy = np.array(list(self.poly.xy[:i + 1]) + [(event.xdata, event.ydata)] + list(self.poly.xy[i + 1:])) self.line.set_data(list(zip(*self.poly.xy))) self.canvas.restore_region(self.background) self.ax.draw_artist(self.poly) self.ax.draw_artist(self.line) self.canvas.update() def button_release_callback(self, event): """Whenever a mouse button is released""" if event.button != 1: return self._ind = None self.update_plots() def update_plots(self): """ Update Plots """ polymask = Path(self.poly.xy).contains_points(self.pntxy) self.polyi_changed.emit(polymask.tolist()) def motion_notify_callback(self, event): """on mouse movement""" if self._ind is None: return if event.inaxes is None: return if event.button != 1: return xtmp, ytmp = event.xdata, event.ydata self.poly.xy[self._ind] = xtmp, ytmp if self._ind == 0: self.poly.xy[-1] = xtmp, ytmp self.line.set_data(list(zip(*self.poly.xy))) self.canvas.restore_region(self.background) self.ax.draw_artist(self.poly) self.ax.draw_artist(self.line) self.canvas.update()
class BsplineInteractor(object): """ 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 """ #showverts = True #epsilon = 5 def __init__(self, Lspline, mouse_update_listener=None, optimization_listener=None): self.Lspline = Lspline self.curve = Lspline.curve self.mouse_update_listener = [mouse_update_listener] self.optimization_listener = [optimization_listener] self.showverts = True self.epsilon = 5 self.verbose = True self.fig = plt.figure() #figsize=(4, 4) #self.fig2 = plt.figure() self.ax = self.fig.add_subplot(111) self.fig.add_axes(self.ax) self.controlpoly = Polygon(list( zip(self.curve.vertices[:, 0], self.curve.vertices[:, 1])), animated=True, fill=False, closed=False) self.curve_r = Polygon(list(zip(self.curve.r[:, 0], self.curve.r[:, 1])), animated=True, fill=False, closed=False) self.ax.add_patch(self.controlpoly) self.ax.add_patch(self.curve_r) self.line_poly = Line2D(self.curve.vertices[:, 0], self.curve.vertices[:, 1], color='blue', marker='o', markerfacecolor='r', alpha=.5, animated=True) self.line_r = Line2D(self.curve.r[:, 0], self.curve.r[:, 1], color='black', alpha=.75, markerfacecolor='r', animated=True) self.ax.add_line(self.line_poly) self.ax.add_line(self.line_r) self.cid = self.controlpoly.add_callback(self.poly_changed) self.did = self.curve_r.add_callback(self.poly_changed) self._ind = None # the active vert canvas = self.controlpoly.figure.canvas canvas.mpl_connect('draw_event', self.draw_callback) canvas.mpl_connect('button_press_event', self.button_press_callback) canvas.mpl_connect('key_press_event', self.key_press_callback) canvas.mpl_connect('button_release_event', self.button_release_callback) canvas.mpl_connect('motion_notify_event', self.motion_notify_callback) self.setconstraints = plt.axes([0.50, 0.05, 0.1, 0.075]) self.sconstraints = Button(self.setconstraints, 'SET') self.sconstraints.on_clicked(self.set_contraints) self.axconstraints = plt.axes([0.61, 0.05, 0.1, 0.075]) self.bconstraints = Button(self.axconstraints, 'Form Parm') self.bconstraints.on_clicked(self.add_constraints) self.axprev = plt.axes([0.72, 0.05, 0.1, 0.075]) self.bprev = Button(self.axprev, 'Optimize') self.bprev.on_clicked(self.do_optimization) self.canvas = canvas self.ax.set_title('Lspline Plot') xmin, xmax, ymin, ymax = self.Lspline.extreme_C0() self.ax.set_xlim((xmin - 2., xmax + 2.)) self.ax.set_ylim((ymin - 2., ymax + 2.)) return def add_constraints(self, event): if self.verbose == True: print 'adding constraints' self.FPmaker = FormParameterInteractor(self.Lspline) return def set_contraints(self, event): if self.verbose == True: print 'setting constraints' self.Lspline = self.FPmaker() return def do_optimization(self, event): if self.verbose == True: print 'doing optimization' self.Lspline.optimize() self.compute_curve_plot() if self.optimization_listener[0] is not None: for func in self.optimization_listener: func(self.Lspline.curve.vertices) return def plot_contraints(self, event): if self.verbose == True: print 'plotting constraints' self.Lspline.display(mytitle='Test 1') return def compute_curve_plot(self): self.controlpoly.xy = self.curve.vertices self.line_poly.set_data(zip(*self.curve.vertices)) self.canvas.restore_region(self.background) #self.curve.vertices = self.controlpoly.xy self.curve.allCurveArray() self.curve_r = Polygon(self.curve.r, animated=True, fill=False, closed=False) self.curve_r.yx = self.curve.r self.line_r.set_data(self.curve.r[:, 0], self.curve.r[:, 1]) self.ax.draw_artist(self.controlpoly) self.ax.draw_artist(self.line_poly) self.ax.draw_artist(self.curve_r) self.ax.draw_artist(self.line_r) self.canvas.blit(self.ax.bbox) return def draw_callback(self, event): self.background = self.canvas.copy_from_bbox(self.ax.bbox) self.ax.draw_artist(self.controlpoly) self.ax.draw_artist(self.line_poly) self.ax.draw_artist(self.curve_r) self.ax.draw_artist(self.line_r) #self.canvas.blit(self.ax.bbox) return def poly_changed(self, controlpoly, curve_r): vis = self.line_poly.get_visible() Artist.update_from(self.line_poly, controlpoly) self.line_poly.set_visible(vis) vis = self.line_r.get_visible() Artist.update_from(self.line_r, curve_r) self.line_r.set_visible(vis) return def get_ind_under_point(self, event): """get the index of the vertex under point if within epsilon tolerance """ xy = np.asarray(self.controlpoly.xy) xyt = self.controlpoly.get_transform().transform(xy) xt, yt = xyt[:, 0], xyt[:, 1] print xt, yt print event.x, event.y 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' if self.verbose: print 'button_press_callback GUISPLINE' print not self.showverts print event.inaxes == None print event.button != 1 if not self.showverts: return if event.inaxes == None: return if event.button != 1: return self._ind = self.get_ind_under_point(event) def button_release_callback(self, event): 'whenever a mouse button is reileased' if not self.showverts: return if event.button != 1: return self._ind = None def key_press_callback(self, event): 'whenever a key is pressed' if not event.inaxes: return if event.key == 't': self.showverts = not self.showverts self.line_poly.set_visible(self.showverts) if not self.showverts: self._ind = None elif event.key == 'd': ind = self.get_ind_under_point(event) if ind is not None: self.controlpoly.xy = [ tup for i, tup in enumerate(self.controlpoly.xy) if i != ind ] self.line_poly.set_data(zip(*self.controlpoly.xy)) elif event.key == 'i': xys = self.controlpoly.get_transform().transform( self.controlpoly.xy) p = event.x, event.y # display coords for i in range(len(xys)): s0 = xys[i - 1] s1 = xys[i] d = dist_point_to_segment(p, s0, s1) if d <= self.epsilon: self.controlpoly.xy = np.array( list(self.controlpoly.xy[:i]) + [(event.xdata, event.ydata)] + list(self.controlpoly.xy[i:])) self.line_poly.set_data(zip(*self.controlpoly.xy)) break self.canvas.draw() def motion_notify_callback(self, event): 'on mouse movement' if self.verbose: print 'motion_notify_callback' if not self.showverts: return if self._ind is None: return if event.inaxes is None: return if event.button != 1: return x, y = event.xdata, event.ydata self.curve.vertices[self._ind, 0] = x self.curve.vertices[self._ind, 1] = y self.compute_curve_plot() if self.mouse_update_listener[0] is not None: for func in self.mouse_update_listener: func(x, y, self._ind) return
class MaskCreator(object): """An interactive polygon editor. Parameters ---------- poly_xy : list of (float, float) List of (x, y) coordinates used as vertices of the polygon. max_ds : float Max pixel distance to count as a vertex hit. 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 max_ds of the line connecting two existing vertices """ def __init__(self, ax, poly_xy=None, max_ds=10, line_width=2, line_color=(0, 0, 1), face_color=(1, .5, 0)): self.showverts = True self.max_ds = max_ds if poly_xy is None: poly_xy = default_vertices(ax) self.poly = Polygon(poly_xy, animated=True, fc=face_color, ec='none', alpha=0.4) ax.add_patch(self.poly) ax.set_clip_on(False) ax.set_title("Click and drag a point to move it; or click once, then click again." "\n" "Close figure when done.") self.ax = ax x, y = zip(*self.poly.xy) #line_color = 'none' color = np.array(line_color) * .6 marker_face_color = line_color line_kwargs = {'lw': line_width, 'color': color, 'mfc': marker_face_color} self.line = plt.Line2D(x, y, marker='o', alpha=0.8, animated=True, **line_kwargs) self._update_line() self.ax.add_line(self.line) self.poly.add_callback(self.poly_changed) self._ind = None # the active vert canvas = self.poly.figure.canvas canvas.mpl_connect('draw_event', self.draw_callback) canvas.mpl_connect('button_press_event', self.button_press_callback) canvas.mpl_connect('button_release_event', self.button_release_callback) #canvas.mpl_connect('key_press_event', self.key_press_callback) canvas.mpl_connect('motion_notify_event', self.motion_notify_callback) self.canvas = canvas def get_mask(self, shape): """Return image mask given by mask creator""" mask = verts_to_mask(shape, self.verts) return mask def poly_changed(self, poly): 'this method is called whenever the polygon object is called' # only copy the artist props to the line (except visibility) vis = self.line.get_visible() #Artist.update_from(self.line, poly) self.line.set_visible(vis) # don't use the poly visibility state def draw_callback(self, event): #print('[mask] draw_callback(event=%r)' % 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 button_press_callback(self, event): 'whenever a mouse button is pressed' if not self._ind is None: self._ind = None; return ignore = not self.showverts or event.inaxes is None or event.button != 1 if ignore: return self._ind = self.get_ind_under_cursor(event) if self._ind != None: self.indX, self.indY = self.poly.xy[self._ind] self.mouseX,self.mouseY = event.xdata, event.ydata def button_release_callback(self, event): 'whenever a mouse button is released' ignore = not self.showverts or event.button != 1 if ignore: return if self._ind is None: return currX, currY = self.poly.xy[self._ind] #print (currX, ' ', currY) #print (math.fabs(self.indX - currX)<3, ' and ', math.fabs(self.indY-currY)<3) if math.fabs(self.indX - currX)<3 and math.fabs(self.indY-currY)<3: return self._ind = None # def key_press_callback(self, event): # 'whenever a key is pressed' # if not event.inaxes: # return # if event.key == 't': # self.showverts = not self.showverts # self.line.set_visible(self.showverts) # if not self.showverts: # self._ind = None # elif event.key == 'd': # ind = self.get_ind_under_cursor(event) # if ind is None: # return # if ind == 0 or ind == self.last_vert_ind: # print('[mask] Cannot delete root node') # return # self.poly.xy = [tup for i, tup in enumerate(self.poly.xy) if i != ind] # self._update_line() # elif event.key == 'i': # xys = self.poly.get_transform().transform(self.poly.xy) # p = event.x, event.y # cursor coords # for i in range(len(xys) - 1): # s0 = xys[i] # s1 = xys[i + 1] # d = dist_point_to_segment(p, s0, s1) # if d <= self.max_ds: # self.poly.xy = np.array( # list(self.poly.xy[:i + 1]) + # [(event.xdata, event.ydata)] + # list(self.poly.xy[i + 1:])) # self._update_line() # break # self.canvas.draw() def motion_notify_callback(self, event): 'on mouse movement' # ignore = (not self.showverts or event.inaxes is None or # event.button != 1 or self._ind is None) ignore = (not self.showverts or event.inaxes is None) if ignore: #print ('verts ', not self.showverts, ' inaxes ', event.inaxes, ' event.buton ' ,event.button !=1, ' ind ', self._ind ) return if self._ind is None and event.button ==1: 'move all vertices' self.move_rectangle(event) self._update_line() self.canvas.restore_region(self.background) self.ax.draw_artist(self.poly) self.ax.draw_artist(self.line) self.canvas.blit(self.ax.bbox) self._ind = None 'set new mouse loc' self.mouseX,self.mouseY = event.xdata, event.ydata # if self._ind is None: # #create new poly # poly2_xy = vertices_under_cursor(event) # self.poly2 = Polygon(poly2_xy, animated=True, # fc=face_color, ec='none', alpha=0.4) # #ax.add_patch(self.poly2) # #Grab 3rd(?) ind if self._ind is None: return self.calculate_move(event) self._update_line() 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 move_rectangle(self,event): selectedX, selectedY = (self.poly.xy[1]) beforeX, beforeY = (self.poly.xy[0]) afterX, afterY = (self.poly.xy[2]) acrossX, acrossY = (self.poly.xy[3]) listX = [selectedX, beforeX, afterX, acrossX] listY = [selectedY, beforeY, afterY, acrossY] maxX = max(listX) maxY = max(listY) minX = min(listX) minY = min(listY) x, y = event.xdata, event.ydata if x < minX or x> maxX or y<minY or y>maxY: return # Change selected self.poly.xy[1] = selectedX+(x-self.mouseX), selectedY+(y-self.mouseY) # Change before vert self.poly.xy[0] = beforeX+(x-self.mouseX), beforeY+(y-self.mouseY) self.poly.xy[self.last_vert_ind] = beforeX+(x-self.mouseX), beforeY+(y-self.mouseY) # Change after vert self.poly.xy[2] = afterX+(x-self.mouseX), afterY+(y-self.mouseY) #Change across vert self.poly.xy[3] = acrossX+(x-self.mouseX), acrossY+(y-self.mouseY) def calculate_move(self,event): indBefore = self._ind-1 if(indBefore < 0): indBefore = len(self.poly.xy)-2 indAfter = (self._ind+1)%4 selectedX, selectedY = (self.poly.xy[self._ind]) beforeX, beforeY = (self.poly.xy[indBefore]) afterX, afterY = (self.poly.xy[indAfter]) changeBefore = -1 keepX, changeY = -1, -1 changeAfter = -1 changeX, keepY = -1, -1 if beforeX != selectedX: changeBefore = indBefore keepX, changeY = self.poly.xy[indBefore] changeAfter = indAfter changeX, keepY = self.poly.xy[indAfter] else: changeBefore = indAfter keepX, changeY = self.poly.xy[indAfter] changeAfter = indBefore changeX, keepY = self.poly.xy[indBefore] x, y = event.xdata, event.ydata # Change selected if self._ind == 0 or self._ind == self.last_vert_ind: self.poly.xy[0] = x, y self.poly.xy[self.last_vert_ind] = x, y else: self.poly.xy[self._ind] = x, y # Change vert if changeBefore == 0 or changeBefore == self.last_vert_ind: self.poly.xy[0] = keepX, y self.poly.xy[self.last_vert_ind] = keepX, y else: self.poly.xy[changeBefore] = keepX, y # Change horiz if changeAfter == 0 or changeAfter == self.last_vert_ind: self.poly.xy[0] = x, keepY self.poly.xy[self.last_vert_ind] = x, keepY else: self.poly.xy[changeAfter] = x, keepY def _update_line(self): # save verts because polygon gets deleted when figure is closed self.verts = self.poly.xy self.last_vert_ind = len(self.poly.xy) - 1 self.line.set_data(zip(*self.poly.xy)) def get_ind_under_cursor(self, event): 'get the index of the vertex under cursor if within max_ds 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.max_ds: ind = None return ind
class PolygonInteractor(QtCore.QObject): """ Polygon Interactor Parameters ---------- axtmp : matplotlib axis matplotlib axis pntxy : """ showverts = True epsilon = 5 # max pixel distance to count as a vertex hit polyi_changed = QtCore.pyqtSignal(list) def __init__(self, axtmp, pntxy): QtCore.QObject.__init__(self) self.ax = axtmp self.poly = Polygon([(1, 1)], animated=True) self.ax.add_patch(self.poly) self.canvas = self.poly.figure.canvas self.poly.set_alpha(0.5) self.pntxy = pntxy self.ishist = True self.background = self.canvas.copy_from_bbox(self.ax.bbox) xtmp, ytmp = list(zip(*self.poly.xy)) self.line = Line2D(xtmp, ytmp, marker='o', markerfacecolor='r', color='y', animated=True) self.ax.add_line(self.line) self.poly.add_callback(self.poly_changed) self._ind = None # the active vert 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) def draw_callback(self): """ Draw callback """ self.background = self.canvas.copy_from_bbox(self.ax.bbox) QtWidgets.QApplication.processEvents() self.canvas.restore_region(self.background) self.ax.draw_artist(self.poly) self.ax.draw_artist(self.line) self.canvas.update() def new_poly(self, npoly): """ New Polygon """ self.poly.set_xy(npoly) self.line.set_data(list(zip(*self.poly.xy))) self.canvas.draw() self.update_plots() def poly_changed(self, poly): """ Changed Polygon """ # this method is called whenever the polygon object is called # only copy the artist props to the line (except visibility) vis = self.line.get_visible() Artist.update_from(self.line, poly) self.line.set_visible(vis) # don't use the poly visibility state def get_ind_under_point(self, event): """get the index of vertex under point if within epsilon tolerance""" # display coords xytmp = np.asarray(self.poly.xy) xyt = self.poly.get_transform().transform(xytmp) xtt, ytt = xyt[:, 0], xyt[:, 1] dtt = np.sqrt((xtt - event.x) ** 2 + (ytt - event.y) ** 2) indseq = np.nonzero(np.equal(dtt, np.amin(dtt)))[0] ind = indseq[0] if dtt[ind] >= self.epsilon: ind = None return ind def button_press_callback(self, event): """whenever a mouse button is pressed""" if event.inaxes is None: return if event.button != 1: return self._ind = self.get_ind_under_point(event) if self._ind is None: xys = self.poly.get_transform().transform(self.poly.xy) ptmp = event.x, event.y # display coords if len(xys) == 1: self.poly.xy = np.array( [(event.xdata, event.ydata)] + [(event.xdata, event.ydata)]) self.line.set_data(list(zip(*self.poly.xy))) self.canvas.restore_region(self.background) self.ax.draw_artist(self.poly) self.ax.draw_artist(self.line) self.canvas.update() return dmin = -1 imin = -1 for i in range(len(xys) - 1): s0tmp = xys[i] s1tmp = xys[i + 1] dtmp = dist_point_to_segment(ptmp, s0tmp, s1tmp) if dmin == -1: dmin = dtmp imin = i elif dtmp < dmin: dmin = dtmp imin = i i = imin self.poly.xy = np.array(list(self.poly.xy[:i + 1]) + [(event.xdata, event.ydata)] + list(self.poly.xy[i + 1:])) self.line.set_data(list(zip(*self.poly.xy))) self.canvas.restore_region(self.background) self.ax.draw_artist(self.poly) self.ax.draw_artist(self.line) self.canvas.update() def button_release_callback(self, event): """Whenever a mouse button is released""" if event.button != 1: return self._ind = None self.update_plots() def update_plots(self): """ Update Plots """ polymask = Path(self.poly.xy).contains_points(self.pntxy) self.polyi_changed.emit(polymask.tolist()) def motion_notify_callback(self, event): """on mouse movement""" if self._ind is None: return if event.inaxes is None: return if event.button != 1: return xtmp, ytmp = event.xdata, event.ydata self.poly.xy[self._ind] = xtmp, ytmp if self._ind == 0: self.poly.xy[-1] = xtmp, ytmp self.line.set_data(list(zip(*self.poly.xy))) self.canvas.restore_region(self.background) self.ax.draw_artist(self.poly) self.ax.draw_artist(self.line) self.canvas.update()
class MaskDrawer(object): """An interactive polygon mask drawer on an image. Parameters ---------- ax : matplotlib plot axis Inpimg : 2d numpy array Input image to overlay for drawing mask Mask : Boolean numpy array same size of inpimg A Mask which will be used as initial mask and updated upon confirmation max_ds : float Max pixel distance to count as a vertex hit. PolyAtStart : List of vertices A list of square vertices to draw the initial polygon 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 max_ds of the line connecting two existing vertices 'n' : Invert the region selected by polynomial for masking 'c' : Confirm the polygon and update the mask """ showverts = True epsilon = 5 # max pixel distance to count as a vertex hit def __init__(self, ax, Inpimg, Mask, max_ds=10, PolyAtStart=[(50, 50), (100, 50), (100, 100), (50, 100)]): self.showverts = True self.max_ds = max_ds self.Mask = Mask self.img = Inpimg self.maskinvert = False # imshow the image self.imgplot = ax.imshow(np.ma.filled( np.ma.array(self.img, mask=self.Mask, fill_value=np.nan)), cmap=cm.gray) self.poly = Polygon(PolyAtStart, animated=True, fc='y', ec='none', alpha=0.5) ax.add_patch(self.poly) ax.set_clip_on(False) ax.set_title( "Click and drag a point to move it; " "'i' to insert; 'd' to delete.\n" "'n' to invert the region for masking, 'c' to confirm & apply the mask." ) self.ax = ax x, y = zip(*self.poly.xy) self.line = Line2D(x, y, color='none', marker='o', mfc='r', alpha=0.7, animated=True) # self._update_line() self.ax.add_line(self.line) self.poly.add_callback(self.poly_changed) self._ind = None # the active vert canvas = self.poly.figure.canvas canvas.mpl_connect('draw_event', self.draw_callback) canvas.mpl_connect('button_press_event', self.button_press_callback) canvas.mpl_connect('button_release_event', self.button_release_callback) canvas.mpl_connect('key_press_event', self.key_press_callback) canvas.mpl_connect('motion_notify_event', self.motion_notify_callback) self.canvas = canvas 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 poly_changed(self, poly): 'this method is called whenever the polygon object is called' # only copy the artist props to the line (except visibility) vis = self.line.get_visible() Artist.update_from(self.line, poly) self.line.set_visible(vis) # don't use the poly visibility state 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' if not self.showverts: return if event.inaxes == None: return if event.button != 1: return self._ind = self.get_ind_under_point(event) def button_release_callback(self, event): 'whenever a mouse button is released' if not self.showverts: return if event.button != 1: return self._ind = None def key_press_callback(self, event): 'whenever a key is pressed' if not event.inaxes: return if event.key == 't': self.showverts = not self.showverts self.line.set_visible(self.showverts) if not self.showverts: self._ind = None elif event.key == 'd': ind = self.get_ind_under_point(event) if ind is not None: self.poly.xy = [ tup for i, tup in enumerate(self.poly.xy) if i != ind ] self.line.set_data(zip(*self.poly.xy)) elif event.key == 'i': xys = self.poly.get_transform().transform(self.poly.xy) p = event.x, event.y # display coords for i in range(len(xys) - 1): s0 = xys[i] s1 = xys[i + 1] d = dist_point_to_segment(p, s0, s1) if d <= self.epsilon: self.poly.xy = np.array( list(self.poly.xy[:i]) + [(event.xdata, event.ydata)] + list(self.poly.xy[i:])) self.line.set_data(zip(*self.poly.xy)) break elif event.key == 'n': """ Flips the region inside out of the polygon to be masked """ print('Inverting the mask region') self.maskinvert = not self.maskinvert elif event.key == 'c': """ Confirm the drawn polynomial shape and add update the mask """ self.UpdateMask() #Update the imshowed image with new mask self.imgplot.set_data( np.ma.filled( np.ma.array(self.img, mask=self.Mask, fill_value=np.nan))) self.imgplot.figure.canvas.draw() self.canvas.draw() def UpdateMask(self): """ Updates the maks array with points insied the polygon """ print('Updating the original Mask..') Path = self.poly.get_path() h, w = self.Mask.shape y, x = np.mgrid[:h, :w] XYpoints = np.transpose((x.ravel(), y.ravel())) NewMask = Path.contains_points(XYpoints) if self.maskinvert: NewMask = ~NewMask # Combine the two mask by taing an elemet wise or self.Mask = self.Mask | NewMask.reshape((h, w)) def motion_notify_callback(self, event): 'on mouse movement' if not self.showverts: return if self._ind is None: return if event.inaxes is None: return if event.button != 1: return x, y = event.xdata, event.ydata 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)
class PolyClick(object): """ p = PolyClick() # Start interactive polygon generator, or p = PolyClick(verts, ax=gca()) # Initialize polygon based on verts, or p = PolyClick(x, y, ax=gca()) # Initialize polygon based on x and y, or p = PolyClick('verts.pickle', ax=gca()) # Load in verts from pickle file If verts or (x, y) are not given, an interactive polygon creation session is started. Verticies are orange. Switch to editing mode by hitting return. This changes the vertecies to black. The points may be moved with the mouse, or modified with the key commands listed below. Current data are always available in bry.x, bry.y and bry.verts. Key commands: enter : switch to grid editing mode t : toggle visibility of verticies d : delete a vertex i : insert a vertex at a point on the polygon line Methods: p.dump(bry_file='bry.pickle) Write the current boundary informtion (bry.verts) to a cPickle file bry_file. p.load(bry_file='bry.pickle) Read in boundary informtion (verts) from the cPickle file bry_file. Attributes: p.x, p.y : The x and y locations of the polygon verticies. p.verts : The verticies of the polygon (equiv. to zip(x, y)) p.area : the area of the polygon. Positive for a counterclockwise path, negative for a clockwise path. p.centroid : the (x, y) location of the polygon centroid. """ _showverts = True _epsilon = 5 # max pixel distance to count as a vertex hit def _init_boundary_interactor(self): """Send polyclick thread to boundary interactor""" # Get rid old mpl connections. self._ax.figure.canvas.mpl_disconnect(self._key_id) self._ax.figure.canvas.mpl_disconnect(self._click_id) # Shade the selected region in a polygon self._poly = Polygon(self.verts, alpha=0.2, fc='k', animated=True) self._ax.add_patch(self._poly) # modify the line properties self._line.set_markerfacecolor('k') # get the canvas and connect the callback events cid = self._poly.add_callback(self._poly_changed) self._ind = None # the active vert self._canvas.mpl_connect('draw_event', self._draw_callback) self._canvas.mpl_connect('button_press_event', self._button_press_callback) self._canvas.mpl_connect('key_press_event', self._key_press_callback) self._canvas.mpl_connect('button_release_event', self._button_release_callback) self._canvas.mpl_connect('motion_notify_event', self._motion_notify_callback) 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 _poly_changed(self, poly): 'this method is called whenever the polygon object is called' # only copy the artist props to the line (except visibility) vis = self._line.get_visible() Artist.update_from(self._line, poly) self._line.set_visible(vis) # don't use the poly visibility state def _get_ind_under_point(self, event): 'get the index of the vertex under point if within epsilon tolerance' try: x, y = zip(*self._poly.xy) # display coords xt, yt = self._poly.get_transform().numerix_x_y(x, y) d = sqrt((xt-event.x)**2 + (yt-event.y)**2) indseq = nonzero(equal(d, amin(d))) ind = indseq[0] if d[ind]>=self._epsilon: ind = None return ind except: # display coords xy = asarray(self._poly.xy) xyt = self._poly.get_transform().transform(xy) xt, yt = xyt[:, 0], xyt[:, 1] d = sqrt((xt-event.x)**2 + (yt-event.y)**2) indseq = nonzero(equal(d, 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' # if not self._showverts: return if event.inaxes==None: return if event.button != 1: return self._ind = self._get_ind_under_point(event) def _button_release_callback(self, event): 'whenever a mouse button is released' # if not self._showverts: return if event.button != 1: return self._ind = None def _key_press_callback(self, event): 'whenever a key is pressed' if not event.inaxes: return if event.key=='t': self._showverts = not self._showverts elif event.key=='d': ind = self._get_ind_under_point(event) if ind is not None: self._poly.xy = [tup for i,tup in enumerate(self._poly.xy) if i!=ind] self._line.set_data(zip(*self._poly.xy)) elif event.key=='i': xys = self._poly.get_transform().seq_xy_tups(self._poly.xy) p = event.x, event.y # display coords for i in range(len(xys)-1): s0 = xys[i] s1 = xys[i+1] d = dist_point_to_segment(p, s0, s1) if d<=self._epsilon: self._poly.xy.insert(i+1, (event.xdata, event.ydata)) self._line.set_data(zip(*self._poly.xy)) break s0 = xys[-1] s1 = xys[0] d = dist_point_to_segment(p, s0, s1) if d<=self._epsilon: self._poly.xy.append((event.xdata, event.ydata)) self._line.set_data(zip(*self._poly.xy)) self._draw_callback(event) self._canvas.draw() def _motion_notify_callback(self, event): 'on mouse movement' if self._ind is None: return if event.inaxes is None: return if event.button != 1: return x,y = event.xdata, event.ydata self._poly.xy[self._ind] = x,y x, y = zip(*self._poly.xy) self._line.set_data(x, y) 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_return(self, event): if event.key in ('enter', None): self._init_boundary_interactor() def _on_click(self, event): self.x.append(event.xdata) self.y.append(event.ydata) self._line.set_data(self.x, self.y) self._background = self._canvas.copy_from_bbox(self._ax.bbox) self._ax.draw_artist(self._line) self._canvas.blit(self._ax.bbox) def __init__(self, *args, **kwargs): ax = kwargs.pop('ax', None) if ax is None: ax = pl.gca() self._ax = ax assert len(args) <=2, 'Input is either verticies (1 arg), or x and y (2 args)' if args is not (): if isinstance(args[0], str): verts = load(x) x, y = zip(*verts) x = list(x); y = list(y) elif len(args) == 1: # assume verticies input verts = args[0] x, y = zip(*verts) x = list(x); y = list(y) else: # x and y inputs x = list(args[0]) y = list(args[1]) assert len(x) == len(y), 'x and y must have the same length.' else: # no input x = []; y = [] self._line = pl.Line2D(x, y, marker='o', markerfacecolor='orange', animated=True) self._ax.add_line(self._line) self._canvas = self._line.figure.canvas self._key_id = self._ax.figure.canvas.mpl_connect('key_press_event', self._on_return) self._click_id = self._ax.figure.canvas.mpl_connect('button_press_event', self._on_click) if len(x) > 0: self._init_boundary_interactor() def dump(self, poly_file): f = open(bry_file, 'wb') cPickle.dump(self.verts, f, protocol=-1) f.close() def load(self, poly_file): verts = load(bry_file) x, y = zip(*verts) self._line.set_data(x, y) if hasattr(self, '_poly'): self._poly.xy = verts self._draw_callback(None) self._canvas.draw() def inside(self, *args): """docstring for inside""" assert len(args) > 0, 'must provide either verticies or x and y.' if len(args) == 1: # assume verticies input verts = args[0] else: # x and y inputs x = list(args[0]) y = list(args[1]) assert len(x) == len(y), 'x and y must have the same length.' verts = zip(x, y) return self._geom.inside(verts) def get_verts(self):return zip(self.x, self.y) verts = property(get_verts) def get_xdata(self): return self._line.get_xdata() x = property(get_xdata) def get_ydata(self): return self._line.get_ydata() y = property(get_ydata) def _get_geom(self): return Polygeom(self.verts) _geom = property(_get_geom) def _get_area(self): return self._geom.area area = property(_get_area) def _get_centroid(self): return self._geom.centroid centroid = property(_get_centroid)
class NDViewer(QWidget): def __init__(self , parent=None , globalApplicationDict = None, show_tour = True, show_select_tools = False, plotParams = None, ): self.globalApplicationDict = globalApplicationDict self.plotParams = plotParams if self.plotParams is None: self.plotParams = ParamWidget(plotParamDefault).get_dict() self.show_tour = show_tour self.show_select_tools = show_select_tools QWidget.__init__(self,parent ) self.spikeSortingWin = self.parent() mainLayout = QVBoxLayout() self.setLayout(mainLayout) h = QHBoxLayout() mainLayout.addLayout(h) self.widgetProjection = QWidget() v = QVBoxLayout() h.addLayout(v) self.scrollArea = QScrollArea() self.scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) #~ self.scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) #~ self.scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.scrollArea.setWidget( self.widgetProjection ) self.scrollArea.setMinimumWidth(180) v.addWidget( self.scrollArea ) if show_tour: self.randButton = QPushButton( QIcon(':/roll.png') , 'Random') v.addWidget(self.randButton) self.connect(self.randButton, SIGNAL('clicked()') , self.randomPosition) self.startRandTourButton = QPushButton( QIcon(':/helicoper_and_roll.png') , 'Random tour') self.startRandTourButton.setCheckable(True) v.addWidget(self.startRandTourButton) self.connect(self.startRandTourButton, SIGNAL('clicked()') , self.startStopTour) self.timerRandTour = QTimer() self.timerRandTour.setInterval(self.plotParams['interval']) self.connect(self.timerRandTour, SIGNAL('timeout()') , self.stepRandTour) self.startOptimizedTourButton = QPushButton( QIcon(':/helicoper_and_magic.png') , 'Optimized tour') self.startOptimizedTourButton.setCheckable(True) v.addWidget(self.startOptimizedTourButton) self.connect(self.startOptimizedTourButton, SIGNAL('clicked()') , self.startStopTour) self.timerOptimizedTour = QTimer() self.timerOptimizedTour.setInterval(self.plotParams['interval']) self.connect(self.timerOptimizedTour, SIGNAL('timeout()') , self.stepOptimizedTour) but = QPushButton( QIcon(':/configure.png') , 'Configure') v.addWidget(but) self.connect(but, SIGNAL('clicked()') , self.openConfigure) if show_select_tools: h2 = QHBoxLayout() groupbox = QGroupBox( 'Selection mode') groupbox.setLayout(h2) v.addWidget(groupbox) icons = [ ['pickone' , ':/color-picker.png'], ['lasso' , ':/lasso.png'], ['contour' , ':/polygon-editor.png'], ] self.selectButton = { } for name, icon in icons: but = QPushButton(QIcon(icon),'') h2.addWidget(but) but.setAutoExclusive(True) but.setCheckable(True) self.connect(but, SIGNAL('clicked()') , self.changeSelectMode) self.selectButton[name] = but #~ self.selectButton['pickone'].setChecked(True) self.clearSelectBut = QPushButton(QIcon(':/view-refresh.png'),'Clear selection') v.addWidget(self.clearSelectBut) self.connect(self.clearSelectBut, SIGNAL('clicked()') , self.clearSelection) self.canvas = SimpleCanvas() #~ self.ax = self.canvas.fig.add_subplot(1,1,1) self.ax = self.canvas.fig.add_axes([0.02, 0.02, .96, .96]) h.addWidget(self.canvas,2) self.canvas.setMinimumWidth(180) self.ax_circle = None self.create_axe_circle() self.tour_running = False # internal attribute self.dim = 0 # self.spins = [ ] # spin widget list self.toBeDisconnected = [ ] # manage mpl_connect and disconnect self.selectMode = None # actual mode self.epsilon = 4. # for pickle event self.poly = None # for contour self.selectionLine = None self.actualSelection = array([ ] , dtype='i') self.connect(self, SIGNAL('selectionChanged') , self.redrawSelection ) ## draw and redraw ## def change_dim(self, ndim): self.projection = zeros( (ndim, 2)) self.projection[0,0] = 1 self.projection[1,1] = 1 #spinwidgets self.widgetProjection = QWidget() self.widgetProjection.updateGeometry() g = QGridLayout() self.widgetProjection.setLayout(g) self.spins = [ ] for i in range(ndim): d1 = QDoubleSpinBox() d1.setValue(self.projection[i,0]) d2 = QDoubleSpinBox() d2.setValue(self.projection[i,1]) g.addWidget( QLabel('dim %d'%i), i, 0 ) g.addWidget( d1, i, 1 ) g.addWidget( d2, i, 2 ) self.connect(d1, SIGNAL('valueChanged( double )'), self.spinsChanged) self.connect(d2, SIGNAL('valueChanged( double )'), self.spinsChanged) d1.setSingleStep(0.05) d2.setSingleStep(0.05) #~ d1.setRange(0.,1.) #~ d2.setRange(0.,1.) d1.setRange(-1.,1.) d2.setRange(-1.,1.) self.spins.append( [d1, d2] ) self.scrollArea.setWidget( self.widgetProjection ) self.scrollArea.update() self.dim = ndim # speed vector for tour #~ self.tourSpeed = (random.rand( self.projection.shape[0], 2)-.5)/100. def change_point(self, data, dataLabels = None, colors = None, subset = None): """ data = dim 0 elements dim 1 dimension dataLabels = vector of cluster for colors """ if data.shape[1] != self.dim : self.change_dim(data.shape[1]) self.data = data if dataLabels is None: dataLabels = zeros( data.shape[0], dtype = 'i') self.dataLabels = dataLabels self.allLabels = unique(self.dataLabels) if colors is None: colors = [ 'c' , 'g' , 'r' , 'b' , 'k' , 'm' , 'y']*100 self.colors = colors if subset is None: subset = { } for c in self.allLabels: ind = self.dataLabels ==c subset[c] = ind self.subset = subset self.fullRedraw() self.refreshSpins() def fullRedraw(self): self.ax.clear() #~ proj = dot( self.data, self.projection ) for c in self.allLabels: #~ ind = self.dataLabels ==c ind = self.subset[c] proj = dot( self.data[ind,:], self.projection ) self.ax.plot( proj[:,0], proj[:,1], #proj[ind,0] , proj[ind,1], linestyle = 'None', marker = '.', color = self.colors[c], picker=self.epsilon) self.redraw() def redraw(self): if not(self.plotParams['autoscale']): self.ax.set_xlim( self.plotParams['xlim'] ) self.ax.set_ylim( self.plotParams['ylim'] ) self.canvas.draw() def spinsChanged(self,value): for i in range(self.projection.shape[0]): self.projection[i,0] =self.spins[i][0].value() self.projection[i,1] =self.spins[i][1].value() if self.plotParams['force_orthonormality']: m = sqrt(sum(self.projection**2, axis=0)) m = m[newaxis, :] self.projection /= m self.refreshSpins() self.fullRedraw() def refreshSpins(self): for i in range(self.projection.shape[0]): d1, d2 = self.spins[i] self.disconnect(d1, SIGNAL('valueChanged( double )'), self.spinsChanged) self.disconnect(d2, SIGNAL('valueChanged( double )'), self.spinsChanged) #~ d1.disconnect(SIGNAL('valueChanged( double )')) #~ d2.disconnect(SIGNAL('valueChanged( double )')) d1.setValue(self.projection[i,0]) d2.setValue(self.projection[i,1]) self.connect(d1, SIGNAL('valueChanged( double )'), self.spinsChanged) self.connect(d2, SIGNAL('valueChanged( double )'), self.spinsChanged) if self.plotParams['display_circle']: self.refreshCircleRadius() def refreshCircleRadius(self): for l in self.radiusLines: self.ax_circle.lines.remove(l) self.radiusLines = [ ] for i in range(self.projection.shape[0]): l, = self.ax_circle.plot([0,self.projection[i,0]] , [0 , self.projection[i,1]] , color = 'g') self.radiusLines.append(l) self.canvas.draw() def create_axe_circle(self): if self.plotParams['display_circle']: if self.ax_circle is None: ax= self.canvas.fig.add_axes([0.04, 0.04, .1, .1]) else: ax = self.ax_circle ax.clear() ax.set_xticks([ ]) ax.set_yticks([ ]) circle = Circle((0,0) , radius = 1. , facecolor = 'w') ax.add_patch(circle) ax.set_xlim([-1.02,1.02]) ax.set_ylim([-1.02,1.02]) # Fixme #~ ax.xaxis.set_visible(False) #~ ax.yaxis.set_visible(False) self.ax_circle = ax self.canvas.draw() self.radiusLines = [ ] else: if self.ax_circle is not None: self.canvas.fig.delaxes(self.ax_circle) self.ax_circle = None #~ else: #~ print 'oulala' ## config ## def openConfigure(self): dia = ParamDialog(plotParamDefault , keyformemory = 'ndviewer/options' , applicationdict = self.globalApplicationDict, title = 'Plot parameters', ) dia.param_widget.update( self.plotParams ) if dia.exec_(): self.plotParams = dia.param_widget.get_dict() self.timerRandTour.setInterval(self.plotParams['interval']) self.timerOptimizedTour.setInterval(self.plotParams['interval']) self.create_axe_circle() self.fullRedraw() ## random and tour tour ## def randomPosition(self): ndim = self.projection.shape[0] self.projection = random.rand(ndim,2)*2-1. if self.plotParams['force_orthonormality']: m = sqrt(sum(self.projection**2, axis=0)) self.projection /= m self.refreshSpins( ) self.fullRedraw( ) def startStopTour(self): if self.sender() == self.startRandTourButton: but = self.startRandTourButton mode = 'rand' self.startOptimizedTourButton.setChecked(False) elif self.sender() == self.startOptimizedTourButton: but = self.startOptimizedTourButton mode = 'optimized' self.startRandTourButton.setChecked(False) start = but.isChecked() if start: if self.show_select_tools: for name, but in self.selectButton.iteritems(): but.setChecked(False) but.setEnabled(False) self.clearSelectBut.setEnabled(False) self.selectMode = None self.clearSelection() if mode == 'rand': self.timerOptimizedTour.stop() self.timerRandTour.start() self.actualStep = self.plotParams['nsteps'] +1 elif mode == 'optimized': self.timerRandTour.stop() self.timerOptimizedTour.start() self.tour_running = True else: if self.show_select_tools: for name, but in self.selectButton.iteritems(): but.setEnabled(True) self.clearSelectBut.setEnabled(True) self.changeSelectMode() self.timerRandTour.stop() self.timerOptimizedTour.stop() self.tour_running = False self.refreshSpins( ) self.fullRedraw( ) def stepRandTour(self): #~ print 'stepRandTour' nsteps = self.plotParams['nsteps'] ndim = self.projection.shape[0] if self.actualStep >= nsteps: # random for next etap nextEtap = random.rand(ndim,2)*2-1. self.allSteps = empty( (ndim , 2 , nsteps)) for i in range(ndim): for j in range(2): self.allSteps[i,j , : ] = linspace(self.projection[i,j] , nextEtap[i,j] , nsteps) if self.plotParams['force_orthonormality']: m = sqrt(sum(self.allSteps**2, axis=0)) m = m[newaxis, : , :] self.allSteps /= m self.actualStep = 0 self.projection = self.allSteps[:,: , self.actualStep] self.actualStep += 1 self.refreshSpins( ) self.fullRedraw( ) def stepOptimizedTour(self): #~ print 'stepOptimizedTour' actual_lda = ComputeIndexLda(self.projection, self.data, self.dataLabels) nloop = 1 ndim = self.projection.shape[0] for i in range(nloop): delta = (random.rand(ndim, 2)*2-1)/20. new_proj = self.projection + delta # normalize orthonormality m = sqrt(sum(new_proj**2, axis=0)) m = m[newaxis, :] new_proj /= m new_lda = ComputeIndexLda(new_proj, self.data, self.dataLabels) if new_lda >=actual_lda: actual_lda = new_lda self.projection = new_proj self.refreshSpins() self.fullRedraw() ## selections ## def changeSelection(self, ind): self.actualSelection = array(ind, dtype='i') if not self.tour_running: self.redrawSelection() def changeSelectMode(self): self.selectMode = None for name, but in self.selectButton.iteritems(): if but.isChecked(): self.selectMode = name for e in self.toBeDisconnected: self.canvas.mpl_disconnect(e) self.toBeDisconnected = [ ] self.clearSelection() def clearSelection(self): self.clearArtistSelection() if self.selectMode =='pickone': cid = self.canvas.mpl_connect('pick_event', self.onPick) self.toBeDisconnected.append(cid) elif self.selectMode =='contour': cid1 = self.canvas.mpl_connect('button_press_event', self.pressContour) cid2 = self.canvas.mpl_connect('button_release_event', self.releaseContour) cid3 = self.canvas.mpl_connect('motion_notify_event', self.motionContour) self.toBeDisconnected += [cid1, cid2, cid3 ] self.poly =None self._ind = None elif self.selectMode =='lasso': cid = self.canvas.mpl_connect('button_press_event', self.startLasso) self.toBeDisconnected.append(cid) self.actualSelection = array([ ] , dtype='i') self.emit(SIGNAL('selectionChanged')) def clearArtistSelection(self): if self.poly is not None: self.ax.lines.remove(self.line) self.ax.patches.remove(self.poly) self.poly = None self.line = None #~ self.canvas.draw() self.redraw() # should not: if hasattr(self,'lasso'): #~ print 'del lasso in clearArtistSelection', self.canvas.widgetlock.locked() self.canvas.widgetlock.release(self.lasso) del self.lasso def onPick(self , event): if isinstance(event.artist, Line2D): xdata, ydata = event.artist.get_data() x,y = xdata[event.ind[0]], ydata[event.ind[0]] self.actualSelection = array([argmin( sum( (dot( self.data, self.projection )-array([[ x,y ]]) )**2 , axis=1)) ] , dtype='i') else: self.actualSelection = array([ ] , dtype='i') self.emit(SIGNAL('selectionChanged')) def startLasso(self, event): if event.button != 1: return #~ print 'startLasso', self.canvas.widgetlock.locked() if self.canvas.widgetlock.locked(): # sometimes there is a bug lassostop is not intercepted!!! # so to avoid 2 start #~ print 'in lasso bug' self.clearArtistSelection() return if event.inaxes is None: return #~ for e in self.toBeDisconnected: #~ self.canvas.mpl_disconnect(e) self.lasso = Lasso(event.inaxes, (event.xdata, event.ydata), self.stopLasso) # acquire a lock on the widget drawing self.canvas.widgetlock(self.lasso) def stopLasso(self, verts): #~ print 'stopLasso', self.canvas.widgetlock.locked() verts=Path(verts) self.actualSelection, = where(verts.contains_points(dot( self.data, self.projection ))) #~ self.canvas.draw() self.canvas.widgetlock.release(self.lasso) #~ print self.canvas.artists del self.lasso #~ print 'lasso deleted' #~ self.redraw() self.emit(SIGNAL('selectionChanged')) def pressContour(self, event): if event.inaxes==None: return if event.button != 1: return # new contour if self.poly is None: self.poly = Polygon( [(event.xdata , event.ydata)] , animated=False , alpha = .3 , color = 'g') self.ax.add_patch(self.poly) self.line, = self.ax.plot([event.xdata] , [event.ydata] , color = 'g', linewidth = 2 , marker = 'o' , markerfacecolor='g', animated=False) #~ self.canvas.draw() self.redraw() return # event near a point xy = asarray(self.poly.xy) xyt = self.poly.get_transform().transform(xy) xt, yt = xyt[:, 0], xyt[:, 1] print '#######' d = sqrt((xt-event.x)**2 + (yt-event.y)**2) indseq = nonzero(equal(d, amin(d)))[0] self._ind = indseq[0] if d[self._ind]>=self.epsilon: self._ind = None self.a=list(numpy.copy(self.poly.xy)) for i,j in enumerate(self.a): self.a[i]=tuple(j) # new point if self._ind is None: b=float(event.xdata) c=float(event.ydata) d=(b,c) e=[d] #self.a=numpy.append(self.a,e) self.a.extend(e) self.line.set_xdata( list(self.line.get_xdata()) + [ event.xdata] ) self.line.set_ydata( list(self.line.get_ydata()) + [ event.ydata] ) #~ self.canvas.draw() self.redraw() #self.poly.xy=a print self.a, type(self.a) if self.a != None: test=Path(self.a) self.actualSelection, = where(test.contains_points(dot( self.data, self.projection ))) self.emit(SIGNAL('selectionChanged')) def releaseContour(self , event): if event.button != 1: return self._ind = None def motionContour(self , event): if self._ind is None: return if event.inaxes is None: return if event.button != 1: return x,y = event.xdata, event.ydata self.poly.xy[self._ind] = x,y #self.line.set_data(zip(*self.poly.xy)) #~ self.canvas.draw() self.redraw() #self.poly.xy=list(self.poly.xy) #print self.poly.xy self.poly.xy=Path(self.poly.xy) self.actualSelection, = where(self.poly.xy.contains_points(dot( self.data, self.projection ))) self.emit(SIGNAL('selectionChanged')) def redrawSelection(self): if self.selectionLine is not None: if self.selectionLine in self.ax.lines: self.ax.lines.remove(self.selectionLine) proj = dot( self.data, self.projection ) self.selectionLine, = self.ax.plot(proj[self.actualSelection,0] , proj[self.actualSelection,1], linestyle = 'None', markersize = 10, marker = 'o' , markerfacecolor='m', markeredgecolor='k', alpha = .6, ) #~ self.canvas.draw() self.redraw()
class NDViewer(QWidget): def __init__( self, parent=None, globalApplicationDict=None, show_tour=True, show_select_tools=False, plotParams=None, ): self.globalApplicationDict = globalApplicationDict self.plotParams = plotParams if self.plotParams is None: self.plotParams = ParamWidget(plotParamDefault).get_dict() self.show_tour = show_tour self.show_select_tools = show_select_tools QWidget.__init__(self, parent) self.spikeSortingWin = self.parent() mainLayout = QVBoxLayout() self.setLayout(mainLayout) h = QHBoxLayout() mainLayout.addLayout(h) self.widgetProjection = QWidget() v = QVBoxLayout() h.addLayout(v) self.scrollArea = QScrollArea() self.scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) #~ self.scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) #~ self.scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.scrollArea.setWidget(self.widgetProjection) self.scrollArea.setMinimumWidth(180) v.addWidget(self.scrollArea) if show_tour: self.randButton = QPushButton(QIcon(':/roll.png'), 'Random') v.addWidget(self.randButton) self.connect(self.randButton, SIGNAL('clicked()'), self.randomPosition) self.startRandTourButton = QPushButton( QIcon(':/helicoper_and_roll.png'), 'Random tour') self.startRandTourButton.setCheckable(True) v.addWidget(self.startRandTourButton) self.connect(self.startRandTourButton, SIGNAL('clicked()'), self.startStopTour) self.timerRandTour = QTimer() self.timerRandTour.setInterval(self.plotParams['interval']) self.connect(self.timerRandTour, SIGNAL('timeout()'), self.stepRandTour) self.startOptimizedTourButton = QPushButton( QIcon(':/helicoper_and_magic.png'), 'Optimized tour') self.startOptimizedTourButton.setCheckable(True) v.addWidget(self.startOptimizedTourButton) self.connect(self.startOptimizedTourButton, SIGNAL('clicked()'), self.startStopTour) self.timerOptimizedTour = QTimer() self.timerOptimizedTour.setInterval(self.plotParams['interval']) self.connect(self.timerOptimizedTour, SIGNAL('timeout()'), self.stepOptimizedTour) but = QPushButton(QIcon(':/configure.png'), 'Configure') v.addWidget(but) self.connect(but, SIGNAL('clicked()'), self.openConfigure) if show_select_tools: h2 = QHBoxLayout() groupbox = QGroupBox('Selection mode') groupbox.setLayout(h2) v.addWidget(groupbox) icons = [ ['pickone', ':/color-picker.png'], ['lasso', ':/lasso.png'], ['contour', ':/polygon-editor.png'], ] self.selectButton = {} for name, icon in icons: but = QPushButton(QIcon(icon), '') h2.addWidget(but) but.setAutoExclusive(True) but.setCheckable(True) self.connect(but, SIGNAL('clicked()'), self.changeSelectMode) self.selectButton[name] = but #~ self.selectButton['pickone'].setChecked(True) self.clearSelectBut = QPushButton(QIcon(':/view-refresh.png'), 'Clear selection') v.addWidget(self.clearSelectBut) self.connect(self.clearSelectBut, SIGNAL('clicked()'), self.clearSelection) self.canvas = SimpleCanvas() #~ self.ax = self.canvas.fig.add_subplot(1,1,1) self.ax = self.canvas.fig.add_axes([0.02, 0.02, .96, .96]) h.addWidget(self.canvas, 2) self.canvas.setMinimumWidth(180) self.ax_circle = None self.create_axe_circle() self.tour_running = False # internal attribute self.dim = 0 # self.spins = [] # spin widget list self.toBeDisconnected = [] # manage mpl_connect and disconnect self.selectMode = None # actual mode self.epsilon = 4. # for pickle event self.poly = None # for contour self.selectionLine = None self.actualSelection = array([], dtype='i') self.connect(self, SIGNAL('selectionChanged'), self.redrawSelection) ## draw and redraw ## def change_dim(self, ndim): self.projection = zeros((ndim, 2)) self.projection[0, 0] = 1 self.projection[1, 1] = 1 #spinwidgets self.widgetProjection = QWidget() self.widgetProjection.updateGeometry() g = QGridLayout() self.widgetProjection.setLayout(g) self.spins = [] for i in range(ndim): d1 = QDoubleSpinBox() d1.setValue(self.projection[i, 0]) d2 = QDoubleSpinBox() d2.setValue(self.projection[i, 1]) g.addWidget(QLabel('dim %d' % i), i, 0) g.addWidget(d1, i, 1) g.addWidget(d2, i, 2) self.connect(d1, SIGNAL('valueChanged( double )'), self.spinsChanged) self.connect(d2, SIGNAL('valueChanged( double )'), self.spinsChanged) d1.setSingleStep(0.05) d2.setSingleStep(0.05) #~ d1.setRange(0.,1.) #~ d2.setRange(0.,1.) d1.setRange(-1., 1.) d2.setRange(-1., 1.) self.spins.append([d1, d2]) self.scrollArea.setWidget(self.widgetProjection) self.scrollArea.update() self.dim = ndim # speed vector for tour #~ self.tourSpeed = (random.rand( self.projection.shape[0], 2)-.5)/100. def change_point(self, data, dataLabels=None, colors=None, subset=None): """ data = dim 0 elements dim 1 dimension dataLabels = vector of cluster for colors """ if data.shape[1] != self.dim: self.change_dim(data.shape[1]) self.data = data if dataLabels is None: dataLabels = zeros(data.shape[0], dtype='i') self.dataLabels = dataLabels self.allLabels = unique(self.dataLabels) if colors is None: colors = ['c', 'g', 'r', 'b', 'k', 'm', 'y'] * 100 self.colors = colors if subset is None: subset = {} for c in self.allLabels: ind = self.dataLabels == c subset[c] = ind self.subset = subset self.fullRedraw() self.refreshSpins() def fullRedraw(self): self.ax.clear() #~ proj = dot( self.data, self.projection ) for c in self.allLabels: #~ ind = self.dataLabels ==c ind = self.subset[c] proj = dot(self.data[ind, :], self.projection) self.ax.plot( proj[:, 0], proj[:, 1], #proj[ind,0] , proj[ind,1], linestyle='None', marker='.', color=self.colors[c], picker=self.epsilon) self.redraw() def redraw(self): if not (self.plotParams['autoscale']): self.ax.set_xlim(self.plotParams['xlim']) self.ax.set_ylim(self.plotParams['ylim']) self.canvas.draw() def spinsChanged(self, value): for i in range(self.projection.shape[0]): self.projection[i, 0] = self.spins[i][0].value() self.projection[i, 1] = self.spins[i][1].value() if self.plotParams['force_orthonormality']: m = sqrt(sum(self.projection**2, axis=0)) m = m[newaxis, :] self.projection /= m self.refreshSpins() self.fullRedraw() def refreshSpins(self): for i in range(self.projection.shape[0]): d1, d2 = self.spins[i] self.disconnect(d1, SIGNAL('valueChanged( double )'), self.spinsChanged) self.disconnect(d2, SIGNAL('valueChanged( double )'), self.spinsChanged) #~ d1.disconnect(SIGNAL('valueChanged( double )')) #~ d2.disconnect(SIGNAL('valueChanged( double )')) d1.setValue(self.projection[i, 0]) d2.setValue(self.projection[i, 1]) self.connect(d1, SIGNAL('valueChanged( double )'), self.spinsChanged) self.connect(d2, SIGNAL('valueChanged( double )'), self.spinsChanged) if self.plotParams['display_circle']: self.refreshCircleRadius() def refreshCircleRadius(self): for l in self.radiusLines: self.ax_circle.lines.remove(l) self.radiusLines = [] for i in range(self.projection.shape[0]): l, = self.ax_circle.plot([0, self.projection[i, 0]], [0, self.projection[i, 1]], color='g') self.radiusLines.append(l) self.canvas.draw() def create_axe_circle(self): if self.plotParams['display_circle']: if self.ax_circle is None: ax = self.canvas.fig.add_axes([0.04, 0.04, .1, .1]) else: ax = self.ax_circle ax.clear() ax.set_xticks([]) ax.set_yticks([]) circle = Circle((0, 0), radius=1., facecolor='w') ax.add_patch(circle) ax.set_xlim([-1.02, 1.02]) ax.set_ylim([-1.02, 1.02]) # Fixme #~ ax.xaxis.set_visible(False) #~ ax.yaxis.set_visible(False) self.ax_circle = ax self.canvas.draw() self.radiusLines = [] else: if self.ax_circle is not None: self.canvas.fig.delaxes(self.ax_circle) self.ax_circle = None #~ else: #~ print 'oulala' ## config ## def openConfigure(self): dia = ParamDialog( plotParamDefault, keyformemory='ndviewer/options', applicationdict=self.globalApplicationDict, title='Plot parameters', ) dia.param_widget.update(self.plotParams) if dia.exec_(): self.plotParams = dia.param_widget.get_dict() self.timerRandTour.setInterval(self.plotParams['interval']) self.timerOptimizedTour.setInterval(self.plotParams['interval']) self.create_axe_circle() self.fullRedraw() ## random and tour tour ## def randomPosition(self): ndim = self.projection.shape[0] self.projection = random.rand(ndim, 2) * 2 - 1. if self.plotParams['force_orthonormality']: m = sqrt(sum(self.projection**2, axis=0)) self.projection /= m self.refreshSpins() self.fullRedraw() def startStopTour(self): if self.sender() == self.startRandTourButton: but = self.startRandTourButton mode = 'rand' self.startOptimizedTourButton.setChecked(False) elif self.sender() == self.startOptimizedTourButton: but = self.startOptimizedTourButton mode = 'optimized' self.startRandTourButton.setChecked(False) start = but.isChecked() if start: if self.show_select_tools: for name, but in self.selectButton.iteritems(): but.setChecked(False) but.setEnabled(False) self.clearSelectBut.setEnabled(False) self.selectMode = None self.clearSelection() if mode == 'rand': self.timerOptimizedTour.stop() self.timerRandTour.start() self.actualStep = self.plotParams['nsteps'] + 1 elif mode == 'optimized': self.timerRandTour.stop() self.timerOptimizedTour.start() self.tour_running = True else: if self.show_select_tools: for name, but in self.selectButton.iteritems(): but.setEnabled(True) self.clearSelectBut.setEnabled(True) self.changeSelectMode() self.timerRandTour.stop() self.timerOptimizedTour.stop() self.tour_running = False self.refreshSpins() self.fullRedraw() def stepRandTour(self): #~ print 'stepRandTour' nsteps = self.plotParams['nsteps'] ndim = self.projection.shape[0] if self.actualStep >= nsteps: # random for next etap nextEtap = random.rand(ndim, 2) * 2 - 1. self.allSteps = empty((ndim, 2, nsteps)) for i in range(ndim): for j in range(2): self.allSteps[i, j, :] = linspace(self.projection[i, j], nextEtap[i, j], nsteps) if self.plotParams['force_orthonormality']: m = sqrt(sum(self.allSteps**2, axis=0)) m = m[newaxis, :, :] self.allSteps /= m self.actualStep = 0 self.projection = self.allSteps[:, :, self.actualStep] self.actualStep += 1 self.refreshSpins() self.fullRedraw() def stepOptimizedTour(self): #~ print 'stepOptimizedTour' actual_lda = ComputeIndexLda(self.projection, self.data, self.dataLabels) nloop = 1 ndim = self.projection.shape[0] for i in range(nloop): delta = (random.rand(ndim, 2) * 2 - 1) / 20. new_proj = self.projection + delta # normalize orthonormality m = sqrt(sum(new_proj**2, axis=0)) m = m[newaxis, :] new_proj /= m new_lda = ComputeIndexLda(new_proj, self.data, self.dataLabels) if new_lda >= actual_lda: actual_lda = new_lda self.projection = new_proj self.refreshSpins() self.fullRedraw() ## selections ## def changeSelection(self, ind): self.actualSelection = array(ind, dtype='i') if not self.tour_running: self.redrawSelection() def changeSelectMode(self): self.selectMode = None for name, but in self.selectButton.iteritems(): if but.isChecked(): self.selectMode = name for e in self.toBeDisconnected: self.canvas.mpl_disconnect(e) self.toBeDisconnected = [] self.clearSelection() def clearSelection(self): self.clearArtistSelection() if self.selectMode == 'pickone': cid = self.canvas.mpl_connect('pick_event', self.onPick) self.toBeDisconnected.append(cid) elif self.selectMode == 'contour': cid1 = self.canvas.mpl_connect('button_press_event', self.pressContour) cid2 = self.canvas.mpl_connect('button_release_event', self.releaseContour) cid3 = self.canvas.mpl_connect('motion_notify_event', self.motionContour) self.toBeDisconnected += [cid1, cid2, cid3] self.poly = None self._ind = None elif self.selectMode == 'lasso': cid = self.canvas.mpl_connect('button_press_event', self.startLasso) self.toBeDisconnected.append(cid) self.actualSelection = array([], dtype='i') self.emit(SIGNAL('selectionChanged')) def clearArtistSelection(self): if self.poly is not None: self.ax.lines.remove(self.line) self.ax.patches.remove(self.poly) self.poly = None self.line = None #~ self.canvas.draw() self.redraw() # should not: if hasattr(self, 'lasso'): #~ print 'del lasso in clearArtistSelection', self.canvas.widgetlock.locked() self.canvas.widgetlock.release(self.lasso) del self.lasso def onPick(self, event): if isinstance(event.artist, Line2D): xdata, ydata = event.artist.get_data() x, y = xdata[event.ind[0]], ydata[event.ind[0]] self.actualSelection = array([ argmin( sum((dot(self.data, self.projection) - array([[x, y]]))**2, axis=1)) ], dtype='i') else: self.actualSelection = array([], dtype='i') self.emit(SIGNAL('selectionChanged')) def startLasso(self, event): if event.button != 1: return #~ print 'startLasso', self.canvas.widgetlock.locked() if self.canvas.widgetlock.locked(): # sometimes there is a bug lassostop is not intercepted!!! # so to avoid 2 start #~ print 'in lasso bug' self.clearArtistSelection() return if event.inaxes is None: return #~ for e in self.toBeDisconnected: #~ self.canvas.mpl_disconnect(e) self.lasso = Lasso(event.inaxes, (event.xdata, event.ydata), self.stopLasso) # acquire a lock on the widget drawing self.canvas.widgetlock(self.lasso) def stopLasso(self, verts): #~ print 'stopLasso', self.canvas.widgetlock.locked() verts = Path(verts) self.actualSelection, = where( verts.contains_points(dot(self.data, self.projection))) #~ self.canvas.draw() self.canvas.widgetlock.release(self.lasso) #~ print self.canvas.artists del self.lasso #~ print 'lasso deleted' #~ self.redraw() self.emit(SIGNAL('selectionChanged')) def pressContour(self, event): if event.inaxes == None: return if event.button != 1: return # new contour if self.poly is None: self.poly = Polygon([(event.xdata, event.ydata)], animated=False, alpha=.3, color='g') self.ax.add_patch(self.poly) self.line, = self.ax.plot([event.xdata], [event.ydata], color='g', linewidth=2, marker='o', markerfacecolor='g', animated=False) #~ self.canvas.draw() self.redraw() return # event near a point xy = asarray(self.poly.xy) xyt = self.poly.get_transform().transform(xy) xt, yt = xyt[:, 0], xyt[:, 1] print '#######' d = sqrt((xt - event.x)**2 + (yt - event.y)**2) indseq = nonzero(equal(d, amin(d)))[0] self._ind = indseq[0] if d[self._ind] >= self.epsilon: self._ind = None self.a = list(numpy.copy(self.poly.xy)) for i, j in enumerate(self.a): self.a[i] = tuple(j) # new point if self._ind is None: b = float(event.xdata) c = float(event.ydata) d = (b, c) e = [d] #self.a=numpy.append(self.a,e) self.a.extend(e) self.line.set_xdata(list(self.line.get_xdata()) + [event.xdata]) self.line.set_ydata(list(self.line.get_ydata()) + [event.ydata]) #~ self.canvas.draw() self.redraw() #self.poly.xy=a print self.a, type(self.a) if self.a != None: test = Path(self.a) self.actualSelection, = where( test.contains_points(dot(self.data, self.projection))) self.emit(SIGNAL('selectionChanged')) def releaseContour(self, event): if event.button != 1: return self._ind = None def motionContour(self, event): if self._ind is None: return if event.inaxes is None: return if event.button != 1: return x, y = event.xdata, event.ydata self.poly.xy[self._ind] = x, y #self.line.set_data(zip(*self.poly.xy)) #~ self.canvas.draw() self.redraw() #self.poly.xy=list(self.poly.xy) #print self.poly.xy self.poly.xy = Path(self.poly.xy) self.actualSelection, = where( self.poly.xy.contains_points(dot(self.data, self.projection))) self.emit(SIGNAL('selectionChanged')) def redrawSelection(self): if self.selectionLine is not None: if self.selectionLine in self.ax.lines: self.ax.lines.remove(self.selectionLine) proj = dot(self.data, self.projection) self.selectionLine, = self.ax.plot( proj[self.actualSelection, 0], proj[self.actualSelection, 1], linestyle='None', markersize=10, marker='o', markerfacecolor='m', markeredgecolor='k', alpha=.6, ) #~ self.canvas.draw() self.redraw()