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
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()
class PolygonInteractor(QObject): """ An polygon editor. Key-bindings 't' toggle vertex markers on and off. When vertex markers are on, you can move them, delete them 'd' delete the vertex under point 'i' insert a vertex at point. You must be within epsilon of the line connecting two existing vertices """ showverts = True epsilon = 5 # max pixel distance to count as a vertex hit mySignal = pyqtSignal(str) modSignal = pyqtSignal(str) def __init__(self, ax, verts): super().__init__() from matplotlib.patches import Polygon from matplotlib.lines import Line2D # from matplotlib.artist import Artist self.ax = ax self.type = 'Polygon' self.poly = Polygon(list(verts), animated=True, fill=False, closed=True, color='lime') self.ax.add_patch(self.poly) self.canvas = self.poly.figure.canvas x, y = zip(*self.poly.xy) self.line = Line2D(x, y, marker='o', linestyle=None, linewidth=0., markerfacecolor='g', animated=True) self.ax.add_line(self.line) self.cid = self.poly.add_callback(self.poly_changed) self._ind = None # the active vert self.connect() self.aperture = self.poly def connect(self): self.cid_draw = self.canvas.mpl_connect('draw_event', self.draw_callback) self.cid_press = self.canvas.mpl_connect('button_press_event', self.button_press_callback) self.cid_key = self.canvas.mpl_connect('key_press_event', self.key_press_callback) self.cid_release = self.canvas.mpl_connect('button_release_event', self.button_release_callback) self.cid_motion = self.canvas.mpl_connect('motion_notify_event', self.motion_notify_callback) self.canvas.draw_idle() def disconnect(self): self.canvas.mpl_disconnect(self.cid_draw) self.canvas.mpl_disconnect(self.cid_press) self.canvas.mpl_disconnect(self.cid_key) self.canvas.mpl_disconnect(self.cid_release) self.canvas.mpl_disconnect(self.cid_motion) self.poly.remove() self.line.remove() self.canvas.draw_idle() self.aperture = None def draw_callback(self, event): self.background = self.canvas.copy_from_bbox(self.ax.bbox) self.ax.draw_artist(self.poly) self.ax.draw_artist(self.line) #self.canvas.blit(self.ax.bbox) #self.canvas.udpate() #self.canvas.flush_events() def poly_changed(self, poly): 'this method is called whenever the polygon object is called' # only copy the artist props to the line (except visibility) vis = self.line.get_visible() Artist.update_from(self.line, poly) self.line.set_visible(vis) # don't use the poly visibility state def get_ind_under_point(self, event): 'get the index of the vertex under point if within epsilon tolerance' # display coords xy = np.asarray(self.poly.xy) xyt = self.poly.get_transform().transform(xy) xt, yt = xyt[:, 0], xyt[:, 1] d = np.hypot(xt - event.x, yt - event.y) indseq, = np.nonzero(d == d.min()) ind = indseq[0] if d[ind] >= self.epsilon: ind = None return ind def button_press_callback(self, event): 'whenever a mouse button is pressed' if not self.showverts: return if event.inaxes is None: return if event.button != 1: return self._ind = self.get_ind_under_point(event) def button_release_callback(self, event): 'whenever a mouse button is released' if not self.showverts: return if event.button != 1: return self._ind = None def key_press_callback(self, event): 'whenever a key is pressed' if not event.inaxes: return if event.key == 't': self.showverts = not self.showverts self.line.set_visible(self.showverts) if not self.showverts: self._ind = None elif event.key == 'd': ind = self.get_ind_under_point(event) if ind is not None: if len(self.poly.xy) < 5: # the minimum polygon has 4 points since the 1st is repeated as final # Delete polygon #self.disconnect() #self.poly = None #self.line = None self.mySignal.emit('polygon deleted') else: self.poly.xy = [tup for i, tup in enumerate(self.poly.xy) if i != ind] self.line.set_data(zip(*self.poly.xy)) self.mySignal.emit('one vertex of polygon removed') elif event.key == 'i': xys = self.poly.get_transform().transform(self.poly.xy) p = event.x, event.y # display coords for i in range(len(xys) - 1): s0 = xys[i] s1 = xys[i + 1] d = dist_point_to_segment(p, s0, s1) if d <= self.epsilon: self.poly.xy = np.array( list(self.poly.xy[:i+1]) + [(event.xdata, event.ydata)] + list(self.poly.xy[i+1:])) self.line.set_data(zip(*self.poly.xy)) break self.canvas.draw_idle() def motion_notify_callback(self, event): 'on mouse movement' if not self.showverts: return if self._ind is None: return if event.inaxes is None: return if event.button != 1: return x, y = event.xdata, event.ydata self.poly.xy[self._ind] = x, y if self._ind == 0: self.poly.xy[-1] = x, y elif self._ind == len(self.poly.xy) - 1: self.poly.xy[0] = x, y self.updateMarkers() self.canvas.restore_region(self.background) self.ax.draw_artist(self.poly) self.ax.draw_artist(self.line) self.canvas.update() self.canvas.flush_events() # Notify callback self.modSignal.emit('polygon modified') def updateMarkers(self): self.line.set_data(zip(*self.poly.xy))
class 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
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()
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()
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()
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:
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()
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
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')
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)