Ejemplo n.º 1
0
class RectangleInteractor(QObject):

    epsilon = 5
    showverts = True
    mySignal = pyqtSignal(str)
    modSignal = pyqtSignal(str)
    
    def __init__(self,ax,corner,width,height=None,angle=0.):
        super().__init__()
        from matplotlib.patches import Rectangle
        from matplotlib.lines import Line2D
        # from matplotlib.artist import Artist
        # To avoid crashing with maximum recursion depth exceeded
        import sys
        sys.setrecursionlimit(10000) # 10000 is 10x the default value

        if height is None:
            self.type = 'Square'
            height = width
        else:
            self.type = 'Rectangle'
        self.ax = ax
        self.angle  = angle/180.*np.pi
        self.width  = width
        self.height = height
        self.rect = Rectangle(corner,width,height,edgecolor='Lime',facecolor='none',angle=angle,fill=False,animated=True)
        self.ax.add_patch(self.rect)
        self.canvas = self.rect.figure.canvas
        x,y = self.compute_markers()
        self.line = Line2D(x, y, marker='o', linestyle=None, linewidth=0., markerfacecolor='g', animated=True)
        self.ax.add_line(self.line)
        self.cid = self.rect.add_callback(self.rectangle_changed)
        self._ind = None  # the active point
        self.connect()
        self.aperture = self.rect
        self.press = None
        self.lock = None

    def compute_markers(self):

        theta0 = self.rect.angle / 180.*np.pi
        w0 = self.rect.get_width()
        h0 = self.rect.get_height()
        x0,y0 = self.rect.get_xy()
        c, s = np.cos(-theta0), np.sin(-theta0)
        R = np.matrix('{} {}; {} {}'.format(c, s, -s, c))

        x = [0.5*w0, w0, 0.5*w0]
        y = [0.5*h0, 0.5*h0, h0]

        self.xy = []
        x_ = []
        y_ = []
        for dx,dy in zip(x,y):
            (dx_,dy_), = np.array(np.dot(R,np.array([dx,dy])))
            self.xy.append((dx_+x0,dy_+y0))
            x_.append(dx_+x0)
            y_.append(dy_+y0)

        return x_,y_

    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_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.cid_key = self.canvas.mpl_connect('key_press_event', self.key_press_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_release)
        self.canvas.mpl_disconnect(self.cid_motion)
        self.canvas.mpl_disconnect(self.cid_key)
        self.rect.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.rect)
        self.ax.draw_artist(self.line)

    def rectangle_changed(self, rect):
        '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, rect)
        self.line.set_visible(vis)  
        
    def get_ind_under_point(self, event):
        'get the index of the point if within epsilon tolerance'

        x, y = zip(*self.xy)
        d = np.hypot(x - event.xdata, y - event.ydata)
        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)
        x0, y0 = self.rect.get_xy()
        w0, h0 = self.rect.get_width(), self.rect.get_height()
        theta0 = self.rect.angle/180*np.pi
        self.press = x0, y0, w0, h0, theta0, event.xdata, event.ydata
        self.xy0 = self.xy

        self.lock = "pressed"

    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':
            #self.disconnect()
            #self.rect = None
            #self.line = None
            self.mySignal.emit('rectangle deleted')
        self.canvas.draw_idle()

    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
        self.press = None
        self.lock = "released"
        self.background = None
        # To get other aperture redrawn
        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
        x0, y0, w0, h0, theta0, xpress, ypress = self.press
        self.dx = event.xdata - xpress
        self.dy = event.ydata - ypress
        self.update_rectangle()

        # Redraw rectangle and points
        self.canvas.restore_region(self.background)
        self.ax.draw_artist(self.rect)
        self.ax.draw_artist(self.line)
        self.canvas.update()
        self.canvas.flush_events()

        # Notify callback
        self.modSignal.emit('rectangle modified')

    def update_rectangle(self):

        x0, y0, w0, h0, theta0, xpress, ypress = self.press
        dx, dy = self.dx, self.dy
        
        if self.lock == "pressed":
            if self._ind == 0:
                self.lock = "move"
            else:
                self.lock = "resizerotate"
        elif self.lock == "move":
            if x0+dx < 0:
                xn = x0
                dx = 0
            else:
                xn = x0+dx
            if y0+dy < 0:
                yn = y0
                dy = 0
            else:
                yn = y0+dy
            self.rect.set_xy((xn,yn))
            # update line
            self.xy = [(i+dx,j+dy) for (i,j) in self.xy0]
            # Redefine line
            self.line.set_data(zip(*self.xy))
        # otherwise rotate and resize
        elif self.lock == 'resizerotate':
            xc,yc = self.xy0[0] # center is conserved in the markers
            dtheta = np.arctan2(ypress+dy-yc,xpress+dx-xc)-np.arctan2(ypress-yc,xpress-xc)
            theta_ = (theta0+dtheta) * 180./np.pi
            c, s = np.cos(theta0), np.sin(theta0)
            R = np.matrix('{} {}; {} {}'.format(c, s, -s, c))
            (dx_,dy_), = np.array(np.dot(R,np.array([dx,dy])))

            # Avoid to pass through the center            
            if self._ind == 1:
                w_ = w0+2*dx_  if (w0+2*dx_) > 0 else w0
                if self.type == 'Square':
                    h_ = w_
                else:
                    h_ = h0
            elif self._ind == 2:
                h_ = h0+2*dy_  if (h0+2*dy_) > 0 else h0
                if self.type == 'Square':
                    w_ = h_
                else:
                    w_ = w0
            # update rectangle
            self.rect.set_width(w_)
            self.rect.set_height(h_)
            self.rect.angle = theta_
            # update markers
            self.updateMarkers()

    def updateMarkers(self):
        # update points
        x,y = self.compute_markers()
        self.line.set_data(x,y)
