Ejemplo n.º 1
0
class Polygon(object):

    def __init__(self, ax, xy, **kwargs):
        self._pol = MplPol(xy, kwargs)
        ax.add_patch(self._pol)

    @property
    def xy(self):
        return self._pol.get_xy()

    @xy.setter
    def xy(self, xy):
        self._pol.set_xy(xy)

    def remove(self):
        self._pol.remove()

    def anim_update(self, *args):
        pass
Ejemplo n.º 2
0
class OldLineInteractor(QObject):
    #    """
    #    A Gaussian line interactor.
    #    Arguments:
    #        ax axes to which the interactor is associated
    #        c0 intercept of continuum
    #        cs slope of continuum
    #        x0 center of the line
    #        A  amplitude of the line
    #        fwhm FWHM of the line
    #        epsilon max pixel distance to count as a vertex hit
    #    """
    showverts = True
    mySignal = pyqtSignal(str)
    modSignal = pyqtSignal(str)

    def __init__(self, ax, c0, cs, x0, A, fwhm, epsilon=10):
        super().__init__()
        # To avoid crashing with maximum recursion depth exceeded
        sys.setrecursionlimit(10000)  # 10000 is 10x the default value
        self.epsilon = epsilon
        self.ax = ax
        self.fig = ax.figure
        self.canvas = ax.figure.canvas
        self.c0 = c0  # Value of continuum at origin
        self.cs = cs  # Slope of the continuum
        self.type = 'Line'
        color = '#7ec0ee'
        self.x0 = x0
        self.A = A
        self.fwhm = fwhm
        self.computeMarkers()
        self.computeGaussian()
        self.gauss = Polygon(self.verts,
                             animated=True,
                             fill=False,
                             closed=False,
                             color=color)
        self.ax.add_patch(self.gauss)
        x, y = zip(*self.xy)
        self.line = Line2D(x,
                           y,
                           marker='o',
                           linestyle=None,
                           linewidth=0.,
                           markerfacecolor=color,
                           animated=True)
        self.ax.add_line(self.line)
        self._ind = None  # the active vert
        self.connect()

    def computeMarkers(self):
        'Compute position of markers.'
        x = self.x0 + 0.5 * self.fwhm * np.array([-1, 0, 1])
        y = self.c0 + x * self.cs + self.A * np.array([0.5, 1., 0.5])
        self.xy = [(i, j) for (i, j) in zip(x, y)]

    def computeGaussian(self):
        'Compute the Gaussian polygon from the position of the markers.'
        self.sigma = self.fwhm / (2 * np.sqrt(2 * np.log(2)))
        # Create an array of x values and compute the value of the Gaussian on it
        x = np.linspace(self.x0 - self.fwhm, self.x0 + self.fwhm, 30)
        dx = (x - self.x0) / self.sigma / np.sqrt(2.)
        y = self.c0 + x * self.cs + self.A * np.exp(-dx * dx)
        self.verts = [(x_, y_) for x_, y_ in zip(x, y)]
        return

    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)
        try:
            self.line.remove()
        except BaseException:
            print('no markers')
        try:
            self.gauss.remove()
        except BaseException:
            print('no line')
        self.canvas.draw_idle()

    def draw_callback(self, event):
        self.grab_background()
        self.ax.draw_artist(self.gauss)
        self.ax.draw_artist(self.line)

    def safe_draw(self):
        """Temporarily disconnect the draw_event callback to avoid recursion."""
        self.canvas.mpl_disconnect(self.cid_draw)
        self.canvas.draw_idle()
        self.cid_draw = self.canvas.mpl_connect('draw_event',
                                                self.draw_callback)

    def grab_background(self):
        self.safe_draw()
        self.background = self.canvas.copy_from_bbox(
            self.fig.bbox)  # or self.ax.bbox

    def get_ind_under_point(self, event):
        'get the index of the point if within epsilon tolerance'
        # Distance is computed in pixels on the screen
        xy = self.ax.transData.transform(self.xy)
        x, y = zip(*xy)
        x = np.array(x)
        y = np.array(y)
        d = np.hypot(x - event.x, y - event.y)
        indseq, = np.nonzero(d == d.min())
        ind = indseq[0]
        if d[ind] >= self.epsilon:
            ind = None
        return ind

    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('line deleted')
        self.canvas.draw_idle()

    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
        # Redrawing
        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
        # Update markers and Gaussian parameters
        x_, y_ = event.xdata, event.ydata
        x, y = zip(*self.xy)
        if self._ind == 0:
            if x_ < x[1]:
                self.fwhm = 2 * (x[1] - x_)
        elif self._ind == 1:
            dx = x_ - x[1]
            self.x0 += dx
            dy = y_ - y[1]
            if (self.A > 0) & (dy < -self.A):  # Emission line
                pass
            elif (self.A < 0) & (dy > -self.A):  # Absorption line
                pass
            else:
                self.A += dy
        elif self._ind == 2:
            if x_ > x[1]:
                self.fwhm = 2 * (x_ - x[1])
        self.updateCurves()
        # Notify callback
        self.modSignal.emit('line guess modified')

    def updateCurves(self):
        self.computeGaussian()
        self.computeMarkers()
        self.canvas.restore_region(self.background)
        self.line.set_data(zip(*self.xy))
        self.gauss.xy = self.verts
        self.ax.draw_artist(self.line)
        self.ax.draw_artist(self.gauss)
        self.canvas.update()
        self.canvas.flush_events()
