Beispiel #1
0
class MplPathROI(MplPolygonalROI):
    """
    Matplotlib ROI for path selections

    Parameters
    ----------
    axes : :class:`matplotlib.axes.Axes`
        The Matplotlib axes to draw to.
    """

    _roi_cls = Path

    def __init__(self, axes, roi=None):

        super(MplPolygonalROI, self).__init__(axes)

        self.plot_opts = {
            'edgecolor': PATCH_COLOR,
            'facecolor': PATCH_COLOR,
            'alpha': 0.3
        }

        self._patch = None

    def start_selection(self, event):

        if self._patch is not None:
            self._patch.remove()
            self._patch = None

        self._background_cache = None
        self._axes.figure.canvas.draw()

        super(MplPathROI, self).start_selection(event)

    def _sync_patch(self):

        if self._patch is not None:
            self._patch.remove()
            self._patch = None

        if self._roi.defined():
            x, y = self._roi.to_polygon()
            p = MplPath(np.column_stack((x, y)))
            self._patch = PathPatch(p, transform=self._axes.transData)
            self._patch.set_visible(True)
            self._patch.set(**self.plot_opts)
            self._axes.add_artist(self._patch)

    def finalize_selection(self, event):
        self._mid_selection = False
        if self._patch is not None:
            self._patch.remove()
            self._patch = None
        self._draw()
Beispiel #2
0
class VerticalMarker(QObject):

    moved = Signal(float)

    def __init__(self, canvas, x, color):
        super(VerticalMarker, self).__init__()
        self.x = x
        self.ax = canvas.figure.get_axes()[0]
        y0, y1 = self.ax.get_ylim()
        path = Path([(x, y0), (x, y1)], [Path.MOVETO, Path.LINETO])
        self.patch = PathPatch(path,
                               facecolor='None',
                               edgecolor=color,
                               picker=5,
                               linewidth=2.0,
                               animated=True)
        self.ax.add_patch(self.patch)
        self.is_moving = False

    def remove(self):
        self.patch.remove()

    def redraw(self):
        y0, y1 = self.ax.get_ylim()
        vertices = self.patch.get_path().vertices
        vertices[0] = self.x, y0
        vertices[1] = self.x, y1
        self.ax.draw_artist(self.patch)

    def get_x_in_pixels(self):
        x_pixels, _ = self.patch.get_transform().transform((self.x, 0))
        return x_pixels

    def is_above(self, x):
        return np.abs(self.get_x_in_pixels() - x) < 3

    def on_click(self, x):
        if self.is_above(x):
            self.is_moving = True

    def stop(self):
        self.is_moving = False

    def mouse_move(self, xd):
        if self.is_moving and xd is not None:
            self.x = xd
            self.moved.emit(xd)

    def should_override_cursor(self, x):
        return self.is_moving or self.is_above(x)
