Ejemplo n.º 1
0
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)
Ejemplo n.º 2
0
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)
Ejemplo n.º 3
0
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
Ejemplo n.º 5
0
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()
Ejemplo n.º 6
0
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)
Ejemplo n.º 7
0
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))
Ejemplo n.º 8
0
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
Ejemplo n.º 9
0
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()
Ejemplo n.º 10
0
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
Ejemplo n.º 11
0
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
Ejemplo n.º 12
0
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()
Ejemplo n.º 13
0
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)
Ejemplo n.º 14
0
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)
Ejemplo n.º 15
0
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()
Ejemplo n.º 16
0
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()