Ejemplo n.º 3
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.º 4
0
class Shape(object):
    """
    Displays the polygon objects onto the canvas by supplying draw methods
    and maintaining internal information on the shape. Draws to matplotlib
    backend
    """
    def __init__(self, canvas=None, tag='', color=''):
        self.__canvas = canvas
        self.__coordinates = []
        self.__tag = tag
        self.__color = color
        self.__item_handler = None
        self.__plot = Plot.baseplot
        self.__hdf = None
        self.__attributes = []
        self.__note = ''
        self.__id = None
        self.__prev_x = 1.0
        self.__prev_y = 1.0
        self.__lines = []
        self.__saved = False
        self.__selected = False
        self.__drawing_line = None  # temporary line for free draw

    def add_attribute(self, attr):
        """
        Append a passed attribute onto the internal attribute list

        :param str attr: An attribute enum
        """
        if attr in TAGS:
            self.__attributes.append(attr)
            self.__saved = False
        else:
            logger.error('Caught invalid attribute for adding \'%s\'' % attr)

    def anchor_rectangle(self, event):
        """
        Establishes a corner of a rectangle as an anchor for when the user drags the cursor to
        create a rectangle. Used in 'Draw Rect' button

        :param event: A matplotlib backend event object
        """
        self.__coordinates.append((event.xdata, event.ydata))
        self.__prev_x = event.x
        self.__prev_y = self.__canvas.figure.bbox.height - event.y

    def clear_lines(self):
        """
        Remove any existing lines and clear the shape data. This is called so the lines don't
        remain on the screen if the user unclicks the toggleable button.
        """
        for line in self.__lines:
            line.remove()

    def clear_unfinished_data(self):
        """
        In the event the user is plotting points and decides to switch
        buttons without finishing the free draw polygon, the lines must
        be cleared and data must be reset. this function will ensure
        any unfinished data is cleared for future shape drawing.
        """
        if self.__can_draw() != -1:
            return
        for line in self.__lines:
            line.remove()
        self.__coordinates = []

    def draw(self, fig, fl, plot=Plot.baseplot, fill=False):
        """
        Draw the shape to the canvas, onto the passed figure. Only fill the
        object if the *fill* parameter is set to ``True``

        :param fig: A ``SubplotAxes`` object from the matplotlib backend
        :param fl: A string representing the HDF path
        :param plot: ``constants.Plot`` enum specifying which plot the object belongs to
        :param bool fill: ``False`` for fill, ``True`` for outline
        """
        logger.info("Drawing polygon")

        # Generates a random color
        r = lambda: random.randint(0, 255)
        clr = '#%02X%02X%02X' % (r(), r(), r())

        self.__color = clr
        self.__plot = plot
        self.__hdf = fl
        self.__item_handler = \
            Polygon(self.__coordinates, facecolor=clr, fill=fill, picker=5)
        if self.__selected:
            self.set_highlight(True)
        fig.add_patch(self.__item_handler)

    def fill_rectangle(self, event, plot, fl, fig, fill=False):
        """
        Draws the rectangle and stores the coordinates of the rectangle internally. Used
        in 'Draw Rect' button. Forwards argument parameters to ``draw``

        :param fig: Figure to draw canvas to
        :param bool fill: Whether to fill or no fill the shape
        """
        try:
            self.lastrect
        except AttributeError:
            pass
        else:
            self.__canvas._tkcanvas.delete(self.lastrect)
            del self.lastrect

        if event.xdata is not None and event.ydata is not None:
            beg = self.__coordinates[0]
            self.__coordinates.append((event.xdata, beg[1]))
            self.__coordinates.append((event.xdata, event.ydata))
            self.__coordinates.append((beg[0], event.ydata))

            self.draw(fig, fl, plot, fill)
        else:
            self.__coordinates = []

    def generate_lat_range(self):
        axes = self.__canvas.figure.get_axes()
        labels = [x.get_xlabel() for x in axes]
        lat = axes[labels.index(u'Latitude')]
        time = axes[labels.index(u'Time')]
        min_ = lat.transData.inverted().transform(
            time.transData.transform(np.array(min(self.__coordinates))))[0]
        max_ = lat.transData.inverted().transform(
            time.transData.transform(np.array(max(self.__coordinates))))[0]
        return '%.4f - %.4f' % (min_, max_)

    def get_attributes(self):
        """
        Return attributes list maintained by shape

        :rtype: :py:class:`list`
        """
        return self.__attributes

    def get_color(self):
        """
        Return the hexdecimal color value

        :rtype: :py:class:`str`
        """
        return self.__color

    def get_coordinates(self):
        """
        Return the list of coordinates internally maintained by shape

        :rtype: :py:class:`list`
        """
        return self.__coordinates

    def get_id(self):
        """
        Return the database ID of shape

        :rtype: :py:class:`int`
        """
        return self.__id

    def get_itemhandler(self):
        """
        Return the item handler object to the actual backend base

        :rtype: :py:class:`matplotlib.patches.polygon`
        """
        return self.__item_handler

    def get_max_lat(self):
        axes = self.__canvas.figure.get_axes()
        labels = [x.get_xlabel() for x in axes]
        lat = axes[labels.index(u'Latitude')]
        time = axes[labels.index(u'Time')]
        max_ = lat.transData.inverted().transform(
            time.transData.transform(np.array(max(self.__coordinates))))[0]
        return max_

    def get_min_lat(self):
        axes = self.__canvas.figure.get_axes()
        labels = [x.get_xlabel() for x in axes]
        lat = axes[labels.index(u'Latitude')]
        time = axes[labels.index(u'Time')]
        min_ = lat.transData.inverted().transform(
            time.transData.transform(np.array(min(self.__coordinates))))[0]
        return min_

    def get_notes(self):
        """
        Return the notes string internally maintained by shape

        :rtype: :py:class:`str`
        """
        return self.__note

    def get_plot(self):
        """
        Return the plot type

        :rtype: :py:class:`int`
        """
        return self.__plot

    def get_hdf(self):
        """
        Return the file used

        :rtype: :py:class:`str`
        """
        return self.__hdf

    def get_saved(self):
        """
        Returns if the shape has been saved or not
        """
        return self.__saved

    def get_tag(self):
        """
        Return the program Tag of shape

        :rtype: :py:class:`str`
        """
        return self.__tag

    def in_x_extent(self, x):
        time_cords = [pair[0] for pair in self.__coordinates]
        if min(time_cords) <= x <= max(time_cords):
            return True
        else:
            return False

    def in_y_extent(self, y):
        altitude_cords = [pair[1] for pair in self.__coordinates]
        if min(altitude_cords) <= y <= max(altitude_cords):
            return True
        else:
            return False

    def is_attribute(self, attr):
        """
        Return ``True`` if *attr* is inside the attributes list, ``False``
        otherwise.

        :param str attr:
        :rtype: :py:class:`bool`
        """
        for item in self.__attributes:
            if attr == item:
                logger.info('Found attribute')
                return True
        return False

    def is_empty(self):
        """
        Return ``True`` if empty, ``False`` otherwise
        """
        if len(self.__coordinates) == 0:
            return True
        return False

    def is_selected(self):
        """
        Return a boolean value based on whether the object is currently
        highlighted in the figure. Uses ``__selected``

        :rtype: :py:class:`bool`
        """
        return self.__selected

    def loaded_draw(self, fig, fill):
        """
        Called in the case of panning the plot, since panning the plot invalidates
        the previous figure, the figures must first be cleared and the shapes are removed.
        Loaded draw draws the shapes back into view using a new figure.

        :param fig: A ``SubplotAxes`` object to add the patch to
        :param bool fill: Boolean value whether to have the shape filled in when drawn to or not
        """
        self.__item_handler = \
            Polygon(self.__coordinates, facecolor=self.__color, fill=fill, picker=5)
        if self.__selected:
            self.set_highlight(True)
        fig.add_patch(self.__item_handler)

    def paint(self, color):
        """
        Changes the color of the shape and saves it internally

        :param color: the new color of the shape
        """
        self.set_color(color)
        self.__saved = False

    def plot_point(self, event, plot, fl, fig, fill=False):
        """
        Plot a single point to the shape, connect any previous existing
        points and fill to a shape if the current coordinate intersects
        the beginning point.

        :param event: A ``matplotlib.backend_bases.MouseEvent`` passed object
        :param plot: an integer indicating which plot it was draw on
        :param fl: A string representing the HDF it was drawn on
        :param fig: The figure to be drawing the canvas to
        :param bool fill: Whether the shape will have a solid fill or not
        """
        self.__coordinates.append((event.xdata, event.ydata))
        logger.debug("Plotted point at (%0.5f, %0.5f)", event.xdata,
                     event.ydata)
        if len(self.__coordinates) > 1:
            logger.debug("Drawing line from plot")
            self.__lines.append(
                mlines.Line2D((self.__prev_x, event.xdata),
                              (self.__prev_y, event.ydata),
                              linewidth=2.0,
                              color='#000000'))
            fig.add_artist(self.__lines[-1])
            self.__canvas.show()

        if len(self.__coordinates) > 3:
            index = self.__can_draw()
            if index > -1:
                logger.debug("Creating polygon from points")
                a1 = tuple_to_nparray(self.__coordinates[index])
                a2 = tuple_to_nparray(self.__coordinates[index + 1])
                b1 = tuple_to_nparray(self.__coordinates[-1])
                b2 = tuple_to_nparray(self.__coordinates[-2])
                x = get_intersection(a1, a2, b1, b2)
                pair = nparray_to_tuple(x)
                self.__coordinates[index] = pair

                del self.__coordinates[:index]
                self.__coordinates.pop()
                for line in self.__lines:
                    line.remove()
                self.__drawing_line.remove()
                self.__drawing_line = None
                self.__lines = []
                self.draw(fig, fl, plot=plot, fill=fill)
                self.__plot = plot
                self.__hdf = fl
                return True

        self.__prev_x = event.xdata
        self.__prev_y = event.ydata

    def sketch_line(self, event, fig):
        if self.__drawing_line:
            self.__drawing_line.remove()
        self.__drawing_line = \
            mlines.Line2D((self.__prev_x, event.xdata), (self.__prev_y, event.ydata), linewidth=2.0,
                          color='#000000')
        fig.add_artist(self.__drawing_line)
        self.__canvas.show()
        return

    def close_polygon(self, event, plot, fl, fig, fill=False):
        if len(self.__coordinates) > 3:
            index = self.__can_draw()
            if index > -1:
                logger.debug("Creating polygon from points")
                a1 = tuple_to_nparray(self.__coordinates[index])
                a2 = tuple_to_nparray(self.__coordinates[index + 1])
                b1 = tuple_to_nparray(self.__coordinates[-1])
                b2 = tuple_to_nparray(self.__coordinates[-2])
                x = get_intersection(a1, a2, b1, b2)
                pair = nparray_to_tuple(x)
                self.__coordinates[index] = pair

                del self.__coordinates[:index]
                self.__coordinates.pop()
                for line in self.__lines:
                    line.remove()
                self.__drawing_line.remove()
                self.__drawing_line = None
                self.__lines = []
                self.draw(fig, plot, fill)
                self.__plot = plot
                self.__hdf = fl
                return True
        else:
            logger.warning('Not enough points')

    def redraw(self, fig, fl, fill):
        """
        Function to draw the shape in the event the shape *may* or *may not* already
        be drawn. Checks if the image already exists, if not draws the image

        :param fig: A ``SubplotAxes`` object to add the patch to
        :param fl: A string representing the HDF file
        :param bool fill: Boolean value whether to have the shape filled in when drawn or not
        """
        if self.__item_handler is not None and self.__item_handler.is_figure_set(
        ):
            self.__item_handler.remove()
        self.__item_handler = \
            Polygon(self.__coordinates, facecolor=self.__color, fill=fill, picker=5)
        if self.__selected:
            self.set_highlight(True)
        self.__hdf = fl
        fig.add_patch(self.__item_handler)

    def remove(self):
        """
        Wrapper function to internally call matplotlib backend to remove
        the shape from the figure
        """
        if self.__item_handler is None:
            self.clear_lines()
        else:
            self.__item_handler.remove()

    def remove_attribute(self, attr):
        """
        Remove an attribute as specified in ``constants.py`` from the internal attributes variable

        :param str attr:
        """
        if attr in TAGS:
            self.__attributes.remove(attr)
            self.__saved = False
        else:
            logger.error('Caught invalid attribute for removal \'%s\'' % attr)

    # noinspection PyAttributeOutsideInit
    def rubberband(self, event):
        """
        Draws a temporary helper rectangle that outlines the final shape of the rectangle for
        the user. This draws to **screen** coordiantes, so backend is not needed here.

        :param event: A ``matplotlib.backend_bases.MouseEvent`` forwarded object.
        """
        try:
            self.lastrect
        except AttributeError:
            pass
        else:
            self.__canvas._tkcanvas.delete(self.lastrect)

        height = self.__canvas.figure.bbox.height
        y2 = height - event.y
        self.lastrect = self.__canvas._tkcanvas.create_rectangle(
            self.__prev_x, self.__prev_y, event.x, y2)

    def save(self):
        """
        Marks the shape as saved
        """
        self.__saved = True

    def set_attributes(self, attributes_list):
        """
        Set the internal list of attributes to a custom passed list

        :param list attributes_list:
        """
        for i in attributes_list:
            if i not in TAGS:
                logger.error('Caught invalid attribute for setting \'%s\'' % i)
                return
        self.__attributes = attributes_list

    def set_color(self, color):
        """
        Set internal color variable

        :param str color: Valid hexadecimal color value
        """
        self.__color = color

    def set_coordinates(self, coordinates):
        """
        Pass a list of coordinates to set to the shape to.

        :param list coordinates:
        """
        self.__coordinates = coordinates

    def set_highlight(self, highlight):
        """
        Set the ``linewidth`` and ``linestyle`` attributes of a the internal item
        handler. Highlights if *highlight* is ``True``, otherwise sets to normal
        outline.

        :param bool highlight:
        """
        if highlight:
            self.__selected = True
            self.__item_handler.set_linewidth(3.0)
            self.__item_handler.set_linestyle('dashed')
        else:
            self.__selected = False
            self.__item_handler.set_linewidth(1.0)
            self.__item_handler.set_linestyle('solid')

    def set_id(self, _id):
        """
        Set the database ID of the shape. **unsafe** to use outside letting
        database call this.

        :param int _id: Database primary key
        """
        self.__id = _id

    def set_notes(self, note):
        """
        Pass a string containing new notes to set the shape to

        :param str note: New note string
        """
        self.__note = note

    def set_plot(self, plot):
        """
        Manually set the new value of the internal plot variable. **unsafe**

        :param constants.Plot plot: Plot value
        """
        self.__plot = plot

    def set_hdf(self, fl):
        """
        Manually set the value of the internal file variable

        :param fl: HDF file path
        """
        self.__hdf = fl

    def set_tag(self, tag):
        """
        Set internal tag variable

        :param str tag:
        """
        self.__tag = tag

    def __can_draw(self):
        if not self.__coordinates:
            logger.warning('Attempting to ask to draw empty shape, probably just ' + \
                           'toggling a button after using free draw? See ticket #92')
            return -1
        b1 = tuple_to_nparray(self.__coordinates[-1])
        b2 = tuple_to_nparray(self.__coordinates[-2])
        for i in range(len(self.__coordinates) - 3):
            a1 = tuple_to_nparray(self.__coordinates[i])
            a2 = tuple_to_nparray(self.__coordinates[i + 1])
            if is_intersecting(a1, a2, b1, b2):
                logger.debug("Polygon labeled for draw")
                return i
        return -1

    def __str__(self):
        logger.debug('Stringing %s' % self.__tag)
        time_cords = [
            mpl.dates.num2date(x[0]).strftime('%H:%M:%S')
            for x in self.__coordinates
        ]
        altitude_cords = [x[1] for x in self.__coordinates]
        string = 'Name:\n\t%s\n' % self.__tag
        string += 'Time Scale:\n\t%s - %s\n' % (min(time_cords),
                                                max(time_cords))
        string += 'Latitude Scale:\n\t%s\n' % self.generate_lat_range()
        string += 'Altitude Scale:\n\t%.4f km - %.4f km\n' % (
            min(altitude_cords), max(altitude_cords))
        string += 'Color:\n\t%s\n' % self.__color
        if len(self.__attributes) > 0:
            string += 'Attributes:\n'
            for item in self.__attributes:
                string += '\t%s\n' % item
        if self.__note != '':
            string += 'Notes:\n\t%s' % self.__note
        return string