Beispiel #3
0
class VerticalMarker(QObject):
    """
    An interactive marker displayed as a vertical line.
    """

    x_moved = Signal(float)

    def __init__(self,
                 canvas,
                 color,
                 x,
                 y0=None,
                 y1=None,
                 line_width=1.0,
                 picker_width=5,
                 line_style='-'):
        """
        Init the marker.
        :param canvas: A MPL canvas.
        :param color: An MPL colour value
        :param x: The x coordinate (data) of the marker.
        :param y0: The y coordinate (data) of the bottom end of the marker. Default is None which means dynamically
            set it to the current lowest y value displayed.
        :param y1: The y coordinate (data) of the top end of the marker. Default is None which means dynamically
            set it to the current highest y value displayed.
        :param line_width: The line width (pixels).
        :param picker_width: The picker sensitivity (pixels).
        :param line_style: An MPL line style value.
        """
        super(VerticalMarker, self).__init__()
        self.ax = canvas.figure.get_axes()[0]
        self.x = x
        self.y0 = y0
        self.y1 = y1
        y0, y1 = self._get_y0_y1()
        path = Path([(x, y0), (x, y1)], [Path.MOVETO, Path.LINETO])
        self.patch = PathPatch(path,
                               facecolor='None',
                               edgecolor=color,
                               picker=picker_width,
                               linewidth=line_width,
                               linestyle=line_style,
                               animated=True)
        self.ax.add_patch(self.patch)
        self.is_moving = False

    def _get_y0_y1(self):
        """
        Calculate the current y coordinates of the line ends.
        :return: Tuple y0, y1.
        """
        if self.y0 is None or self.y1 is None:
            y0, y1 = self.ax.get_ylim()
        if self.y0 is not None:
            y0 = self.y0
        if self.y1 is not None:
            y1 = self.y1
        return y0, y1

    def remove(self):
        """
        Remove this marker from the canvas.
        """
        self.patch.remove()

    def redraw(self):
        """
        Redraw this marker.
        """
        y0, y1 = self._get_y0_y1()
        vertices = self.patch.get_path().vertices
        vertices[0] = self.x, y0
        vertices[1] = self.x, y1
        self.ax.draw_artist(self.patch)

    def get_x_in_pixels(self):
        """
        Get the x coordinate in screen pixels.
        """
        x_pixels, _ = self.patch.get_transform().transform((self.x, 0))
        return x_pixels

    def is_above(self, x, y):
        """
        Check if a mouse positioned at (x, y) is over this marker.
        :param x: An x mouse coordinate.
        :param y: An y mouse coordinate.
        :return: True or False.
        """
        x_pixels, y_pixels = self.patch.get_transform().transform((x, y))
        if self.y0 is not None and y < self.y0:
            return False
        if self.y1 is not None and y > self.y1:
            return False
        return abs(self.get_x_in_pixels() - x_pixels) < 3

    def mouse_move_start(self, x, y):
        """
        Start moving this marker if (x, y) is above it. Ignore otherwise.
        :param x: An x mouse coordinate.
        :param y: An y mouse coordinate.
        """
        self.is_moving = self.is_above(x, y)

    def mouse_move_stop(self):
        """
        Stop moving.
        """
        self.is_moving = False

    def mouse_move(self, x, y=None):
        """
        Move this marker to a new position if movement had been started earlier by a call to mouse_move_start(x, y)
        :param x: An x mouse coordinate.
        :param y: An y mouse coordinate.
        :return: True if moved or False if stayed at the old position.
        """
        if self.is_moving and x is not None:
            self.x = x
            self.x_moved.emit(x)
            return True
        return False

    def get_cursor_at_y(self, y):
        """
        Get an override cursor for an y coordinate given that the x == self.x
        :param y: A y coordinate.
        :return: QCursor or None.
        """
        return QCursor(Qt.SizeHorCursor)

    def override_cursor(self, x, y):
        """
        Get the override cursor for mouse position (x, y)
        :param x: An x mouse coordinate.
        :param y: An y mouse coordinate.
        :return: QCursor or None.
        """
        if self.y0 is not None and y < self.y0:
            return None
        if self.y1 is not None and y > self.y1:
            return None
        if self.is_moving or self.is_above(x, y):
            return self.get_cursor_at_y(y)
        return None