Ejemplo n.º 2
0
class PixelInteractor(QObject):

    epsilon = 10
    showverts = True
    mySignal = pyqtSignal(str)
    modSignal = pyqtSignal(str)

    
    def __init__(self,ax,corner,width,angle=0.):
        super().__init__()
        from matplotlib.patches import Rectangle
        from matplotlib.lines import Line2D
        # from matplotlib.artist import Artist
        # To avoid crashing with maximum recursion depth exceeded
        import sys
        sys.setrecursionlimit(10000) # 10000 is 10x the default value

        self.type = 'Pixel'
        height = width
        self.ax = ax
        self.angle  = angle
        self.width  = width
        self.height = width
        # print('corner is ', corner)
        self.rect = Rectangle(corner,width,height,edgecolor='Lime',facecolor='none',angle=angle,fill=False,animated=True)
        self.ax.add_patch(self.rect)
        self.canvas = self.rect.figure.canvas

        x,y = self.compute_markers()
        self.line = Line2D(x, y, marker='s', linestyle=None, linewidth=0., markerfacecolor='g', animated=True)
        self.ax.add_line(self.line)

        self.cid = self.rect.add_callback(self.rectangle_changed)
        self._ind = None  # the active point

        self.connect()

        self.aperture = self.rect
        self.press = None
        self.lock = None


    def compute_markers(self):

        # theta0 = self.rect.angle / 180.*np.pi
        w0 = self.rect.get_width()
        # h0 = self.rect.get_height()
        x0,y0 = self.rect.get_xy()
        angle0 = self.rect.angle

        x = [x0+w0/np.sqrt(2.)*np.sin((45.-angle0)*np.pi/180.)]
        y = [y0+w0/np.sqrt(2.)*np.cos((45.-angle0)*np.pi/180.)]

        self.xy = [(x,y)]
        return x, y

    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_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.cid_key = self.canvas.mpl_connect('key_press_event', self.key_press_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_release)
        self.canvas.mpl_disconnect(self.cid_motion)
        self.canvas.mpl_disconnect(self.cid_key)
        self.rect.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.rect)
        self.ax.draw_artist(self.line)


    def rectangle_changed(self, rect):
        '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, rect)
        self.line.set_visible(vis)  

        
    def get_ind_under_point(self, event):
        'get the index of the point if within epsilon tolerance'

        x, y = self.xy[0]
        d = np.hypot(x - event.xdata, y - event.ydata)

        if d >= self.epsilon:
            ind = None
        else:
            ind = 0
            
        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)
        x0, y0 = self.rect.get_xy()
        w0, h0 = self.rect.get_width(), self.rect.get_height()
        theta0 = self.rect.angle/180*np.pi
        self.press = x0, y0, w0, h0, theta0, event.xdata, event.ydata
        self.xy0 = self.xy

        self.lock = "pressed"


    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':
            self.mySignal.emit('rectangle deleted')

        self.canvas.draw_idle()

    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
        self.press = None
        self.lock = "released"
        self.background = None
        # To get other aperture redrawn
        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

        x0, y0, w0, h0, theta0, xpress, ypress = self.press
        self.dx = event.xdata - xpress
        self.dy = event.ydata - ypress
        self.update_rectangle()

        # Redraw rectangle and points
        self.canvas.restore_region(self.background)
        self.ax.draw_artist(self.rect)
        self.ax.draw_artist(self.line)
        self.canvas.update()
        self.canvas.flush_events()
        
        # alternative (slower)
        # self.canvas.draw_idle()

        # Notify callback
        self.modSignal.emit('rectangle modified')

    def update_rectangle(self):

        x0, y0, w0, h0, theta0, xpress, ypress = self.press
        dx, dy = self.dx, self.dy
        
        if self.lock == "pressed":
            self.lock = "move"
        elif self.lock == "move":
            if x0+dx < 0:
                xn = x0
                dx = 0
            else:
                xn = x0+dx
            if y0+dy < 0:
                yn = y0
                dy = 0
            else:
                yn = y0+dy
            self.rect.set_xy((xn,yn))
            # update line
            self.xy = [(i+dx,j+dy) for (i,j) in self.xy0]
            # Redefine line
            self.line.set_data(zip(*self.xy))
            self.updateMarkers()

    def updateMarkers(self):
        # update points
        x,y = self.compute_markers()
        self.line.set_data(x,y)