Ejemplo n.º 5
0
class Annotate(object):
    def __init__(self, tcc, viirs_aod, viirs_flags, orac_aod, orac_cost, ax,
                 plume_polygons, background_polygons, tail_locations):
        self.ax = ax
        self.tcc = tcc
        self.orac_aod = orac_aod
        self.orac_cost = orac_cost
        self.viirs_aod = viirs_aod
        self.viirs_flags = viirs_flags
        self.im = self.ax.imshow(self.orac_aod,
                                 interpolation='none',
                                 cmap='viridis',
                                 vmin=0,
                                 vmax=10)
        # if fires is not None:
        #    self.plot = self.ax.plot(fires[1], fires[0], 'r.')
        self.background_polygons = self._add_polygons_to_axis(
            background_polygons, 'Blues_r')
        self.plume_polygons = self._add_polygons_to_axis(
            plume_polygons, 'Reds_r')
        self.tail_locations = self._add_points_to_axis(tail_locations)

        # set up the point holders for the plume and background polygons
        self.plume_x = []
        self.plume_y = []
        self.background_x = []
        self.background_y = []
        self.tail_x = []
        self.tail_y = []

        # set varaible to check if we are using plume tracking on this plume or not
        self.tracking = True

        # set up the digitising patch
        self.plume_p = Circle((1, 1))
        self.background_p = Circle((1, 1))
        self.tail_p = Circle((1, 1))

        # set up the events
        self.ax.figure.canvas.mpl_connect('button_press_event', self.click)

        # set up check to see if we keep annotating
        self.do_annotation = True

        # set up default digitisation type as plume (0 is plume, 1 is background, 2 is vector)
        self.type = 0

        # set up radio buttons
        self.axcolor = 'lightgoldenrodyellow'

        self.rax_peat = plt.axes([0.01, 0.9, 0.1, 0.15],
                                 facecolor=self.axcolor)
        self.radio_peat = RadioButtons(self.rax_peat, ('Peat', 'Not'))
        self.radio_peat.on_clicked(self.peat_func)

        self.rax_digitise = plt.axes([0.01, 0.7, 0.1, 0.15],
                                     facecolor=self.axcolor)
        self.radio_disitise = RadioButtons(self.rax_digitise,
                                           ('Digitise', 'Stop'))
        self.radio_disitise.on_clicked(self.annotation_func)

        self.rax_discard = plt.axes([0.01, 0.5, 0.1, 0.15],
                                    facecolor=self.axcolor)
        self.radio_discard = RadioButtons(self.rax_discard,
                                          ('Keep', 'Discard'))
        self.radio_discard.on_clicked(self.discard_func)

        self.rax_type = plt.axes([0.01, 0.3, 0.1, 0.15],
                                 facecolor=self.axcolor)
        self.radio_type = RadioButtons(self.rax_type,
                                       ('Plume', 'Background', 'Tail'))
        self.radio_type.on_clicked(self.type_func)

        self.rax_image = plt.axes([0.01, 0.1, 0.1, 0.15],
                                  facecolor=self.axcolor)
        self.radio_image = RadioButtons(self.rax_image, self._radio_labels())
        self.radio_image.on_clicked(self.image_func)

        self.cax = plt.axes([0.925, 0.1, 0.025, 0.8])
        self.cbar = plt.colorbar(self.im, self.cax)

    def _add_polygons_to_axis(self, polygons, cmap):
        patches = [Polygon(verts, True) for verts in polygons]
        colors = [0] * len(patches)
        p = PatchCollection(patches, cmap=cmap, alpha=0.8)
        p.set_array(np.array(colors))
        return self.ax.add_collection(p)

    def _add_points_to_axis(self, points):
        for p in points:
            x1, y1 = p
            self.ax.plot(x1, y1, 'ko')

    def _radio_labels(self):
        labels = []
        # FCC and TCC always present
        labels.append('ORAC_AOD')
        labels.append('ORAC_COST')
        labels.append('VIIRS_AOD')
        labels.append('VIIRS_FLAGS')
        labels.append('TCC')
        return tuple(labels)

    def _radio_label_mapping(self):
        label_mapping = {}
        label_mapping['ORAC_AOD'] = self.orac_aod
        label_mapping['ORAC_COST'] = self.orac_cost
        label_mapping['VIIRS_AOD'] = self.viirs_aod
        label_mapping['VIIRS_FLAGS'] = self.viirs_flags
        label_mapping['TCC'] = self.tcc
        return label_mapping

    def peat_func(self, label):
        anno_dict = {'Peat': True, 'Not': False}
        self.tracking = anno_dict[label]

    def annotation_func(self, label):
        anno_dict = {'Digitise': True, 'Stop': False}
        self.do_annotation = anno_dict[label]

    def discard_func(self, label):
        keep_dict = {'Keep': False, 'Discard': True}
        if keep_dict[label] and self.type:
            self.plume_x = []
            self.plume_y = []
        elif keep_dict[label] and not self.type:
            self.background_x = []
            self.background_y = []

    def type_func(self, label):
        type_dict = {'Plume': 0, 'Background': 1, 'Tail': 2}
        self.type = type_dict[label]

    def image_func(self, label):

        image_dict = self._radio_label_mapping()
        im_data = image_dict[label]
        self.im.set_data(im_data)

        if label == "VIIRS_AOD":
            self.im.set_clim(vmax=10, vmin=0)
            self.im.set_cmap('viridis')
        if label == "VIIRS_FLAGS":
            self.im.set_clim(vmax=4, vmin=0)
            cmap = cm.get_cmap('Set1', 4)
            self.im.set_cmap(cmap)
        if label == "ORAC_AOD":
            self.im.set_clim(vmax=10, vmin=0)
            self.im.set_cmap('viridis')
        if label == "ORAC_COST":
            self.im.set_clim(vmax=5, vmin=0)
            self.im.set_cmap('plasma')
        plt.draw()

    def click(self, event):
        if event.button == 3:
            if self.type == 0:  # plume polygon
                self.plume_x.append(int(event.xdata))
                self.plume_y.append(int(event.ydata))
                if len(self.plume_x) < 3:
                    self.plume_p = Circle((event.xdata, event.ydata),
                                          radius=0.25,
                                          facecolor='red',
                                          edgecolor='black')
                    self.ax.add_patch(self.plume_p)
                else:
                    self.plume_p.remove()
                    self.plume_p = Polygon(zip(self.plume_x, self.plume_y),
                                           color='red',
                                           alpha=0.5)
                    plume_p = self.ax.add_patch(self.plume_p)
                self.ax.figure.canvas.draw()
            elif self.type == 1:  # background polygon
                self.background_x.append(int(event.xdata))
                self.background_y.append(int(event.ydata))
                if len(self.background_x) < 3:
                    self.background_p = Circle((event.xdata, event.ydata),
                                               radius=0.25,
                                               facecolor='blue',
                                               edgecolor='black')
                    self.ax.add_patch(self.background_p)
                else:
                    self.background_p.remove()
                    self.background_p = Polygon(zip(self.background_x,
                                                    self.background_y),
                                                color='blue',
                                                alpha=0.5)
                    background_p = self.ax.add_patch(self.background_p)
                self.ax.figure.canvas.draw()
            elif self.type == 2:  # plume tail point
                self.tail_x.append(int(event.xdata))
                self.tail_y.append(int(event.ydata))
                self.tail_p = Circle((event.xdata, event.ydata),
                                     radius=1,
                                     facecolor='black',
                                     edgecolor='blue')
                tail_p = self.ax.add_patch(self.tail_p)
                self.ax.figure.canvas.draw()