Beispiel #4
0
class HorizontalMarker(QObject):
    """
    An interactive marker displayed as a horizontal line.
    """

    y_moved = Signal(float)

    def __init__(self,
                 canvas,
                 color,
                 y,
                 x0=None,
                 x1=None,
                 line_width=1.0,
                 picker_width=5,
                 line_style='-',
                 move_cursor=None,
                 axis=None):
        """
        Init the marker.
        :param canvas: A MPL canvas.
        :param color: An MPL colour value
        :param y: The y coordinate (data) of the marker.
        :param x0: The x coordinate (data) of the left end of the marker. Default is None which means dynamically
            set it to the current maximum x value displayed.
        :param x1: The x coordinate (data) of the right end of the marker. Default is None which means dynamically
            set it to the current minimum x value displayed.
        :param line_width: The line width (pixels).
        :param picker_width: The picker sensitivity (pixels).
        :param line_style: An MPL line style value.
        """
        super(HorizontalMarker, self).__init__()
        self.canvas = canvas
        if axis is None:
            self.axis = canvas.figure.get_axes()[0]
        else:
            self.axis = axis
        self.y = y
        self.x0 = x0
        self.x1 = x1
        x0, x1 = self._get_x0_x1()
        path = Path([(x0, y), (x1, y)], [Path.MOVETO, Path.LINETO])
        self.patch = PathPatch(path,
                               facecolor='None',
                               edgecolor=color,
                               picker=picker_width,
                               linewidth=line_width,
                               linestyle=line_style,
                               animated=True)
        self.axis.add_patch(self.patch)
        self.axis.interactive_markers.append(self.patch)
        self.is_moving = False
        self.move_cursor = move_cursor

    def _get_x0_x1(self):
        """
        Calculate the current x coordinates of the line ends.
        :return: Tuple x0, x1.
        """
        if self.x0 is None or self.x1 is None:
            x0, x1 = self.axis.get_xlim()
        if self.x0 is not None:
            x0 = self.x0
        if self.x1 is not None:
            x1 = self.x1
        return x0, x1

    def remove(self):
        """
        Remove this marker from the canvas.
        """
        self.patch.remove()

    def redraw(self):
        """
        Redraw this marker.
        """
        x0, x1 = self._get_x0_x1()
        vertices = self.patch.get_path().vertices
        vertices[0] = x0, self.y
        vertices[1] = x1, self.y
        self.axis.draw_artist(self.patch)

    def set_visible(self, visible):
        self.patch.set_visible(visible)

    def set_color(self, color):
        """
        Set the colour of the marker
        :param color: The color to set the marker to.
        """
        self.patch.set_edgecolor(color)

    def get_position(self):
        """
        Get the y coordinate in axes coords.
        :return: y in axes coords
        """
        return self.y

    def set_position(self, y):
        """
        Set the y position of the marker.
        :param y: An y axis coordinate.
        """
        self.y = y
        self.y_moved.emit(y)

    def get_y_in_pixels(self):
        """
        Returns the y coordinate in screen pixels.
        :return: y in pixels
        """
        _, y_pixels = self.patch.get_transform().transform((0, self.y))
        return y_pixels

    def is_above(self, x, y):
        """
        Check if a mouse positioned at (x, y) is over this marker.
        :param x: An x mouse coordinate.
        :param y: An y mouse coordinate.
        :return: True or False.
        """
        _, y_pixels = self.patch.get_transform().transform((x, y))

        if self.x0 is not None and x < self.x0:
            return False
        if self.x1 is not None and x > self.x1:
            return False
        return abs(self.get_y_in_pixels() - y_pixels) < MARKER_SENSITIVITY

    def mouse_move_start(self, x, y):
        """
        Start moving this marker if (x, y) is above it. Ignore otherwise.
        :param x: An x mouse coordinate.
        :param y: An y mouse coordinate.
        :param pixels: True if the coordinates are already in pixels.
        """
        self.is_moving = self.is_above(x, y)

    def mouse_move_stop(self):
        """
        Stop moving.
        """
        self.is_moving = False

    def mouse_move(self, x, y):
        """
        Move this marker to a new position if movement had been started earlier by a call to mouse_move_start(x, y)
        :param x: An x mouse coordinate.
        :param y: An y mouse coordinate.
        :return: True if moved or False if stayed at the old position.
        """
        if self.is_moving and y is not None and x is not None:
            self.set_position(y)
            return True
        return False

    def is_marker_moving(self):
        """
        Returns true if the marker is being moved
        :return: True if the marker is being moved.
        """
        return self.is_moving

    def override_cursor(self, x, y):
        """
        Get the override cursor for mouse position (x, y)
        :param x: An x mouse coordinate.
        :param y: An y mouse coordinate.
        :return: QCursor or None.
        """
        if self.x0 is not None and x < self.x0:
            return None
        if self.x1 is not None and x > self.x1:
            return None
        if self.is_moving or self.is_above(x, y):
            return self.move_cursor if self.move_cursor is not None else QCursor(
                Qt.SizeVerCursor)
        return None

    def set_move_cursor(self, cursor, x_pos, y_pos):
        """Set the style of the cursor to use when the marker is moving"""
        if cursor is not None:
            cursor = QCursor(cursor)
        self.move_cursor = cursor
        self.override_cursor(x_pos, y_pos)
Beispiel #5
0
class TopoModule(ModuleTemplate):
    """
    Module for simple Topography visualization without computing a geological model
    """
    def __init__(self, extent: list = None, **kwargs):
        pn.extension()
        self.max_height = 800
        self.center = 0
        self.min_height = -200
        self.sea = False
        self.sea_contour = False
        self.sea_level_patch = None
        self.col = None  # Check if image is loaded
        self.type_fluid = ["water", "lava", "slime"]
        self.name_fluid = self.type_fluid[0]
        self.fluid = None  # Image to create the texture
        self.texture = None  # resultant texture after masking with path
        if extent is not None:
            self.extent = extent
            self.load_fluid()
        self.animate = True
        self._anim = 0  # animation
        # Settings for sea level polygon
        self.path = None
        self.sea_level_polygon_alpha = 0.7
        self.sea_level_polygon_line_thickness = 2.
        self.sea_level_polygon_line_color = mcolors.to_hex("blue")
        self.sea_zorder = 1000
        self.sea_fill = True

        self._marker_contour_val = None
        self.side_flooding = False
        logger.info("TopoModule loaded successfully")

    def update(self, sb_params: dict):
        """
        Acquire the information from th sb_params dict and call the functions to modify
        the frame and/or plot in the axes
        Args:
            sb_params:
        Returns:
        """
        frame = sb_params.get('frame')
        extent = sb_params.get('extent')
        ax = sb_params.get('ax')
        frame, extent = self.normalize_topography(frame,
                                                  extent,
                                                  min_height=self.min_height,
                                                  max_height=self.max_height)
        marker = sb_params.get('marker')
        if len(marker) > 0:
            self._marker_contour_val = marker.loc[marker.is_inside_box,
                                                  ('box_x', 'box_y')].values[0]
        else:
            self._marker_contour_val = None

        self.extent = extent
        self.plot(frame, ax, extent)
        sb_params['frame'] = frame
        sb_params['ax'] = ax
        sb_params['extent'] = extent

        return sb_params

    def plot(self, frame, ax, extent):
        """
        Deals with everything related to plotting in the axes
        Args:
            frame: Sandbox frame
            ax: axes of matplotlib figure to paint on
        Returns:

        """
        if self.sea or self.sea_contour:
            self._delete_path()
            #if self._center != self.center:
            # TODO: Avoid unnecessary calculation of new paths
            try:
                if self.side_flooding and self._marker_contour_val is not None:
                    self.path, self.center = self.find_single_path(
                        frame, self._marker_contour_val)
                else:
                    self.path = self.create_paths(frame, self.center)
            except Exception as e:
                logger.error(e, exc_info=True)

            if self.sea and self.path is not None:
                self.set_texture(self.path)
                if self.col is None:
                    self.col = ax.imshow(self.texture,
                                         origin='lower',
                                         aspect='auto',
                                         zorder=self.sea_zorder + 1,
                                         alpha=self.sea_level_polygon_alpha)
                else:
                    self.col.set_data(self.texture)
                    self.col.set_alpha(self.sea_level_polygon_alpha)
            else:
                self._delete_image()

            if self.sea_contour and self.path is not None:
                # Add contour polygon of sea level
                self.sea_level_patch = PathPatch(
                    self.path,
                    alpha=self.sea_level_polygon_alpha,
                    linewidth=self.sea_level_polygon_line_thickness,
                    # ec=self.sea_level_polygon_line_color,
                    color=self.sea_level_polygon_line_color,
                    zorder=self.sea_zorder,
                    fill=self.sea_fill)
                # ContourLinesModule
                ax.add_patch(self.sea_level_patch)
        else:
            self._delete_image()
            self._delete_path()

    def _delete_image(self):
        """Remove sea-texture"""
        if self.col:
            self.col.remove()
        self.col = None

    def _delete_path(self):
        """remove sea-level patch, if previously defined"""
        if self.sea_level_patch:
            self.sea_level_patch.remove()
        self.sea_level_patch = None

    def set_texture(self, path):
        x = np.arange(0, self.extent[1] - 1, 1)
        y = np.arange(0, self.extent[3] - 1, 1)
        xx, yy = np.meshgrid(x, y)
        xy = np.vstack((xx.ravel(), yy.ravel())).T
        mask = path.contains_points(xy)
        present = xy[mask]
        self.texture = self.fluid.copy()
        if self.animate:
            self.animate_texture()
        for i in range(len(present)):
            self.texture[present[i][1], present[i][0], -1] = 1

    def animate_texture(self):
        """
        Move the texture image to give the appearance of moving like waves
        Returns:
        """
        A = self.texture.shape[0] / 5
        w = 2.0 / self.texture.shape[1]
        shift = lambda x: A * np.sin(2.0 * np.pi * (x + self._anim) * w)
        for i in range(self.texture.shape[1]):
            vector = np.roll(np.arange(0, self.texture.shape[0]),
                             int(round(shift(i))))
            self.texture[:, i][:] = self.texture[:, i][:][vector]
        self._anim += 1

    @staticmethod
    def normalize_topography(frame, extent, min_height, max_height):
        """
        Change the max an min value of the frame and normalize accordingly
        Args:
            frame: sensor frame
            extent: sensor extent
            max_height: Target max height
            min_height: Target min height

        Returns:
            normalized frame and new extent

        """
        # first position the frame in 0 if the original extent is not in 0
        if extent[-2] != 0:
            displ = 0 - extent[-2]
            frame = frame - displ
        # calculate how much we need to move the frame so the 0 value correspond to the approximate 0 in the frame
        # min_height assuming is under 0.

        if min_height < 0:
            displace = min_height * (-1) * (extent[-1] - extent[-2]) / (
                max_height - min_height)
            frame = frame - displace
            extent[-1] = extent[-1] - displace
            extent[-2] = extent[-2] - displace
            # now we set 2 regions. One above sea level and one below sea level. So now we can normalize these two
            # regions above 0
            frame[frame > 0] = frame[frame > 0] * (max_height / extent[-1])
            # below 0
            frame[frame < 0] = frame[frame < 0] * (min_height / extent[-2])
        elif min_height > 0:
            frame = frame * (max_height - min_height) / (extent[-1] -
                                                         extent[-2])
            frame = frame + min_height  # just displace all up to start in min_height
        elif min_height == 0:
            frame = frame * max_height / (extent[-1])
        else:
            raise AttributeError
        extent[-1] = max_height  # self.plot.vmax = max_height
        extent[-2] = min_height  # self.plot.vmin = min_height
        return frame, extent

    @staticmethod
    def reshape(image, shape: tuple):
        """
        Reshape any image to the desired shape. Change shape of numpy array to desired shape
        Args:
            image:
            shape: sandbox shape
        Returns:
             reshaped frame
        """
        return skimage.transform.resize(image,
                                        shape,
                                        order=3,
                                        mode='edge',
                                        anti_aliasing=True,
                                        preserve_range=False)

    def load_fluid(self):
        image = plt.imread(_package_dir + '/modules/img/' + self.name_fluid +
                           '.jpg')
        fluid = self.reshape(image,
                             (self.extent[3], self.extent[1]))  # height, width
        # convert RGB image to RGBA
        self.fluid = np.dstack(
            (fluid, np.zeros((self.extent[3], self.extent[1]))))

    @staticmethod
    def create_paths(frame, contour_val):
        """Create compound path for given contour value
        Args:
            frame: sensor frame
            contour_val (float): value of contour
        Returns:
            path: matplotlib.Path object for contour polygon
        """
        # create padding
        frame_padded = np.pad(frame,
                              pad_width=1,
                              mode='constant',
                              constant_values=np.max(frame) + 1)
        contours = measure.find_contours(frame_padded.T, contour_val)

        # combine values
        contour_comb = np.concatenate(contours, axis=0)

        # generate codes to close polygons
        # First: link all
        codes = [Path.LINETO for _ in range(contour_comb.shape[0])]
        # Next: find ends of each contour and close
        index = 0
        for contour in contours:
            codes[index] = Path.MOVETO
            index += len(contour)
            codes[index - 1] = Path.CLOSEPOLY
        path = Path(contour_comb, codes)
        return path

    @staticmethod
    def find_single_path(frame: tuple, point: tuple):
        """
        From a frame, find the point that contains the point and start changing the contour val excluding isolated
        contours
        Args:
            frame: frame of sandbox
            point: Aruco point coordinate (x, y)
        Returns:
            path:
            contour_val:
        """
        # create padding
        contour_val = frame[int(point[1]), int(
            point[0])]  # To be sure that there will be a contour in that point
        frame_padded = np.pad(frame,
                              pad_width=1,
                              mode='constant',
                              constant_values=np.max(frame) + 1)
        contours = measure.find_contours(frame_padded.T, contour_val)
        # Look 5 pixels in the surrounding (2 each side) if a path contains any of those points
        surrounding_points = [(point[0] - 2 + i, point[1] - 2 + j)
                              for i in range(5) for j in range(5)]
        for con in contours:
            codes = [Path.LINETO for _ in range(len(con))]
            codes[0] = Path.MOVETO
            codes[-1] = Path.CLOSEPOLY
            path = Path(con, codes)

            if True in path.contains_points(surrounding_points):
                return path, contour_val
        logger.warning("No path includes the point %s" % point)
        return None, None

    def show_widgets(self):
        self._create_widgets()
        panel = pn.Column(
            "### Widgets for Topography normalization",
            pn.Row(
                pn.Column(self._widget_min_height, self._widget_max_height,
                          self._widget_sea,
                          pn.Row(self._widget_sea_contour,
                                 self._widget_fill), self._widget_sea_level),
                pn.Column(self._widget_animation, self._widget_type_fluid,
                          self._widget_transparency, self._widget_color)),
            " #### Use aruco marker for local filling", self._widget_aruco)
        return panel

    def _create_widgets(self):
        self._widget_min_height = pn.widgets.Spinner(
            name="Minimum height of topography",
            value=self.min_height,
            step=20)
        self._widget_min_height.param.watch(self._callback_min_height,
                                            'value',
                                            onlychanged=False)

        self._widget_max_height = pn.widgets.Spinner(
            name="Maximum height of topography",
            value=self.max_height,
            step=20)
        self._widget_max_height.param.watch(self._callback_max_height,
                                            'value',
                                            onlychanged=False)

        self._widget_sea_level = pn.widgets.IntSlider(
            name="Set sea level height",
            start=self.min_height,
            end=self.max_height,
            step=5,
            value=self.center,
        )
        self._widget_sea_level.param.watch(self._callback_sea_level,
                                           'value',
                                           onlychanged=False)

        self._widget_sea = pn.widgets.Checkbox(name='Show sea level',
                                               value=self.sea)
        self._widget_sea.param.watch(self._callback_see,
                                     'value',
                                     onlychanged=False)

        self._widget_sea_contour = pn.widgets.Checkbox(
            name='Show sea level contour',
            value=self.sea_contour,
            width_policy='min')
        self._widget_sea_contour.param.watch(self._callback_see_contour,
                                             'value',
                                             onlychanged=False)

        self._widget_fill = pn.widgets.Checkbox(name='Fill contour',
                                                value=self.sea_fill,
                                                width_policy='min')
        self._widget_fill.param.watch(self._callback_fill,
                                      'value',
                                      onlychanged=False)

        self._widget_type_fluid = pn.widgets.Select(
            name='Select type of texture for the fluid',
            options=self.type_fluid,
            size=3,
            value=self.name_fluid)
        self._widget_type_fluid.param.watch(self._callback_select_fluid,
                                            'value',
                                            onlychanged=False)

        self._widget_transparency = pn.widgets.Spinner(
            name="Select transparency",
            value=self.sea_level_polygon_alpha,
            step=0.05)
        self._widget_transparency.param.watch(self._callback_transparency,
                                              'value',
                                              onlychanged=False)

        self._widget_color = pn.widgets.ColorPicker(
            name='Color level contour',
            value=self.sea_level_polygon_line_color)
        self._widget_color.param.watch(self._callback_color,
                                       'value',
                                       onlychanged=False)

        self._widget_animation = pn.widgets.Checkbox(
            name='Animate wave movement', value=self.animate)
        self._widget_animation.param.watch(self._callback_animation,
                                           'value',
                                           onlychanged=False)

        self._widget_aruco = pn.widgets.Checkbox(name='Local level',
                                                 value=self.side_flooding)
        self._widget_aruco.param.watch(self._callback_aruco,
                                       'value',
                                       onlychanged=False)

    def _callback_aruco(self, event):
        self.side_flooding = event.new
        self._widget_sea_level.disabled = event.new

    def _callback_fill(self, event):
        self.sea_fill = event.new

    def _callback_transparency(self, event):
        self.sea_level_polygon_alpha = event.new

    def _callback_color(self, event):
        self.sea_level_polygon_line_color = event.new

    def _callback_animation(self, event):
        self.animate = event.new

    def _callback_select_fluid(self, event):
        self.name_fluid = event.new
        self.load_fluid()

    def _callback_min_height(self, event):
        self.min_height = event.new
        if self.center < self.min_height:
            self.center = self.min_height + 1
            self._widget_sea_level.value = self.center + 1
        self._widget_sea_level.start = event.new + 1

    def _callback_max_height(self, event):
        self.max_height = event.new
        if self.center > self.max_height:
            self.center = self.max_height - 1
            self._widget_sea_level.value = self.center - 1
        self._widget_sea_level.end = event.new - 1

    def _callback_sea_level(self, event):
        self.center = event.new

    def _callback_see(self, event):
        self.sea = event.new

    def _callback_see_contour(self, event):
        self.sea_contour = event.new