Ejemplo n.º 6
0
class BoxEditor(object):
    """ Box editor is to select area using rubber band sort of drawing rectangle.
    it uses matplotlib RectangleSelector under the hood """
    polygon = None

    def __init__(self, axes, canvas):
        """ initialises class and creates a rectangle selector """
        self.axes = axes
        self.canvas = canvas
        self.rectangle_selector = RectangleSelector(axes,
                                                    self.line_select_callback,
                                                    drawtype='box',
                                                    useblit=True,
                                                    button=[
                                                        1,
                                                    ],
                                                    minspanx=5,
                                                    minspany=5,
                                                    spancoords='pixels')

    def line_select_callback(self, eclick, erelease):
        """ callback to the rectangleselector """
        x1_val, y1_val = eclick.xdata, eclick.ydata
        x2_val, y2_val = erelease.xdata, erelease.ydata
        xy_values = np.array([
            [
                x1_val,
                y1_val,
            ],
            [
                x1_val,
                y2_val,
            ],
            [
                x2_val,
                y2_val,
            ],
            [
                x2_val,
                y1_val,
            ],
        ])
        self.reset_polygon()
        self.polygon = Polygon(xy_values, animated=False, alpha=polygon_alpha)
        self.axes.add_patch(self.polygon)
        self.canvas.draw()

    def enable(self):
        """ enable the box selector """
        self.rectangle_selector.set_active(True)

    def disable(self):
        """ disables or removes the box selector """
        self.reset_polygon()
        self.rectangle_selector.set_active(False)
        self.canvas.draw()

    def reset_polygon(self):
        """ resets rectangle polygon """
        if self.polygon != None:
            self.polygon.remove()
            self.polygon = None

    def reset(self):
        """ reset the Box selector """
        self.reset_polygon()