Beispiel #6
0
class VerticalMarker(QObject):
    """
    An interactive marker displayed as a vertical line.
    """

    x_moved = Signal(float)

    def __init__(self, canvas, color, x, y0=None, y1=None, line_width=1.0, picker_width=5, line_style='-'):
        """
        Init the marker.
        :param canvas: A MPL canvas.
        :param color: An MPL colour value
        :param x: The x coordinate (data) of the marker.
        :param y0: The y coordinate (data) of the bottom end of the marker. Default is None which means dynamically
            set it to the current lowest y value displayed.
        :param y1: The y coordinate (data) of the top end of the marker. Default is None which means dynamically
            set it to the current highest y value displayed.
        :param line_width: The line width (pixels).
        :param picker_width: The picker sensitivity (pixels).
        :param line_style: An MPL line style value.
        """
        super(VerticalMarker, self).__init__()
        self.ax = canvas.figure.get_axes()[0]
        self.x = x
        self.y0 = y0
        self.y1 = y1
        y0, y1 = self._get_y0_y1()
        path = Path([(x, y0), (x, y1)], [Path.MOVETO, Path.LINETO])
        self.patch = PathPatch(path, facecolor='None', edgecolor=color, picker=picker_width,
                               linewidth=line_width, linestyle=line_style, animated=True)
        self.ax.add_patch(self.patch)
        self.is_moving = False

    def _get_y0_y1(self):
        """
        Calculate the current y coordinates of the line ends.
        :return: Tuple y0, y1.
        """
        if self.y0 is None or self.y1 is None:
            y0, y1 = self.ax.get_ylim()
        if self.y0 is not None:
            y0 = self.y0
        if self.y1 is not None:
            y1 = self.y1
        return y0, y1

    def remove(self):
        """
        Remove this marker from the canvas.
        """
        self.patch.remove()

    def redraw(self):
        """
        Redraw this marker.
        """
        y0, y1 = self._get_y0_y1()
        vertices = self.patch.get_path().vertices
        vertices[0] = self.x, y0
        vertices[1] = self.x, y1
        self.ax.draw_artist(self.patch)

    def get_x_in_pixels(self):
        """
        Get the x coordinate in screen pixels.
        """
        x_pixels, _ = self.patch.get_transform().transform((self.x, 0))
        return x_pixels

    def is_above(self, x, y):
        """
        Check if a mouse positioned at (x, y) is over this marker.
        :param x: An x mouse coordinate.
        :param y: An y mouse coordinate.
        :return: True or False.
        """
        x_pixels, y_pixels = self.patch.get_transform().transform((x, y))
        if self.y0 is not None and y < self.y0:
            return False
        if self.y1 is not None and y > self.y1:
            return False
        return abs(self.get_x_in_pixels() - x_pixels) < 3

    def mouse_move_start(self, x, y):
        """
        Start moving this marker if (x, y) is above it. Ignore otherwise.
        :param x: An x mouse coordinate.
        :param y: An y mouse coordinate.
        """
        self.is_moving = self.is_above(x, y)

    def mouse_move_stop(self):
        """
        Stop moving.
        """
        self.is_moving = False

    def mouse_move(self, x, y=None):
        """
        Move this marker to a new position if movement had been started earlier by a call to mouse_move_start(x, y)
        :param x: An x mouse coordinate.
        :param y: An y mouse coordinate.
        :return: True if moved or False if stayed at the old position.
        """
        if self.is_moving and x is not None:
            self.x = x
            self.x_moved.emit(x)
            return True
        return False

    def get_cursor_at_y(self, y):
        """
        Get an override cursor for an y coordinate given that the x == self.x
        :param y: A y coordinate.
        :return: QCursor or None.
        """
        return QCursor(Qt.SizeHorCursor)

    def override_cursor(self, x, y):
        """
        Get the override cursor for mouse position (x, y)
        :param x: An x mouse coordinate.
        :param y: An y mouse coordinate.
        :return: QCursor or None.
        """
        if self.y0 is not None and y < self.y0:
            return None
        if self.y1 is not None and y > self.y1:
            return None
        if self.is_moving or self.is_above(x, y):
            return self.get_cursor_at_y(y)
        return None