Ejemplo n.º 7
0
class PolygonEditor(object):
    '''
    This edits the polygons drawn on the map
    '''

    show_verts = True
    epsilon = 3  #threshold

    def __init__(self, axis, canvas):
        '''
        initialises the editable polygon object
        '''
        self.axis = axis
        self.polygon = None
        self.line = None
        self.xy_values = np.array([])
        self._ind = None
        self.background = None  #background copying

        self._callback_ids = list()
        self._callback_ids.append(
            canvas.mpl_connect('draw_event', self.draw_callback))
        self._callback_ids.append(
            canvas.mpl_connect('button_press_event',
                               self.button_press_callback))
        self._callback_ids.append(
            canvas.mpl_connect('button_release_event',
                               self.button_release_callback))
        self._callback_ids.append(
            canvas.mpl_connect('motion_notify_event',
                               self.motion_notify_callback))
        self.canvas = canvas

    def add_point(self, xval, yval):
        """ adds an new point to the selection list and redraws the selection tool"""
        if self.xy_values.shape[0] == 0:
            self.xy_values = np.array([
                (xval, yval),
            ])
        else:
            self.xy_values = np.concatenate((self.xy_values, [
                [xval, yval],
            ]),
                                            axis=0)
        self.refresh()

    def refresh(self):
        """ This method looks at the list of points available and depending on the number of
        points in the list creates a point or line or a polygon and draws them"""
        if self.xy_values.shape[0] == 0:  # No points available
            self.reset_line()
            self.reset_polygon()
        elif self.xy_values.shape[0] <= 2:  # point or line for 1 or 2 points
            xval, yval = zip(*self.xy_values)
            if self.line == None:
                self.line = Line2D(xval,
                                   yval,
                                   marker='o',
                                   markerfacecolor='r',
                                   animated=True)
                self.axis.add_line(self.line)
            else:
                self.line.set_data(zip(*self.xy_values))
            self.reset_polygon()
        else:  # more than 2 points if polygon is not created then creates one and draws
            if self.polygon == None:
                self.polygon = Polygon(self.xy_values,
                                       animated=True,
                                       alpha=polygon_alpha)
                self.polygon.add_callback(self.polygon_changed)
                self.axis.add_patch(self.polygon)
            else:
                self.polygon.xy = self.xy_values
            self.line.set_data(zip(*self.xy_values))
        self.draw_callback(None)
        self.canvas.draw()

    def reset_line(self):
        """ resets the line i.e removes the line from the axes and resets to None """
        if self.line != None:
            self.line.remove()
            self.line = None

    def reset_polygon(self):
        """ resets the polygon ie. removes the polygon from the axis and reset to None """
        if self.polygon != None:
            self.polygon.remove()
            self.polygon = None

    def draw_line(self):
        """ draws the line if available """
        if self.line != None:
            self.axis.draw_artist(self.line)

    def draw_polygon(self):
        """ draws polygon if available"""
        if self.polygon != None:
            self.axis.draw_artist(self.polygon)

    def disable(self):
        """ disables the events and the selection """
        self.set_visibility(False)
        for callback_id in self._callback_ids:
            self.canvas.mpl_disconnect(callback_id)
        self.canvas = None

    def enable(self):
        """ enables the selection """
        self.set_visibility(True)

    def set_visibility(self, status):
        """ sets the visibility of the selection object """
        if self.polygon != None:
            self.polygon.set_visible(status)
        if self.line != None:
            self.line.set_visible(status)
        self.canvas.blit(self.axis.bbox)

    def draw_callback(self, dummy_event):
        """ this method draws the selection object """
        self.background = self.canvas.copy_from_bbox(self.axis.bbox)
        self.draw_polygon()
        self.draw_line()
        self.canvas.blit(self.axis.bbox)

    def polygon_changed(self, poly):
        """ redraws the polygon """
        vis = self.line.get_visible()
        Artist.update_from(self.line, poly)
        self.line.set_visible(vis)

    def get_index_under_point(self, event):
        """ gets the index of the point under the event (mouse click) """
        if self.xy_values.shape[0] == 0:
            return None
        xy_values = self.xy_values
        xt_values, yt_values = xy_values[:, 0], xy_values[:, 1]
        dist = np.sqrt((xt_values - event.xdata)**2 +
                       (yt_values - event.ydata)**2)
        indseq = np.nonzero(np.equal(dist, np.amin(dist)))[0]
        ind = indseq[0]
        if dist[ind] >= self.epsilon:
            ind = None
        return ind

    def button_press_callback(self, event):
        """ callback to mouse press event """
        if not self.show_verts:
            return
        if event.inaxes == None:
            return
        if event.button != 1:
            return
        self._ind = self.get_index_under_point(event)
        if self._ind == None:
            self.insert_datapoint(event)

    def button_release_callback(self, event):
        """ callback to mouse release event """
        if not self.show_verts:
            return
        if event.button == 2:
            self.insert_datapoint(event)
            return
        if event.button == 3:
            self.delete_datapoint(event)
            return
        if event.button != 1:
            return
        self._ind = None

    def insert_datapoint(self, event):
        """ inserts a new data point between the segment that is closest in polygon """
        if self.xy_values.shape[0] <= 2:
            self.add_point(event.xdata, event.ydata)
        else:
            event_point = event.xdata, event.ydata
            prev_d = dist_point_to_segment(event_point, self.xy_values[0],
                                           self.xy_values[-1])
            prev_i = len(self.xy_values)
            for i in range(len(self.xy_values) - 1):
                seg_start = self.xy_values[i]
                seg_end = self.xy_values[i + 1]
                dist_p_s = dist_point_to_segment(event_point, seg_start,
                                                 seg_end)
                if dist_p_s < prev_d:
                    prev_i = i
                    prev_d = dist_p_s
            self.xy_values = np.array(
                list(self.xy_values[:prev_i + 1]) +
                [(event.xdata, event.ydata)] +
                list(self.xy_values[prev_i + 1:]))
            self.refresh()

    def delete_datapoint(self, event):
        """ deletes the data point under the point in event """
        ind = self.get_index_under_point(event)
        if ind is not None:
            self.xy_values = np.array(
                [tup for i, tup in enumerate(self.xy_values) if i != ind])
            self.refresh()
        self.canvas.draw()

    def motion_notify_callback(self, event):
        """ callback for the mouse motion with button press.
        this is to move the edge points of the polygon"""
        if not self.show_verts:
            return
        if self._ind is None:
            return
        if event.inaxes is None:
            return
        if event.button != 1:
            return
        xval, yval = event.xdata, event.ydata

        self.xy_values[self._ind] = xval, yval
        self.refresh()

        self.canvas.restore_region(self.background)
        self.axis.draw_artist(self.polygon)
        self.axis.draw_artist(self.line)
        self.canvas.blit(self.axis.bbox)

    def reset(self):
        """ resets the points in the selection deleting the line and polygon"""
        self.xy_values = np.array([])
        self.reset_line()
        self.reset_polygon()
Ejemplo n.º 8
0
class PlotMap:
    '''  class contains method to plot the pss data, swath boundary, map and
         active receivers
    '''
    def __init__(self, start_date, maptype=None, swaths_selected=None):
        self.date = start_date
        self.maptype = maptype
        self.swaths_selected = swaths_selected
        self.pss_dataframes = [None, None, None]
        self.init_pss_dataframes()

        self.fig, self.ax = self.setup_map(figsize=FIGSIZE)

        connect = self.fig.canvas.mpl_connect
        connect('button_press_event', self.on_click)
        connect('key_press_event', self.on_key)
        connect('resize_event', self.on_resize)

        self.resize_timer = self.fig.canvas.new_timer(interval=250)
        self.resize_timer.add_callback(self.blit)

        # start event loop
        self.artists_on_stage = False
        self.background = None
        self.show(block=False)
        plt.pause(0.1)
        self.blit()

    def setup_map(self, figsize):
        ''' setup the map and background '''
        fig, ax = plt.subplots(figsize=figsize)

        # plot the swath boundary
        _, _, _, swaths_bnd_gpd = GeoData().filter_geo_data_by_swaths(
            swaths_selected=self.swaths_selected,
            swaths_only=True,
            source_boundary=True,
        )
        swaths_bnd_gpd = self.convert_to_map(swaths_bnd_gpd)
        swaths_bnd_gpd.plot(ax=ax, facecolor='none', edgecolor=EDGECOLOR)
        # obtain the extent of the data based on swaths_bnd_gdf
        extent_map = ax.axis()
        logger.info(f'extent data swaths: {extent_map}')

        # plot the selected basemap background
        if self.maptype == maptypes[0]:
            add_basemap_local(ax)

        elif self.maptype == maptypes[1]:
            add_basemap_osm(ax)

        else:
            pass  # no basemap background

        # restore original x/y limits
        ax.axis(extent_map)

        return fig, ax

    def setup_artists(self):
        date_text_x, date_text_y = 0.80, 0.95
        self.vib_artists = {}
        for force_level, force_attr in force_attrs.items():
            self.vib_artists[force_level] = self.ax.scatter(
                [
                    0,
                ],
                [
                    0,
                ],
                s=MARKERSIZE,
                marker='o',
                facecolor=force_attr[0],
            )
        self.date_artist = self.ax.text(
            date_text_x,
            date_text_y,
            '',
            transform=self.ax.transAxes,
        )
        self.actrecv_artist = Polygon(np.array([[0, 0]]),
                                      closed=True,
                                      edgecolor='red',
                                      fill=False)
        self.ax.add_patch(self.actrecv_artist)
        self.cp_artist = Circle((0, 0), radius=SOURCE_CENTER, fc=SOURCE_COLOR)
        self.ax.add_patch(self.cp_artist)
        self.artists_on_stage = True

    def remove_artists(self):
        if self.artists_on_stage:
            for _, vib_artist in self.vib_artists.items():
                vib_artist.remove()
            self.date_artist.remove()
            self.actrecv_artist.remove()
            self.cp_artist.remove()
            self.artists_on_stage = False

        else:
            pass

    def init_pss_dataframes(self):
        dates = [self.date - timedelta(1), self.date, self.date + timedelta(1)]
        for i, _date in enumerate(dates):
            _pss_gpd = get_vps_force_for_date_range(_date, _date, MEDIUM_FORCE,
                                                    HIGH_FORCE)
            _pss_gpd = self.convert_to_map(_pss_gpd)
            self.pss_dataframes[i] = _pss_gpd

    def update_right_pss_dataframes(self):
        self.pss_dataframes[0] = self.pss_dataframes[1]
        self.pss_dataframes[1] = self.pss_dataframes[2]
        _date = self.date + timedelta(1)
        _pss_gpd = get_vps_force_for_date_range(_date, _date, MEDIUM_FORCE,
                                                HIGH_FORCE)
        _pss_gpd = self.convert_to_map(_pss_gpd)
        self.pss_dataframes[2] = _pss_gpd

    def update_left_pss_dataframes(self):
        self.pss_dataframes[2] = self.pss_dataframes[1]
        self.pss_dataframes[1] = self.pss_dataframes[0]
        _date = self.date - timedelta(1)
        _pss_gpd = get_vps_force_for_date_range(_date, _date, MEDIUM_FORCE,
                                                HIGH_FORCE)
        _pss_gpd = self.convert_to_map(_pss_gpd)
        self.pss_dataframes[0] = _pss_gpd

    def plot_pss_data(self, index):
        '''  plot pss force data in three ranges LOW, MEDIUM, HIGH '''
        vib_pss_gpd = self.pss_dataframes[index]
        self.date_artist.set_text(self.date.strftime("%d %m %y"))

        # plot the VP grouped by force_level
        if not vib_pss_gpd.empty:
            for force_level, vib_pss in vib_pss_gpd.groupby('force_level'):
                if pts := [(xy.x, xy.y)
                           for xy in vib_pss['geometry'].to_list()]:
                    self.vib_artists[force_level].set_offsets(pts)

                else:
                    self.vib_artists[force_level].set_offsets([[0, 0]])

        else:
Ejemplo n.º 9
0
class MeasureInPlot(object):
    """ Class to measure signal in a plot. """
    def __init__(self, ax, xUnit='', yUnit=''):
        self.ax = ax
        self.fig = self.ax.get_figure()
        self.lx = ax.axhline(color='k')  # the horiz line
        self.ly = ax.axvline(color='k')  # the vert line
        self.start_x = []
        self.start_y = []
        self.status = "set_start"
        self.xUnit = xUnit
        self.yUnit = yUnit
        self.cid_move = self.fig.canvas.mpl_connect('motion_notify_event',
                                                    self.set_start_point)
        self.cid_click = self.fig.canvas.mpl_connect('button_press_event',
                                                     self.onclick)

        props = dict(boxstyle='round', facecolor='wheat')  #, alpha=0.8)
        self.txt = ax.text(0.05,
                           0.95,
                           '',
                           transform=self.fig.transFigure,
                           verticalalignment='top',
                           horizontalalignment='left',
                           multialignment="left",
                           bbox=props,
                           fontname='monospace')

        plt.ginput(3)  # not nice but could not think of better way

    def set_start_point(self, event):

        if not event.inaxes:
            return

        x, y = event.xdata, event.ydata
        # update the line positions
        self.lx.set_ydata(y)
        self.ly.set_xdata(x)

        self.txt.set_text('Click to set start point:\nx={}, y={}'.format(
            num2string(x), num2string(y)))
        plt.draw()

    def set_end_point(self, event):
        if not event.inaxes:
            return
        yMin = min(self.start_y, event.ydata)
        yMax = max(self.start_y, event.ydata)
        xMin = min(self.start_x, event.xdata)
        xMax = max(self.start_x, event.xdata)
        polygonData = np.array([[xMin, yMin], [xMin, yMax], [xMax, yMax],
                                [xMax, yMin], [xMin, yMin]])
        self.span_polygon.set_xy(polygonData)
        n2s = num2string
        if self.xUnit == "Hz":
            inverse_xUnit = "s"
        elif self.xUnit == "s":
            inverse_xUnit = "Hz"
        elif self.xUnit == "":
            inverse_xUnit = ""
        else:
            inverse_xUnit = "1/" + self.xUnit

    #  n2s =  lambda x: num2string(x, numberFormat=":3.2f")
    #  def n2s(num): return num2string(num, numberFormat=":>3.2f")
        n2s = num2string
        infoText = 'Start : x={:>7}{}, y={:>7}{}\n'.format(
            n2s(self.start_x), self.xUnit, n2s(self.start_y), self.yUnit)
        infoText += 'End   : x={:>7}{}, y={:>7}{}\n'.format(
            n2s(event.xdata), self.xUnit, n2s(event.ydata), self.yUnit)
        infoText += 'Diff  : x={:>7}{}, y={:>7}{}\n'.format(
            n2s(event.xdata - self.start_x), self.xUnit,
            n2s(event.ydata - self.start_y), self.yUnit)
        infoText += '1 / delta_x = {}{}'.format(
            n2s(1 / (event.xdata - self.start_x)), inverse_xUnit)
        self.txt.set_text(infoText)
        plt.draw()

    def onclick(self, event):
        #print('button=%d, x=%d, y=%d, xdata=%f, ydata=%f' %  (event.button, event.x, event.y, event.xdata, event.ydata))
        if event.button == 1:
            if self.status == 'set_start':
                # close crosshair
                self.lx.set_visible(False)
                self.ly.set_visible(False)

                self.start_x = event.xdata
                self.start_y = event.ydata
                polygonData = np.array([[self.start_x, self.start_y],
                                        [self.start_x, self.start_y],
                                        [self.start_x, self.start_y],
                                        [self.start_x, self.start_y],
                                        [self.start_x, self.start_y]])
                self.span_polygon = Polygon(
                    polygonData, facecolor='0.75',
                    alpha=0.5)  #, transform=self.ax.transData)
                self.ax.add_patch(self.span_polygon)
                self.status = 'set_end'
                self.fig.canvas.mpl_disconnect(self.cid_move)
                self.cid_move = self.fig.canvas.mpl_connect(
                    'motion_notify_event', self.set_end_point)
            elif self.status == 'set_end':
                #plt.axhspan(yMin, yMax, xmin=xMin, xmax=xMax, facecolor='0.5', alpha=0.5)
                self.fig.canvas.mpl_disconnect(self.cid_move)
                self.status = 'finished'
            elif self.status == 'finished':
                self.fig.canvas.mpl_disconnect(self.cid_click)
                self.close()

    def close(self):
        self.span_polygon.remove()
        self.lx.remove()
        self.ly.remove()
        self.txt.remove()
Ejemplo n.º 10
0
class BranchMaskCreator(MaskCreator):
    default_radius = 5.

    @MaskCreator.enabled.setter
    def enabled(self, e):
        """Extend the active setter of MaskCreator to also remove any artists if deactivated"""
        # call base class property setter
        MaskCreator.enabled.fset(self, e)
        # handle own derived stuff
        if self.artist is not None:
            self.artist.remove()
            self.artist = None
            self.x, self.y, self.r = [], [], []
            self.update()

    def __init__(self, axes, canvas, update, notify, enabled=False):
        """
            Arguments:
                axes, the axes where the interactive creation takes place
                canvas, the figure canvas, required to connec to signals
                update, a callable which will be called after adding a pixel to the current mask.
                notify, a callable that will get evoked with the coordinates of all pixels of a finished mask.
                enabled, should mask creation be enabled from the begininig (default False)
        """
        self.artist = None
        # container for x,y and radius values
        self.x, self.y, self.r = [], [], []
        super(BranchMaskCreator, self).__init__(axes=axes,
                                                canvas=canvas,
                                                update=update,
                                                notify=notify,
                                                enabled=enabled)

    def onclick(self, event):
        self.x.append(event.xdata)
        self.y.append(event.ydata)
        # reuse last radius for consecutive segments
        if len(self.r) > 0:
            self.r.append(self.r[-1])
        else:
            self.r.append(BranchMaskCreator.default_radius)

        self.__update_artist()

        self.update()

    def __update_artist(self):
        # check if this is the first point of a branch
        if self.artist is None:
            self.artist = Circle([self.x[0], self.y[0]],
                                 radius=self.r[0],
                                 fill=False,
                                 lw=2,
                                 color='red')
            self.axes.add_artist(self.artist)
        elif len(self.x) == 0:
            self.artist.remove()
            self.artist = None
        elif len(self.x) == 1:
            self.artist.remove()
            self.artist = Circle([self.x[0], self.y[0]],
                                 radius=self.r[0],
                                 fill=False,
                                 lw=2,
                                 color='red')
            self.axes.add_artist(self.artist)
        # change from circle to polygon if more than 1 points are available
        elif len(self.x) == 2:
            self.artist.remove()
            branch = Branch(x=self.x,
                            y=self.y,
                            z=[0 for i in self.x],
                            r=self.r)
            self.artist = Polygon(branch.outline,
                                  fill=False,
                                  color='red',
                                  lw=2)
            self.axes.add_artist(self.artist)
        else:
            assert (len(self.x) > 2)
            branch = Branch(x=self.x,
                            y=self.y,
                            z=[0 for i in self.x],
                            r=self.r)
            self.artist.set_xy(branch.outline)

    def onkey(self, event):
        if self.artist is not None:
            if event.key == '+':
                self.r[-1] = self.r[-1] + 1
                self.__update_artist()
                self.update()
            elif event.key == '-':
                self.r[-1] = self.r[-1] - 1
                self.__update_artist()
                self.update()
            elif event.key == 'z':
                print event
                print dir(event)
                self.r = self.r[:-1]
                self.x = self.x[:-1]
                self.y = self.y[:-1]
                self.__update_artist()
                self.update()
            elif event.key == 'enter':
                self.artist.remove()
                self.update()
                self.artist = None
                if len(self.x) == 1:
                    # shift by 0.5 to compensate for pixel offset in imshow
                    mask = CircleMask(
                        center=[self.x[0] + 0.5, self.y[0] + 0.5],
                        radius=self.r[0])
                else:
                    import numpy
                    dtype = [('x', float), ('y', float), ('z', float),
                             ('radius', float)]
                    x = numpy.array(self.x) + 0.5
                    y = numpy.array(self.y) + 0.5
                    z = [0 for i in self.x]
                    r = self.r
                    data = numpy.rec.fromarrays([x, y, z, r], dtype=dtype)
                    # shift by 0.5 to compensate for pixel offset in imshow
                    mask = BranchMask(data=data)
                self.x, self.y, self.r = [], [], []
                self.notify(mask)
                self.enabled = False
Ejemplo n.º 11
0
class Polygon(Element):
    """Represent a polygon in longitude, latitude and provide
    function and methods to manipulate it and display it on a map.
    """
    def __init__(self, parent=None, lon=[0], lat=[0], gain=0.0):
        """Constructor of Polygon class.
        """
        self._parent = parent
        self._earthmap = self._parent._earth_map
        # list of station representing vertex of a polygon
        self._longitude = lon
        self._latitude = lat
        self._gain = gain
        # plot conf
        self._patch = None
        self._linestyle = 'solid'
        self._linewidth = cst.BOLDNESS['light']
        self._color = 'k'

    # end of constructor

    def longitude(self):
        """Return list of longitude of polygon vertex.
        """
        return self._longitude

    # end of function longitude

    def latitude(self):
        """Return list of latitude of polygon vertex.
        """
        return self._latitude

    # end of function latitude

    def gain(self):
        return self._gain

    def path(self):
        path = []
        for i in range(len(self.longitude)):
            path.append((self.longitude[i], self.latitude[i]))
        return Path(path)

    def projected(self, map: Basemap):
        """Return list of stations position in the given Earth projection.
        """
        return map(self._longitude, self._latitude)

    # end of function projected

    def appendvertex(self, lon, lat):
        """Add a station at the end of the list.
        """
        self._longitude.append(lon)
        self._latitude.append(lat)

    # end of method appendvertex

    def plot(self):
        # get coordinates in the Earth projection
        xvec, yvec = self.projected(self._earthmap)
        # create patch with coordinates
        self._patch = MplPolygon(xy=np.array([xvec, yvec]).T,
                                 closed=True,
                                 linestyle=self._linestyle,
                                 linewidth=self._linewidth,
                                 fill=False,
                                 color=self._color)
        # add patch to plot
        self._parent.get_axes().add_patch(self._patch)
        # refresh plot
        self._parent.draw()

    # end of method plot

    def clearplot(self):
        self._patch.remove()
        self._patch = None
        self._parent.draw()

    def len(self):
        """Return number of vertex.
        """
        return len(self._longitude)

    def configure(self, config=None):
        self._longitude = self.set(config, 'longitude')
        self._latitude = self.set(config, 'latitude')
        self._gain = self.set(config, 'gain')
Ejemplo n.º 12
0
def display_instances(image, boxes, masks, class_ids, class_names, ax,
                      scores=None, title="",
                      figsize=(16, 16)):
    """
    boxes: [num_instance, (y1, x1, y2, x2, class_id)] in image coordinates.
    masks: [height, width, num_instances]
    class_ids: [num_instances]
    class_names: list of class names of the dataset
    scores: (optional) confidence scores for each box
    figsize: (optional) the size of the image.
    """

    # fig = plt.figure()
    # Number of instances
    N = boxes.shape[0]
    if not N:
        print("\n*** No instances to display *** \n")
    else:
        assert boxes.shape[0] == masks.shape[-1] == class_ids.shape[0]

    # if not ax:
    #     _, ax = plt.subplots(1, figsize=figsize)

    # Generate random colors
    colors = random_colors(N)

    # Show area outside image boundaries.
    height, width = image.shape[:2]
    ax.set_ylim(height + 10, -10)
    ax.set_xlim(-10, width + 10)
    ax.axis('off')
    p = None
    # ax.set_title(title)
    # ax.set_title("aaaaaaa")

    # masked_image = image.astype(np.uint32).copy()
    masked_image = copy.deepcopy(image)
    camera_image = copy.deepcopy(image)
    camera_image = img_as_ubyte(camera_image)
    # masked_image = image.astype(np.uint32).copy()
    # cv2.imshow("camera", camera_image)

    for i in range(N):
        color = colors[i]

        # Bounding box
        if not np.any(boxes[i]):
            # Skip this instance. Has no bbox. Likely lost in image cropping.
            continue
        y1, x1, y2, x2 = boxes[i]
        p = patches.Rectangle((x1, y1), x2 - x1, y2 - y1, linewidth=2,
                              alpha=0.7, linestyle="dashed",
                              edgecolor=color, facecolor='none')
        ax.add_patch(p)

        # Label
        class_id = class_ids[i]
        score = scores[i] if scores is not None else None
        label = class_names[class_id]
        x = random.randint(x1, (x1 + x2) // 2)
        caption = "{} {:.3f}".format(label, score) if score else label
        ax.text(x1, y1 + 8, caption,
                color='w', size=11, backgroundcolor="none")

        # Mask
        mask = masks[:, :, i]
        masked_image = apply_mask(masked_image, mask, color)

        # Mask Polygon
        # Pad to ensure proper polygons for masks that touch image edges.
        padded_mask = np.zeros(
            (mask.shape[0] + 2, mask.shape[1] + 2), dtype=np.uint8)
        padded_mask[1:-1, 1:-1] = mask
        contours = find_contours(padded_mask, 0.5)
        for verts in contours:
            # Subtract the padding and flip (y, x) to (x, y)
            verts = np.fliplr(verts) - 1
            p = Polygon(verts, facecolor="none", edgecolor=color)
            ax.add_patch(p)

    plt.tight_layout()
    ax.imshow(masked_image.astype(np.uint8))
    # ax.imshow(masked_image.astype(np.uint8),animated=True)
    # ani = animation.FuncAnimation(ax, updateFrame, fargs=(capture, image_plt),
    #                               interval=0, blit=True)
    plt.pause(.01)
    # plt.hold(False)
    # plt.show()
    if p is not None:
        p.remove()
    ax.clear()
    return masked_image.astype(np.uint8)