Esempio n. 1
0
 def draw_nodes(self, net=None, node_colors={}, node_radius={}):
     if not net:
         net = self.net
     if type(node_colors) == str:
         node_colors = {node: node_colors for node in net.nodes()}
     nodeCircles = []
     for n in net.nodes():
         c = NodeCircle(tuple(net.pos[n]), node_radius.get(n, 8.0),
                        color=node_colors.get(n, 'r'),
                    ec='k', lw=1.0, ls='solid', picker=3)
         nodeCircles.append(c)
     node_collection = PatchCollection(nodeCircles, match_original=True)
     node_collection.set_picker(3)
     self.axes.add_collection(node_collection)
     return node_collection
Esempio n. 2
0
 def draw_nodes(self, net=None, node_colors={}, node_radius={}):
     if not net:
         net = self.net
     if type(node_colors) == str:
         node_colors = {node: node_colors for node in net.nodes()}
     nodeCircles = []
     for n in net.nodes():
         c = NodeCircle(tuple(net.pos[n]),
                        node_radius.get(n, 8.0),
                        color=node_colors.get(n, 'r'),
                        ec='k',
                        lw=1.0,
                        ls='solid',
                        picker=3)
         nodeCircles.append(c)
     node_collection = PatchCollection(nodeCircles, match_original=True)
     node_collection.set_picker(3)
     self.axes.add_collection(node_collection)
     return node_collection
Esempio n. 3
0
class CameraDisplay:
    """
    Camera Display using matplotlib.

    Parameters
    ----------
    geometry : `~ctapipe.io.CameraGeometry`
        Definition of the Camera/Image
    image: array_like
        array of values corresponding to the pixels in the CameraGeometry.
    ax : `matplotlib.axes.Axes`
        A matplotlib axes object to plot on, or None to create a new one
    title : str (default "Camera")
        Title to put on camera plot
    norm : str or `matplotlib.color.Normalize` instance (default 'lin')
        Normalization for the color scale.
        Supported str arguments are
        - 'lin': linear scale
        - 'log': logarithmic scale (base 10)
    cmap : str or `matplotlib.colors.Colormap` (default 'hot')
        Color map to use (see `matplotlib.cm`)
    allow_pick : bool (default False)
        if True, allow user to click and select a pixel
    autoupdate : bool (default True)
        redraw automatically (otherwise need to call plt.draw())
    autoscale : bool (default True)
        rescale the vmin/vmax values when the image changes.
        This is set to False if `set_limits_*` is called to explicity
        set data limits.
    antialiased : bool  (default True)
        whether to draw in antialiased mode or not.

    Notes
    -----

    Speed:
        CameraDisplay is not intended to be very fast (matplotlib
        is not a very speed performant graphics library, it is
        intended for nice output plots). However, most of the
        slowness of CameraDisplay is in the constructor.  Once one is
        displayed, changing the image that is displayed is relatively
        fast and efficient. Therefore it is best to initialize an
        instance, and change the data, rather than generating new
        CameraDisplays.

    Pixel Implementation:
        Pixels are rendered as a
        `matplotlib.collections.PatchCollection` of Polygons (either 6
        or 4 sided).  You can access the PatchCollection directly (to
        e.g. change low-level style parameters) via
        `CameraDisplay.pixels`

    Output:
        Since CameraDisplay uses matplotlib, any display can be
        saved to any output file supported via
        plt.savefig(filename). This includes `.pdf` and `.png`.

    """
    def __init__(
        self,
        geometry,
        image=None,
        ax=None,
        title="Camera",
        norm="lin",
        cmap="hot",
        allow_pick=False,
        autoupdate=True,
        autoscale=True,
        antialiased=True,
    ):
        self.axes = ax if ax is not None else plt.gca()
        self.geom = geometry
        self.pixels = None
        self.colorbar = None
        self.autoupdate = autoupdate
        self.autoscale = autoscale
        self._active_pixel = None
        self._active_pixel_label = None

        # initialize the plot and generate the pixels as a
        # RegularPolyCollection

        patches = []

        for xx, yy, aa in zip(
                u.Quantity(self.geom.pix_x).value,
                u.Quantity(self.geom.pix_y).value,
                u.Quantity(np.array(self.geom.pix_area))):
            if self.geom.pix_type.startswith("hex"):
                rr = sqrt(aa * 2 / 3 / sqrt(3))
                poly = RegularPolygon(
                    (xx, yy),
                    6,
                    radius=rr,
                    orientation=self.geom.pix_rotation.rad,
                    fill=True,
                )
            else:
                rr = sqrt(aa)
                poly = Rectangle(
                    (xx - rr / 2., yy - rr / 2.),
                    width=rr,
                    height=rr,
                    angle=self.geom.pix_rotation.deg,
                    fill=True,
                )

            patches.append(poly)

        self.pixels = PatchCollection(patches, cmap=cmap, linewidth=0)
        self.axes.add_collection(self.pixels)

        self.pixel_highlighting = copy.copy(self.pixels)
        self.pixel_highlighting.set_facecolor('none')
        self.pixel_highlighting.set_linewidth(0)
        self.axes.add_collection(self.pixel_highlighting)

        # Set up some nice plot defaults

        self.axes.set_aspect('equal', 'datalim')
        self.axes.set_title(title)
        self.axes.set_xlabel("X position ({})".format(self.geom.pix_x.unit))
        self.axes.set_ylabel("Y position ({})".format(self.geom.pix_y.unit))
        self.axes.autoscale_view()

        # set up a patch to display when a pixel is clicked (and
        # pixel_picker is enabled):

        self._active_pixel = copy.copy(patches[0])
        self._active_pixel.set_facecolor('r')
        self._active_pixel.set_alpha(0.5)
        self._active_pixel.set_linewidth(2.0)
        self._active_pixel.set_visible(False)
        self.axes.add_patch(self._active_pixel)

        self._active_pixel_label = self.axes.text(self._active_pixel.xy[0],
                                                  self._active_pixel.xy[1],
                                                  "0",
                                                  horizontalalignment='center',
                                                  verticalalignment='center')
        self._active_pixel_label.set_visible(False)

        # enable ability to click on pixel and do something (can be
        # enabled on-the-fly later as well:

        if allow_pick:
            self.enable_pixel_picker()

        if image is not None:
            self.image = image
        else:
            self.image = np.zeros_like(self.geom.pix_id, dtype=np.float)

        self.norm = norm

    def highlight_pixels(self, pixels, color='g', linewidth=1, alpha=0.75):
        '''
        Highlight the given pixels with a colored line around them

        Parameters
        ----------
        pixels : index-like
            The pixels to highlight.
            Can either be a list or array of integers or a
            boolean mask of length number of pixels
        color: a matplotlib conform color
            the color for the pixel highlighting
        linewidth: float
            linewidth of the highlighting in points
        alpha: 0 <= alpha <= 1
            The transparency
        '''

        l = np.zeros_like(self.image)
        l[pixels] = linewidth
        self.pixel_highlighting.set_linewidth(l)
        self.pixel_highlighting.set_alpha(alpha)
        self.pixel_highlighting.set_edgecolor(color)
        self.update()

    def enable_pixel_picker(self):
        """ enable ability to click on pixels """
        self.pixels.set_picker(True)  # enable click
        self.pixels.set_pickradius(
            sqrt(u.Quantity(self.geom.pix_area[0]).value) / np.pi)
        self.pixels.set_snap(True)  # snap cursor to pixel center
        self.axes.figure.canvas.mpl_connect('pick_event', self._on_pick)

    def set_limits_minmax(self, zmin, zmax):
        """ set the color scale limits from min to max """
        self.pixels.set_clim(zmin, zmax)
        self.autoscale = False
        self.update()

    def set_limits_percent(self, percent=95):
        """ auto-scale the color range to percent of maximum """
        zmin = self.pixels.get_array().min()
        zmax = self.pixels.get_array().max()
        dz = zmax - zmin
        frac = percent / 100.0
        self.autoscale = False
        self.set_limits_minmax(zmin, zmax - (1.0 - frac) * dz)

    @property
    def norm(self):
        '''
        The norm instance of the Display

        Possible values:

        - "lin": linear scale
        - "log": log scale
        -  any matplotlib.colors.Normalize instance, e. g. PowerNorm(gamma=-2)
        '''
        return self.pixels.norm

    @norm.setter
    def norm(self, norm):

        if norm == 'lin':
            self.pixels.norm = Normalize()
        elif norm == 'log':
            self.pixels.norm = LogNorm()
            self.pixels.autoscale()  # this is to handle matplotlib bug #5424
        elif isinstance(norm, Normalize):
            self.pixels.norm = norm
        else:
            raise ValueError('Unsupported norm: {}'.format(norm))

        self.update(force=True)
        self.pixels.autoscale()

    @property
    def cmap(self):
        """
        Color map to use. Either a name or  `matplotlib.colors.ColorMap`
        instance, e.g. from `matplotlib.pyplot.cm`
        """
        return self.pixels.get_cmap()

    @cmap.setter
    def cmap(self, cmap):
        self.pixels.set_cmap(cmap)
        self.update()

    @property
    def image(self):
        """The image displayed on the camera (1D array of pixel values)"""
        return self.pixels.get_array()

    @image.setter
    def image(self, image):
        """
        Change the image displayed on the Camera.

        Parameters
        ----------
        image: array_like
            array of values corresponding to the pixels in the CameraGeometry.
        """
        image = np.asanyarray(image)
        if image.shape != self.geom.pix_x.shape:
            raise ValueError("Image has a different shape {} than the"
                             "given CameraGeometry {}".format(
                                 image.shape, self.geom.pix_x.shape))

        self.pixels.set_array(image)
        self.pixels.changed()
        if self.autoscale:
            self.pixels.autoscale()
        self.update()

    def update(self, force=False):
        """ signal a redraw if necessary """
        if self.autoupdate:
            if self.colorbar is not None:
                if force is True:
                    self.colorbar.update_bruteforce(self.pixels)
                else:
                    self.colorbar.update_normal(self.pixels)
                self.colorbar.draw_all()
            self.axes.figure.canvas.draw()

    def add_colorbar(self, **kwargs):
        """
        add a colobar to the camera plot
        kwargs are passed to `figure.colorbar(self.pixels, **kwargs)`
        See matplotlib documentation for the supported kwargs:
        http://matplotlib.org/api/figure_api.html#matplotlib.figure.Figure.colorbar
        """
        if self.colorbar is not None:
            raise ValueError(
                'There is already a colorbar attached to this CameraDisplay')
        else:
            self.colorbar = self.axes.figure.colorbar(self.pixels, **kwargs)
        self.update()

    def add_ellipse(self,
                    centroid,
                    length,
                    width,
                    angle,
                    asymmetry=0.0,
                    **kwargs):
        """
        plot an ellipse on top of the camera

        Parameters
        ----------
        centroid: (float, float)
            position of centroid
        length: float
            major axis
        width: float
            minor axis
        angle: float
            rotation angle wrt "up" about the centroid, clockwise, in radians
        asymmetry: float
            3rd-order moment for directionality if known
        kwargs:
            any MatPlotLib style arguments to pass to the Ellipse patch

        """
        ellipse = Ellipse(xy=centroid,
                          width=width,
                          height=length,
                          angle=np.degrees(angle),
                          fill=False,
                          **kwargs)
        self.axes.add_patch(ellipse)
        self.update()
        return ellipse

    def overlay_moments(self, momparams, **kwargs):
        """helper to overlay ellipse from a `reco.MomentParameters` structure

        Parameters
        ----------
        momparams: `reco.MomentParameters`
            structuring containing Hillas-style parameterization
        kwargs: key=value
            any style keywords to pass to matplotlib (e.g. color='red'
            or linewidth=6)
        """
        el = self.add_ellipse(centroid=(momparams.cen_x.value,
                                        momparams.cen_y.value),
                              length=momparams.length.value,
                              width=momparams.width.value,
                              angle=momparams.psi.to(u.rad).value,
                              **kwargs)
        self.axes.text(momparams.cen_x.value,
                       momparams.cen_y.value,
                       ("({:.02f},{:.02f})\n"
                        "[w={:.02f},l={:.02f}]").format(
                            momparams.cen_x, momparams.cen_y, momparams.width,
                            momparams.length),
                       color=el.get_edgecolor())

    def _on_pick(self, event):
        """ handler for when a pixel is clicked """
        pix_id = event.ind[-1]
        xx, yy, aa = u.Quantity(self.geom.pix_x[pix_id]).value, \
                     u.Quantity(self.geom.pix_y[pix_id]).value, \
                     u.Quantity(np.array(self.geom.pix_area)[pix_id])
        if self.geom.pix_type.startswith("hex"):
            self._active_pixel.xy = (xx, yy)
        else:
            rr = sqrt(aa)
            self._active_pixel.xy = (xx - rr / 2., yy - rr / 2.)
        self._active_pixel.set_visible(True)
        self._active_pixel_label.set_x(xx)
        self._active_pixel_label.set_y(yy)
        self._active_pixel_label.set_text("{:003d}".format(pix_id))
        self._active_pixel_label.set_visible(True)
        self.update()
        self.on_pixel_clicked(pix_id)  # call user-function

    def on_pixel_clicked(self, pix_id):
        """virtual function to overide in sub-classes to do something special
        when a pixel is clicked
        """
        print("Clicked pixel_id {}".format(pix_id))

    def show(self):
        self.axes.figure.show()
Esempio n. 4
0
class CameraDisplay:
    """
    Camera Display using matplotlib.

    Parameters
    ----------
    geometry : `~ctapipe.instrument.CameraGeometry`
        Definition of the Camera/Image
    image: array_like
        array of values corresponding to the pixels in the CameraGeometry.
    ax : `matplotlib.axes.Axes`
        A matplotlib axes object to plot on, or None to create a new one
    title : str (default "Camera")
        Title to put on camera plot
    norm : str or `matplotlib.color.Normalize` instance (default 'lin')
        Normalization for the color scale.
        Supported str arguments are
        - 'lin': linear scale
        - 'log': logarithmic scale (base 10)
    cmap : str or `matplotlib.colors.Colormap` (default 'hot')
        Color map to use (see `matplotlib.cm`)
    allow_pick : bool (default False)
        if True, allow user to click and select a pixel
    autoupdate : bool (default True)
        redraw automatically (otherwise need to call plt.draw())
    autoscale : bool (default True)
        rescale the vmin/vmax values when the image changes.
        This is set to False if `set_limits_*` is called to explicity
        set data limits.

    Notes
    -----

    Speed:
        CameraDisplay is not intended to be very fast (matplotlib
        is not a very speed performant graphics library, it is
        intended for nice output plots). However, most of the
        slowness of CameraDisplay is in the constructor.  Once one is
        displayed, changing the image that is displayed is relatively
        fast and efficient. Therefore it is best to initialize an
        instance, and change the data, rather than generating new
        CameraDisplays.

    Pixel Implementation:
        Pixels are rendered as a
        `matplotlib.collections.PatchCollection` of Polygons (either 6
        or 4 sided).  You can access the PatchCollection directly (to
        e.g. change low-level style parameters) via
        `CameraDisplay.pixels`

    Output:
        Since CameraDisplay uses matplotlib, any display can be
        saved to any output file supported via
        plt.savefig(filename). This includes ``.pdf`` and ``.png``.

    """
    def __init__(
        self,
        geometry,
        image=None,
        ax=None,
        title=None,
        norm="lin",
        cmap=None,
        allow_pick=False,
        autoupdate=True,
        autoscale=True,
    ):
        self.axes = ax if ax is not None else plt.gca()
        self.pixels = None
        self.colorbar = None
        self.autoupdate = autoupdate
        self.autoscale = autoscale
        self._active_pixel = None
        self._active_pixel_label = None
        self._axes_overlays = []

        self.geom = geometry

        if title is None:
            title = geometry.camera_name

        # initialize the plot and generate the pixels as a
        # RegularPolyCollection

        patches = []

        if hasattr(self.geom, "mask"):
            self.mask = self.geom.mask
        else:
            self.mask = np.ones_like(self.geom.pix_x.value, dtype=bool)

        pix_x = self.geom.pix_x.value[self.mask]
        pix_y = self.geom.pix_y.value[self.mask]
        pix_area = self.geom.pix_area.value[self.mask]

        for x, y, area in zip(pix_x, pix_y, pix_area):
            if self.geom.pix_type.startswith("hex"):
                r = sqrt(area * 2 / 3 / sqrt(3)) + 2 * PIXEL_EPSILON
                poly = RegularPolygon(
                    (x, y),
                    6,
                    radius=r,
                    orientation=self.geom.pix_rotation.to_value(u.rad),
                    fill=True,
                )
            else:
                r = sqrt(area) + PIXEL_EPSILON
                poly = Rectangle(
                    (x - r / 2, y - r / 2),
                    width=r,
                    height=r,
                    angle=self.geom.pix_rotation.to_value(u.deg),
                    fill=True,
                )

            patches.append(poly)

        self.pixels = PatchCollection(patches, cmap=cmap, linewidth=0)
        self.axes.add_collection(self.pixels)

        self.pixel_highlighting = copy.copy(self.pixels)
        self.pixel_highlighting.set_facecolor("none")
        self.pixel_highlighting.set_linewidth(0)
        self.axes.add_collection(self.pixel_highlighting)

        # Set up some nice plot defaults

        self.axes.set_aspect("equal", "datalim")
        self.axes.set_title(title)
        self.axes.set_xlabel(f"X position ({self.geom.pix_x.unit})")
        self.axes.set_ylabel(f"Y position ({self.geom.pix_y.unit})")
        self.axes.autoscale_view()

        # set up a patch to display when a pixel is clicked (and
        # pixel_picker is enabled):

        self._active_pixel = copy.copy(patches[0])
        self._active_pixel.set_facecolor("r")
        self._active_pixel.set_alpha(0.5)
        self._active_pixel.set_linewidth(2.0)
        self._active_pixel.set_visible(False)
        self.axes.add_patch(self._active_pixel)

        self._active_pixel_label = self.axes.text(
            self._active_pixel.xy[0],
            self._active_pixel.xy[1],
            "0",
            horizontalalignment="center",
            verticalalignment="center",
        )
        self._active_pixel_label.set_visible(False)

        # enable ability to click on pixel and do something (can be
        # enabled on-the-fly later as well:

        if allow_pick:
            self.enable_pixel_picker()

        if image is not None:
            self.image = image
        else:
            self.image = np.zeros_like(self.geom.pix_id, dtype=np.float)

        self.norm = norm

    def highlight_pixels(self, pixels, color="g", linewidth=1, alpha=0.75):
        """
        Highlight the given pixels with a colored line around them

        Parameters
        ----------
        pixels : index-like
            The pixels to highlight.
            Can either be a list or array of integers or a
            boolean mask of length number of pixels
        color: a matplotlib conform color
            the color for the pixel highlighting
        linewidth: float
            linewidth of the highlighting in points
        alpha: 0 <= alpha <= 1
            The transparency
        """

        l = np.zeros_like(self.image)
        l[pixels] = linewidth
        self.pixel_highlighting.set_linewidth(l)
        self.pixel_highlighting.set_alpha(alpha)
        self.pixel_highlighting.set_edgecolor(color)
        self._update()

    def enable_pixel_picker(self):
        """ enable ability to click on pixels """
        self.pixels.set_picker(True)  # enable click
        self.pixels.set_pickradius(
            sqrt(u.Quantity(self.geom.pix_area[0]).value) / np.pi)
        self.pixels.set_snap(True)  # snap cursor to pixel center
        self.axes.figure.canvas.mpl_connect("pick_event", self._on_pick)

    def set_limits_minmax(self, zmin, zmax):
        """ set the color scale limits from min to max """
        self.pixels.set_clim(zmin, zmax)
        self.autoscale = False
        self._update()

    def set_limits_percent(self, percent=95):
        """ auto-scale the color range to percent of maximum """
        zmin = np.nanmin(self.pixels.get_array())
        zmax = np.nanmax(self.pixels.get_array())
        dz = zmax - zmin
        frac = percent / 100.0
        self.autoscale = False
        self.set_limits_minmax(zmin, zmax - (1.0 - frac) * dz)

    @property
    def norm(self):
        """
        The norm instance of the Display

        Possible values:

        - "lin": linear scale
        - "log": log scale (cannot have negative values)
        - "symlog": symmetric log scale (negative values are ok)
        -  any matplotlib.colors.Normalize instance, e. g. PowerNorm(gamma=-2)
        """
        return self.pixels.norm

    @norm.setter
    def norm(self, norm):

        if norm == "lin":
            self.pixels.norm = Normalize()
        elif norm == "log":
            self.pixels.norm = LogNorm()
            self.pixels.autoscale()  # this is to handle matplotlib bug #5424
        elif norm == "symlog":
            self.pixels.norm = SymLogNorm(linthresh=1.0)
            self.pixels.autoscale()
        elif isinstance(norm, Normalize):
            self.pixels.norm = norm
        else:
            raise ValueError(
                "Unsupported norm: '{}', options are 'lin',"
                "'log','symlog', or a matplotlib Normalize object".format(
                    norm))

        self.update(force=True)
        self.pixels.autoscale()

    @property
    def cmap(self):
        """
        Color map to use. Either a name or  `matplotlib.colors.ColorMap`
        instance, e.g. from `matplotlib.pyplot.cm`
        """
        return self.pixels.get_cmap()

    @cmap.setter
    def cmap(self, cmap):
        self.pixels.set_cmap(cmap)
        self._update()

    @property
    def image(self):
        """The image displayed on the camera (1D array of pixel values)"""
        return self.pixels.get_array()

    @image.setter
    def image(self, image):
        """
        Change the image displayed on the Camera.

        Parameters
        ----------
        image: array_like
            array of values corresponding to the pixels in the CameraGeometry.
        """
        image = np.asanyarray(image)
        if image.shape != self.geom.pix_x.shape:
            raise ValueError(
                ("Image has a different shape {} than the "
                 "given CameraGeometry {}").format(image.shape,
                                                   self.geom.pix_x.shape))

        self.pixels.set_array(np.ma.masked_invalid(image[self.mask]))
        self.pixels.changed()
        if self.autoscale:
            self.pixels.autoscale()
        self._update()

    def _update(self, force=False):
        """ signal a redraw if autoupdate is turned on """
        if self.autoupdate:
            self.update(force)

    def update(self, force=False):
        """ redraw the display now """
        self.axes.figure.canvas.draw()
        if self.colorbar is not None:
            if force is True:
                self.colorbar.update_bruteforce(self.pixels)
            else:
                self.colorbar.update_normal(self.pixels)
            self.colorbar.draw_all()

    def add_colorbar(self, **kwargs):
        """
        add a colorbar to the camera plot
        kwargs are passed to `figure.colorbar(self.pixels, **kwargs)`
        See matplotlib documentation for the supported kwargs:
        http://matplotlib.org/api/figure_api.html#matplotlib.figure.Figure.colorbar
        """
        if self.colorbar is not None:
            raise ValueError(
                "There is already a colorbar attached to this CameraDisplay")
        else:
            if "ax" not in kwargs:
                kwargs["ax"] = self.axes
            self.colorbar = self.axes.figure.colorbar(self.pixels, **kwargs)
        self.update()

    def add_ellipse(self,
                    centroid,
                    length,
                    width,
                    angle,
                    asymmetry=0.0,
                    **kwargs):
        """
        plot an ellipse on top of the camera

        Parameters
        ----------
        centroid: (float, float)
            position of centroid
        length: float
            major axis
        width: float
            minor axis
        angle: float
            rotation angle wrt x-axis about the centroid, anticlockwise, in radians
        asymmetry: float
            3rd-order moment for directionality if known
        kwargs:
            any MatPlotLib style arguments to pass to the Ellipse patch

        """
        ellipse = Ellipse(
            xy=centroid,
            width=length,
            height=width,
            angle=np.degrees(angle),
            fill=False,
            **kwargs,
        )
        self.axes.add_patch(ellipse)
        self.update()
        return ellipse

    def overlay_moments(self,
                        hillas_parameters,
                        with_label=True,
                        keep_old=False,
                        **kwargs):
        """helper to overlay ellipse from a `HillasParametersContainer` structure

        Parameters
        ----------
        hillas_parameters: `HillasParametersContainer`
            structuring containing Hillas-style parameterization
        with_label: bool
            If True, show coordinates of centroid and width and length
        keep_old: bool
            If True, to not remove old overlays
        kwargs: key=value
            any style keywords to pass to matplotlib (e.g. color='red'
            or linewidth=6)
        """
        if not keep_old:
            self.clear_overlays()

        # strip off any units
        cen_x = u.Quantity(hillas_parameters.x).value
        cen_y = u.Quantity(hillas_parameters.y).value
        length = u.Quantity(hillas_parameters.length).value
        width = u.Quantity(hillas_parameters.width).value

        el = self.add_ellipse(
            centroid=(cen_x, cen_y),
            length=length * 2,
            width=width * 2,
            angle=hillas_parameters.psi.rad,
            **kwargs,
        )

        self._axes_overlays.append(el)

        if with_label:
            text = self.axes.text(
                cen_x,
                cen_y,
                "({:.02f},{:.02f})\n[w={:.02f},l={:.02f}]".format(
                    hillas_parameters.x,
                    hillas_parameters.y,
                    hillas_parameters.width,
                    hillas_parameters.length,
                ),
                color=el.get_edgecolor(),
            )

            self._axes_overlays.append(text)

    def clear_overlays(self):
        """ Remove added overlays from the axes """
        while self._axes_overlays:
            overlay = self._axes_overlays.pop()
            overlay.remove()

    def _on_pick(self, event):
        """ handler for when a pixel is clicked """
        pix_id = event.ind[-1]
        xx, yy, aa = (
            u.Quantity(self.geom.pix_x[pix_id]).value,
            u.Quantity(self.geom.pix_y[pix_id]).value,
            u.Quantity(np.array(self.geom.pix_area)[pix_id]),
        )
        if self.geom.pix_type.startswith("hex"):
            self._active_pixel.xy = (xx, yy)
        else:
            rr = sqrt(aa)
            self._active_pixel.xy = (xx - rr / 2.0, yy - rr / 2.0)
        self._active_pixel.set_visible(True)
        self._active_pixel_label.set_x(xx)
        self._active_pixel_label.set_y(yy)
        self._active_pixel_label.set_text(f"{pix_id:003d}")
        self._active_pixel_label.set_visible(True)
        self._update()
        self.on_pixel_clicked(pix_id)  # call user-function

    def on_pixel_clicked(self, pix_id):
        """virtual function to overide in sub-classes to do something special
        when a pixel is clicked
        """
        print(f"Clicked pixel_id {pix_id}")

    def show(self):
        self.axes.figure.show()
Esempio n. 5
0
class CameraDisplay:

    """
    Camera Display using matplotlib.

    Parameters
    ----------
    geometry : `~ctapipe.instrument.CameraGeometry`
        Definition of the Camera/Image
    image: array_like
        array of values corresponding to the pixels in the CameraGeometry.
    ax : `matplotlib.axes.Axes`
        A matplotlib axes object to plot on, or None to create a new one
    title : str (default "Camera")
        Title to put on camera plot
    norm : str or `matplotlib.color.Normalize` instance (default 'lin')
        Normalization for the color scale.
        Supported str arguments are
        - 'lin': linear scale
        - 'log': logarithmic scale (base 10)
    cmap : str or `matplotlib.colors.Colormap` (default 'hot')
        Color map to use (see `matplotlib.cm`)
    allow_pick : bool (default False)
        if True, allow user to click and select a pixel
    autoupdate : bool (default True)
        redraw automatically (otherwise need to call plt.draw())
    autoscale : bool (default True)
        rescale the vmin/vmax values when the image changes.
        This is set to False if `set_limits_*` is called to explicity
        set data limits.
    antialiased : bool  (default True)
        whether to draw in antialiased mode or not.

    Notes
    -----

    Speed:
        CameraDisplay is not intended to be very fast (matplotlib
        is not a very speed performant graphics library, it is
        intended for nice output plots). However, most of the
        slowness of CameraDisplay is in the constructor.  Once one is
        displayed, changing the image that is displayed is relatively
        fast and efficient. Therefore it is best to initialize an
        instance, and change the data, rather than generating new
        CameraDisplays.

    Pixel Implementation:
        Pixels are rendered as a
        `matplotlib.collections.PatchCollection` of Polygons (either 6
        or 4 sided).  You can access the PatchCollection directly (to
        e.g. change low-level style parameters) via
        `CameraDisplay.pixels`

    Output:
        Since CameraDisplay uses matplotlib, any display can be
        saved to any output file supported via
        plt.savefig(filename). This includes `.pdf` and `.png`.

    """

    def __init__(
            self,
            geometry,
            image=None,
            ax=None,
            title=None,
            norm="lin",
            cmap=None,
            allow_pick=False,
            autoupdate=True,
            autoscale=True,
            antialiased=True,
            ):
        self.axes = ax if ax is not None else plt.gca()
        self.geom = geometry
        self.pixels = None
        self.colorbar = None
        self.autoupdate = autoupdate
        self.autoscale = autoscale
        self._active_pixel = None
        self._active_pixel_label = None

        if title is None:
            title = geometry.cam_id

        # initialize the plot and generate the pixels as a
        # RegularPolyCollection

        patches = []

        if not hasattr(self.geom, "mask"):
            self.geom.mask = np.ones_like(self.geom.pix_x.value, dtype=bool)

        for xx, yy, aa in zip(
            u.Quantity(self.geom.pix_x[self.geom.mask]).value,
            u.Quantity(self.geom.pix_y[self.geom.mask]).value,
            u.Quantity(np.array(self.geom.pix_area)[self.geom.mask]).value):

            if self.geom.pix_type.startswith("hex"):
                rr = sqrt(aa * 2 / 3 / sqrt(3)) + 2*PIXEL_EPSILON
                poly = RegularPolygon(
                    (xx, yy), 6, radius=rr,
                    orientation=self.geom.pix_rotation.rad,
                    fill=True,
                )
            else:
                rr = sqrt(aa) + PIXEL_EPSILON
                poly = Rectangle(
                    (xx-rr/2., yy-rr/2.),
                    width=rr,
                    height=rr,
                    angle=self.geom.pix_rotation.deg,
                    fill=True,
                )

            patches.append(poly)

        self.pixels = PatchCollection(patches, cmap=cmap, linewidth=0)
        self.axes.add_collection(self.pixels)

        self.pixel_highlighting = copy.copy(self.pixels)
        self.pixel_highlighting.set_facecolor('none')
        self.pixel_highlighting.set_linewidth(0)
        self.axes.add_collection(self.pixel_highlighting)

        # Set up some nice plot defaults

        self.axes.set_aspect('equal', 'datalim')
        self.axes.set_title(title)
        self.axes.set_xlabel("X position ({})".format(self.geom.pix_x.unit))
        self.axes.set_ylabel("Y position ({})".format(self.geom.pix_y.unit))
        self.axes.autoscale_view()

        # set up a patch to display when a pixel is clicked (and
        # pixel_picker is enabled):

        self._active_pixel = copy.copy(patches[0])
        self._active_pixel.set_facecolor('r')
        self._active_pixel.set_alpha(0.5)
        self._active_pixel.set_linewidth(2.0)
        self._active_pixel.set_visible(False)
        self.axes.add_patch(self._active_pixel)

        self._active_pixel_label = self.axes.text(self._active_pixel.xy[0],
                                                  self._active_pixel.xy[1],
                                                  "0",
                                                  horizontalalignment='center',
                                                  verticalalignment='center')
        self._active_pixel_label.set_visible(False)

        # enable ability to click on pixel and do something (can be
        # enabled on-the-fly later as well:

        if allow_pick:
            self.enable_pixel_picker()

        if image is not None:
            self.image = image
        else:
            self.image = np.zeros_like(self.geom.pix_id, dtype=np.float)

        self.norm = norm

    def highlight_pixels(self, pixels, color='g', linewidth=1, alpha=0.75):
        '''
        Highlight the given pixels with a colored line around them

        Parameters
        ----------
        pixels : index-like
            The pixels to highlight.
            Can either be a list or array of integers or a
            boolean mask of length number of pixels
        color: a matplotlib conform color
            the color for the pixel highlighting
        linewidth: float
            linewidth of the highlighting in points
        alpha: 0 <= alpha <= 1
            The transparency
        '''

        l = np.zeros_like(self.image)
        l[pixels] = linewidth
        self.pixel_highlighting.set_linewidth(l)
        self.pixel_highlighting.set_alpha(alpha)
        self.pixel_highlighting.set_edgecolor(color)
        self._update()

    def enable_pixel_picker(self):
        """ enable ability to click on pixels """
        self.pixels.set_picker(True)  # enable click
        self.pixels.set_pickradius(sqrt(u.Quantity(self.geom.pix_area[0])
                                        .value) / np.pi)
        self.pixels.set_snap(True)  # snap cursor to pixel center
        self.axes.figure.canvas.mpl_connect('pick_event', self._on_pick)

    def set_limits_minmax(self, zmin, zmax):
        """ set the color scale limits from min to max """
        self.pixels.set_clim(zmin, zmax)
        self.autoscale = False
        self._update()

    def set_limits_percent(self, percent=95):
        """ auto-scale the color range to percent of maximum """
        zmin = self.pixels.get_array().min()
        zmax = self.pixels.get_array().max()
        dz = zmax - zmin
        frac = percent / 100.0
        self.autoscale = False
        self.set_limits_minmax(zmin, zmax - (1.0 - frac) * dz)

    @property
    def norm(self):
        '''
        The norm instance of the Display

        Possible values:

        - "lin": linear scale
        - "log": log scale (cannot have negative values)
        - "symlog": symmetric log scale (negative values are ok)
        -  any matplotlib.colors.Normalize instance, e. g. PowerNorm(gamma=-2)
        '''
        return self.pixels.norm

    @norm.setter
    def norm(self, norm):

        if norm == 'lin':
            self.pixels.norm = Normalize()
        elif norm == 'log':
            self.pixels.norm = LogNorm()
            self.pixels.autoscale()  # this is to handle matplotlib bug #5424
        elif norm == 'symlog':
            self.pixels.norm = SymLogNorm(linthresh=1.0)
            self.pixels.autoscale()
        elif isinstance(norm, Normalize):
            self.pixels.norm = norm
        else:
            raise ValueError("Unsupported norm: '{}', options are 'lin',"
                             "'log','symlog', or a matplotlib Normalize object"
                             .format(norm))

        self.update(force=True)
        self.pixels.autoscale()

    @property
    def cmap(self):
        """
        Color map to use. Either a name or  `matplotlib.colors.ColorMap`
        instance, e.g. from `matplotlib.pyplot.cm`
        """
        return self.pixels.get_cmap()

    @cmap.setter
    def cmap(self, cmap):
        self.pixels.set_cmap(cmap)
        self._update()

    @property
    def image(self):
        """The image displayed on the camera (1D array of pixel values)"""
        return self.pixels.get_array()

    @image.setter
    def image(self, image):
        """
        Change the image displayed on the Camera.

        Parameters
        ----------
        image: array_like
            array of values corresponding to the pixels in the CameraGeometry.
        """
        image = np.asanyarray(image)
        if image.shape != self.geom.pix_x.shape:
            raise ValueError(
                "Image has a different shape {} than the "
                "given CameraGeometry {}"
                .format(image.shape, self.geom.pix_x.shape)
            )

        self.pixels.set_array(image[self.geom.mask])
        self.pixels.changed()
        if self.autoscale:
            self.pixels.autoscale()
        self._update()

    def _update(self, force=False):
        """ signal a redraw if autoupdate is turned on """
        if self.autoupdate:
            self.update(force)

    def update(self, force=False):
        """ redraw the display now """
        self.axes.figure.canvas.draw()
        if self.colorbar is not None:
            if force is True:
                self.colorbar.update_bruteforce(self.pixels)
            else:
                self.colorbar.update_normal(self.pixels)
            self.colorbar.draw_all()

    def add_colorbar(self, **kwargs):
        """
        add a colobar to the camera plot
        kwargs are passed to `figure.colorbar(self.pixels, **kwargs)`
        See matplotlib documentation for the supported kwargs:
        http://matplotlib.org/api/figure_api.html#matplotlib.figure.Figure.colorbar
        """
        if self.colorbar is not None:
            raise ValueError(
                'There is already a colorbar attached to this CameraDisplay'
            )
        else:
            self.colorbar = self.axes.figure.colorbar(self.pixels, **kwargs)
        self.update()

    def add_ellipse(self, centroid, length, width, angle, asymmetry=0.0,
                    **kwargs):
        """
        plot an ellipse on top of the camera

        Parameters
        ----------
        centroid: (float, float)
            position of centroid
        length: float
            major axis
        width: float
            minor axis
        angle: float
            rotation angle wrt x-axis about the centroid, anticlockwise, in radians
        asymmetry: float
            3rd-order moment for directionality if known
        kwargs:
            any MatPlotLib style arguments to pass to the Ellipse patch

        """
        ellipse = Ellipse(xy=centroid, width=length, height=width,
                          angle=np.degrees(angle), fill=False, **kwargs)
        self.axes.add_patch(ellipse)
        self.update()
        return ellipse

    def overlay_moments(self, momparams, with_label=True, **kwargs):
        """helper to overlay ellipse from a `reco.MomentParameters` structure

        Parameters
        ----------
        momparams: `reco.MomentParameters`
            structuring containing Hillas-style parameterization
        kwargs: key=value
            any style keywords to pass to matplotlib (e.g. color='red'
            or linewidth=6)
        """

        # strip off any units
        cen_x = u.Quantity(momparams.cen_x).value
        cen_y = u.Quantity(momparams.cen_y).value
        length = u.Quantity(momparams.length).value
        width = u.Quantity(momparams.width).value


        el = self.add_ellipse(centroid=(cen_x, cen_y),
                              length=length*2,
                              width=width*2, angle=momparams.psi.rad,
                              **kwargs)
        if with_label:
            self.axes.text(cen_x, cen_y,
                           ("({:.02f},{:.02f})\n"
                            "[w={:.02f},l={:.02f}]")
                           .format(momparams.cen_x,
                                   momparams.cen_y,
                                   momparams.width, momparams.length),
                           color=el.get_edgecolor())

    def _on_pick(self, event):
        """ handler for when a pixel is clicked """
        pix_id = event.ind[-1]
        xx, yy, aa = u.Quantity(self.geom.pix_x[pix_id]).value, \
                     u.Quantity(self.geom.pix_y[pix_id]).value, \
                     u.Quantity(np.array(self.geom.pix_area)[pix_id])
        if self.geom.pix_type.startswith("hex"):
            self._active_pixel.xy = (xx, yy)
        else:
            rr = sqrt(aa)
            self._active_pixel.xy = (xx - rr / 2., yy - rr / 2.)
        self._active_pixel.set_visible(True)
        self._active_pixel_label.set_x(xx)
        self._active_pixel_label.set_y(yy)
        self._active_pixel_label.set_text("{:003d}".format(pix_id))
        self._active_pixel_label.set_visible(True)
        self._update()
        self.on_pixel_clicked(pix_id)  # call user-function

    def on_pixel_clicked(self, pix_id):
        """virtual function to overide in sub-classes to do something special
        when a pixel is clicked
        """
        print("Clicked pixel_id {}".format(pix_id))

    def show(self):
        self.axes.figure.show()
Esempio n. 6
0
class SimulationGui(QMainWindow):
    def __init__(self, net=None, parent=None, fname=None):
        QMainWindow.__init__(self)

        self.ui = Ui_SimulationWindow()
        self.ui.setupUi(self)

        if fname:
            self.set_title(fname)

        # context menu
        self.ui.nodeInspector.addAction(self.ui.actionCopyInspectorData)
        self.ui.nodeInspector.addAction(self.ui.actionShowLocalizedSubclusters)
        # callbacks
        self.ui.actionCopyInspectorData.activated\
            .connect(self.on_actionCopyInspectorData_triggered)
        self.ui.actionShowLocalizedSubclusters.activated\
            .connect(self.on_actionShowLocalizedSubclusters_triggered)

        self.dpi = 72
        # take size of networDisplayWidget
        self.fig = Figure((700 / self.dpi, 731 / self.dpi), self.dpi,
                          facecolor='0.9')
        self.canvas = FigureCanvas(self.fig)
        self.canvas.setParent(self.ui.networkDisplayWidget)
        self.nav = NavigationToolbar(self.canvas, self.ui.networkDisplayWidget,
                                     coordinates=True)
        self.nav.setGeometry(QRect(0, 0, 651, 36))
        self.nav.setIconSize(QSize(24, 24))

        self.axes = self.fig.add_subplot(111)
        # matplotlib.org/api/figure_api.html#matplotlib.figure.SubplotParams
        self.fig.subplots_adjust(left=0.03, right=0.99, top=0.92)

        if net:
            self.init_sim(net)

        self.connect(self.ui.showNodes, SIGNAL('stateChanged(int)'),
                     self.refresh_visibility)
        self.connect(self.ui.showEdges, SIGNAL('stateChanged(int)'),
                     self.refresh_visibility)
        self.connect(self.ui.showMessages, SIGNAL('stateChanged(int)'),
                     self.refresh_visibility)
        self.connect(self.ui.showLabels, SIGNAL('stateChanged(int)'),
                     self.refresh_visibility)
        self.connect(self.ui.redrawNetworkButton, SIGNAL('clicked(bool)'),
                     self.redraw)
        self.connect(self.ui.treeGroupBox, SIGNAL('toggled(bool)'),
                     self.refresh_visibility)
        self.connect(self.ui.treeKey, SIGNAL('textEdited(QString)'),
                     self.redraw)
        self.connect(self.ui.propagationError, SIGNAL('toggled(bool)'),
                     self.refresh_visibility)
        self.connect(self.ui.locKey, SIGNAL('textEdited(QString)'),
                     self.redraw)
        # callbacks
        self.ui.actionOpenNetwork.activated\
            .connect(self.on_actionOpenNetwork_triggered)
        self.ui.actionSaveNetwork.activated\
            .connect(self.on_actionSaveNetwork_triggered)
        self.ui.actionRun.activated.connect(self.on_actionRun_triggered)
        self.ui.actionStep.activated.connect(self.on_actionStep_triggered)
        self.ui.actionReset.activated.connect(self.on_actionReset_triggered)

        self.canvas.mpl_connect('pick_event', self.on_pick)

    def handleInspectorMenu(self, pos):
        menu = QMenu()
        menu.addAction('Add')
        menu.addAction('Delete')
        menu.exec_(QCursor.pos())

    def init_sim(self, net):
        self.net = net
        self.sim = Simulation(net)
        self.connect(self.sim, SIGNAL("redraw()"), self.redraw)
        self.connect(self.sim, SIGNAL("updateLog(QString)"), self.update_log)
        self.redraw()

    def update_log(self, text):
        """ Add item to list widget """
        print "Add: " + text
        self.ui.logListWidget.insertItem(0, text)
        # self.ui.logListWidget.sortItems()

    def redraw(self):
        self.refresh_network_inspector()
        self.draw_network()
        self.reset_zoom()
        self.refresh_visibility()

    def draw_network(self, net=None, clear=True, subclusters=None,
                     drawMessages=True):
        if not net:
            net = self.net
        currentAlgorithm = self.net.get_current_algorithm()
        if clear:
            self.axes.clear()
        self.axes.imshow(net.environment.im, vmin=0, cmap='binary_r',
                         origin='lower')

        self.draw_tree(str(self.ui.treeKey.text()), net)
        self.draw_edges(net)
        self.draw_propagation_errors(str(self.ui.locKey.text()), net)
        if subclusters:
            node_colors = self.get_node_colors(net, subclusters=subclusters)
        else:
            node_colors = self.get_node_colors(net, algorithm=currentAlgorithm)
        self.node_collection = self.draw_nodes(net, node_colors)
        if drawMessages:
            self.draw_messages(net)
        self.draw_labels(net)
        self.drawnNet = net
        step_text = ' (step %d)' % self.net.algorithmState['step'] \
                    if isinstance(currentAlgorithm, NodeAlgorithm) else ''
        self.axes.set_title((currentAlgorithm.name
                             if currentAlgorithm else '') + step_text)

        self.refresh_visibility()
        # To save multiple figs of the simulation uncomment next two lines:
        #self.fig.savefig('network-alg-%d-step-%d.png' %
        #                 (self.net.algorithmState['index'], self.net.algorithmState['step']))

    def draw_nodes(self, net=None, node_colors={}, node_radius={}):
        if not net:
            net = self.net
        if type(node_colors) == str:
            node_colors = {node: node_colors for node in net.nodes()}
        nodeCircles = []
        for n in net.nodes():
            c = NodeCircle(tuple(net.pos[n]), node_radius.get(n, 8.0),
                           color=node_colors.get(n, 'r'),
                       ec='k', lw=1.0, ls='solid', picker=3)
            nodeCircles.append(c)
        node_collection = PatchCollection(nodeCircles, match_original=True)
        node_collection.set_picker(3)
        self.axes.add_collection(node_collection)
        return node_collection

    def get_node_colors(self, net, algorithm=None, subclusters=None,
                        drawLegend=True):
            COLORS = 'rgbcmyw' * 100
            node_colors = {}
            if algorithm:
                color_map = {}
                if isinstance(algorithm, NodeAlgorithm):
                    for ind, status in enumerate(algorithm.STATUS.keys()):
                        if status == 'IDLE':
                            color_map.update({status: 'k'})
                        else:
                            color_map.update({status: COLORS[ind]})
                    if drawLegend:
                        proxy = []
                        labels = []
                        for status, color in color_map.items():
                            proxy.append(Circle((0, 0), radius=8.0,
                                                color=color, ec='k',
                                                lw=1.0, ls='solid'))
                            labels.append(status)
                        self.fig.legends = []
                        self.fig.legend(proxy, labels, loc=8,
                                        prop={'size': '10.0'}, ncol=len(proxy),
                                        title='Statuses for %s:'
                                                % algorithm.name)
                for n in net.nodes():
                    if n.status == '' or not n.status in color_map.keys():
                        node_colors[n] = 'r'
                    else:
                        node_colors[n] = color_map[n.status]
            elif subclusters:
                for i, sc in enumerate(subclusters):
                    for n in sc:
                        if n in node_colors:
                            node_colors[n] = 'k'
                        else:
                            node_colors[n] = COLORS[i]
            return node_colors

    def draw_edges(self, net=None):
        if not net:
            net = self.net
        self.edge_collection = nx.draw_networkx_edges(net, net.pos, alpha=0.6,
                                                      edgelist=None,
                                                      ax=self.axes)

    def draw_messages(self, net=None):
        if not net:
            net = self.net
        self.messages = []
        msgCircles = []
        for node in net.nodes():
            for msg in node.outbox:
                # broadcast
                if msg.destination is None:
                    for neighbor in net.adj[node].keys():
                        nbr_msg = msg.copy()
                        nbr_msg.destination = neighbor
                        c = MessageCircle(nbr_msg, net, 'out', 3.0, lw=0,
                                          picker=3, zorder=3, color='b')
                        self.messages.append(nbr_msg)
                        msgCircles.append(c)
                else:
                    c = MessageCircle(msg, net, 'out', 3.0, lw=0, picker=3,
                                      zorder=3, color='b')
                    self.messages.append(msg)
                    msgCircles.append(c)
            for msg in node.inbox:
                c = MessageCircle(msg, net, 'in', 3.0, lw=0, picker=3,
                                  zorder=3, color='g')
                self.messages.append(msg)
                msgCircles.append(c)
        if self.messages:
            self.message_collection = PatchCollection(msgCircles,
                                                      match_original=True)
            self.message_collection.set_picker(3)
            self.axes.add_collection(self.message_collection)

    def draw_labels(self, net=None):
        if not net:
            net = self.net
        label_pos = {}
        for n in net.nodes():
            label_pos[n] = net.pos[n].copy() + 10
        self.label_collection = nx.draw_networkx_labels(net, label_pos,
                                                        labels=net.labels,
                                                        ax=self.axes)

    def refresh_visibility(self):
        try:
            self.node_collection.set_visible(self.ui.showNodes.isChecked())
            self.edge_collection.set_visible(self.ui.showEdges.isChecked())
            for label in self.label_collection.values():
                label.set_visible(self.ui.showLabels.isChecked())
            self.tree_collection.set_visible(self.ui.treeGroupBox.isChecked())
            self.ini_error_collection.set_visible(self.ui.propagationError\
                                                    .isChecked())
            self.propagation_error_collection.set_visible(self.ui\
                                                          .propagationError\
                                                          .isChecked())
            # sould be last, sometimes there are no messages
            self.message_collection.set_visible(self.ui.showMessages\
                                                    .isChecked())
        except AttributeError:
            print 'Refresh visibility warning'
        self.canvas.draw()

    def draw_tree(self, treeKey, net=None):
        """
        Show tree representation of network.

        Attributes:
            treeKey (str):
                key in nodes memory (dictionary) where tree data is stored
                storage format can be a list off tree neighbors or a dict:
                    {'parent': parent_node,
                     'children': [child_node1, child_node2 ...]}
        """
        if not net:
            net = self.net
        treeNet = net.get_tree_net(treeKey)
        if treeNet:
            self.tree_collection = draw_networkx_edges(treeNet, treeNet.pos,
                                                       treeNet.edges(),
                                                       width=1.8, alpha=0.6,
                                                       ax=self.axes)

    def draw_propagation_errors(self, locKey, net):
        SCALE_FACTOR = 0.6
        if not net:
            net = self.net
        if any([not locKey in node.memory for node in net.nodes()]):
            self.propagation_error_collection = []
            self.ini_error_collection = []
            return

        rms = {'iniRms': {}, 'stitchRms': {}}
        for node in net.nodes():
            rms['iniRms'][node] = get_rms(self.net.pos,
                                          (node.memory['iniLocs']), True) * \
                                          SCALE_FACTOR
            rms['stitchRms'][node] = get_rms(self.net.pos, node.memory[locKey],
                                             True) * SCALE_FACTOR
        self.propagation_error_collection = \
                            self.draw_nodes(net=net, node_colors='g',
                                            node_radius=rms['stitchRms'])
        self.ini_error_collection = self.draw_nodes(net=net, node_colors='b',
                                                    node_radius=rms['iniRms'])

    def reset_zoom(self):
        self.axes.set_xlim((0, self.net.environment.im.shape[1]))
        self.axes.set_ylim((0, self.net.environment.im.shape[0]))

    def set_title(self, fname):
        new = ' - '.join([str(self.windowTitle()).split(' - ')[0], str(fname)])
        self.setWindowTitle(new)

    def refresh_network_inspector(self):
        niModel = DictionaryTreeModel(dic=self.net.get_dic())
        self.ui.networkInspector.setModel(niModel)
        self.ui.networkInspector.expandToDepth(0)

    """
    Callbacks
    """

    def on_actionRun_triggered(self):
        self.ui.logListWidget.clear()
        print 'running ...',
        self.sim.stepping = True
        self.sim.run_all()

    def on_actionStep_triggered(self):
        print 'next step ...',
        self.sim.run(self.ui.stepSize.value())

    def on_actionReset_triggered(self):
        print 'reset ...',
        self.sim.reset()
        self.redraw()

    def on_actionCopyInspectorData_triggered(self):
        string = 'Node inspector data\n-------------------'
        # raise()
        for qModelIndex in self.ui.nodeInspector.selectedIndexes():
            string += '\n' + qModelIndex.internalPointer().toString('    ')

        clipboard = app.clipboard()
        clipboard.setText(string)
        event = QEvent(QEvent.Clipboard)
        app.sendEvent(clipboard, event)

    def on_actionShowLocalizedSubclusters_triggered(self):
        if len(self.ui.nodeInspector.selectedIndexes()) == 1:
            qModelIndex = self.ui.nodeInspector.selectedIndexes()[0]
            treeItem = qModelIndex.internalPointer()
            assert(isinstance(treeItem.itemDataValue, Positions))

            estimated = deepcopy(treeItem.itemDataValue)
            estimatedsub = estimated.subclusters[0]
            # rotate, translate and optionally scale
            # w.r.t. original positions (pos)
            align_clusters(Positions.create(self.net.pos), estimated, True)
            net = self.net.subnetwork(estimatedsub.keys(), pos=estimatedsub)

            self.draw_network(net=net, drawMessages=False)

            edge_pos = numpy.asarray([(self.net.pos[node], estimatedsub[node][:2])
                                       for node in net])
            error_collection = LineCollection(edge_pos, colors='r')
            self.axes.add_collection(error_collection)

            rms = get_rms(self.net.pos, estimated, scale=False)
            self.update_log('rms = %.3f' % rms)
            self.update_log('localized = %.2f%% (%d/%d)' %
                            (len(estimatedsub) * 1. / len(self.net.pos) * 100,
                            len(estimatedsub), len(self.net.pos)))

    def on_actionSaveNetwork_triggered(self, *args):
        default_filetype = 'gz'
        start = datetime.now().strftime('%Y%m%d') + default_filetype

        filters = ['Network pickle (*.gz)', 'All files (*)']
        selectedFilter = 'Network pickle (gz)'
        filters = ';;'.join(filters)

        fname = QFileDialog.getSaveFileName(self, "Choose a filename",
                                            start, filters, selectedFilter)[0]
        if fname:
            try:
                write_pickle(self.net, fname)
            except Exception, e:
                QMessageBox.critical(
                    self, "Error saving file", str(e),
                    QMessageBox.Ok, QMessageBox.NoButton)
            else:
                self.set_title(fname)
Esempio n. 7
0
class SimulationGui(QMainWindow):
    def __init__(self, net=None, parent=None, fname=None):
        QMainWindow.__init__(self)

        self.ui = Ui_SimulationWindow()
        self.ui.setupUi(self)

        if fname:
            self.set_title(fname)

        # context menu
        self.ui.nodeInspector.addAction(self.ui.actionCopyInspectorData)
        self.ui.nodeInspector.addAction(self.ui.actionShowLocalizedSubclusters)
        # callbacks
        self.ui.actionCopyInspectorData.activated\
            .connect(self.on_actionCopyInspectorData_triggered)
        self.ui.actionShowLocalizedSubclusters.activated\
            .connect(self.on_actionShowLocalizedSubclusters_triggered)

        self.dpi = 72
        # take size of networDisplayWidget
        self.fig = Figure((700 / self.dpi, 731 / self.dpi),
                          self.dpi,
                          facecolor='0.9')
        self.canvas = FigureCanvas(self.fig)
        self.canvas.setParent(self.ui.networkDisplayWidget)
        self.nav = NavigationToolbar(self.canvas,
                                     self.ui.networkDisplayWidget,
                                     coordinates=True)
        self.nav.setGeometry(QRect(0, 0, 651, 36))
        self.nav.setIconSize(QSize(24, 24))

        self.axes = self.fig.add_subplot(111)
        # matplotlib.org/api/figure_api.html#matplotlib.figure.SubplotParams
        self.fig.subplots_adjust(left=0.03, right=0.99, top=0.92)

        if net:
            self.init_sim(net)

        self.connect(self.ui.showNodes, SIGNAL('stateChanged(int)'),
                     self.refresh_visibility)
        self.connect(self.ui.showEdges, SIGNAL('stateChanged(int)'),
                     self.refresh_visibility)
        self.connect(self.ui.showMessages, SIGNAL('stateChanged(int)'),
                     self.refresh_visibility)
        self.connect(self.ui.showLabels, SIGNAL('stateChanged(int)'),
                     self.refresh_visibility)
        self.connect(self.ui.redrawNetworkButton, SIGNAL('clicked(bool)'),
                     self.redraw)
        self.connect(self.ui.treeGroupBox, SIGNAL('toggled(bool)'),
                     self.refresh_visibility)
        self.connect(self.ui.treeKey, SIGNAL('textEdited(QString)'),
                     self.redraw)
        self.connect(self.ui.propagationError, SIGNAL('toggled(bool)'),
                     self.refresh_visibility)
        self.connect(self.ui.locKey, SIGNAL('textEdited(QString)'),
                     self.redraw)
        # callbacks
        self.ui.actionOpenNetwork.activated\
            .connect(self.on_actionOpenNetwork_triggered)
        self.ui.actionSaveNetwork.activated\
            .connect(self.on_actionSaveNetwork_triggered)
        self.ui.actionRun.activated.connect(self.on_actionRun_triggered)
        self.ui.actionStep.activated.connect(self.on_actionStep_triggered)
        self.ui.actionReset.activated.connect(self.on_actionReset_triggered)

        self.canvas.mpl_connect('pick_event', self.on_pick)

    def handleInspectorMenu(self, pos):
        menu = QMenu()
        menu.addAction('Add')
        menu.addAction('Delete')
        menu.exec_(QCursor.pos())

    def init_sim(self, net):
        self.net = net
        self.sim = Simulation(net)
        self.connect(self.sim, SIGNAL("redraw()"), self.redraw)
        self.connect(self.sim, SIGNAL("updateLog(QString)"), self.update_log)
        self.redraw()

    def update_log(self, text):
        """ Add item to list widget """
        print "Add: " + text
        self.ui.logListWidget.insertItem(0, text)
        # self.ui.logListWidget.sortItems()

    def redraw(self):
        self.refresh_network_inspector()
        self.draw_network()
        self.reset_zoom()
        self.refresh_visibility()

    def draw_network(self,
                     net=None,
                     clear=True,
                     subclusters=None,
                     drawMessages=True):
        if not net:
            net = self.net
        currentAlgorithm = self.net.get_current_algorithm()
        if clear:
            self.axes.clear()
        self.axes.imshow(net.environment.im,
                         vmin=0,
                         cmap='binary_r',
                         origin='lower')

        self.draw_tree(str(self.ui.treeKey.text()), net)
        self.draw_edges(net)
        self.draw_propagation_errors(str(self.ui.locKey.text()), net)
        if subclusters:
            node_colors = self.get_node_colors(net, subclusters=subclusters)
        else:
            node_colors = self.get_node_colors(net, algorithm=currentAlgorithm)
        self.node_collection = self.draw_nodes(net, node_colors)
        if drawMessages:
            self.draw_messages(net)
        self.draw_labels(net)
        self.drawnNet = net
        step_text = ' (step %d)' % self.net.algorithmState['step'] \
                    if isinstance(currentAlgorithm, NodeAlgorithm) else ''
        self.axes.set_title(
            (currentAlgorithm.name if currentAlgorithm else '') + step_text)

        self.refresh_visibility()
        # To save multiple figs of the simulation uncomment next two lines:
        #self.fig.savefig('network-alg-%d-step-%d.png' %
        #                 (self.net.algorithmState['index'], self.net.algorithmState['step']))

    def draw_nodes(self, net=None, node_colors={}, node_radius={}):
        if not net:
            net = self.net
        if type(node_colors) == str:
            node_colors = {node: node_colors for node in net.nodes()}
        nodeCircles = []
        for n in net.nodes():
            c = NodeCircle(tuple(net.pos[n]),
                           node_radius.get(n, 8.0),
                           color=node_colors.get(n, 'r'),
                           ec='k',
                           lw=1.0,
                           ls='solid',
                           picker=3)
            nodeCircles.append(c)
        node_collection = PatchCollection(nodeCircles, match_original=True)
        node_collection.set_picker(3)
        self.axes.add_collection(node_collection)
        return node_collection

    def get_node_colors(self,
                        net,
                        algorithm=None,
                        subclusters=None,
                        drawLegend=True):
        COLORS = 'rgbcmyw' * 100
        node_colors = {}
        if algorithm:
            color_map = {}
            if isinstance(algorithm, NodeAlgorithm):
                for ind, status in enumerate(algorithm.STATUS.keys()):
                    if status == 'IDLE':
                        color_map.update({status: 'k'})
                    else:
                        color_map.update({status: COLORS[ind]})
                if drawLegend:
                    proxy = []
                    labels = []
                    for status, color in color_map.items():
                        proxy.append(
                            Circle((0, 0),
                                   radius=8.0,
                                   color=color,
                                   ec='k',
                                   lw=1.0,
                                   ls='solid'))
                        labels.append(status)
                    self.fig.legends = []
                    self.fig.legend(proxy,
                                    labels,
                                    loc=8,
                                    prop={'size': '10.0'},
                                    ncol=len(proxy),
                                    title='Statuses for %s:' % algorithm.name)
            for n in net.nodes():
                if n.status == '' or not n.status in color_map.keys():
                    node_colors[n] = 'r'
                else:
                    node_colors[n] = color_map[n.status]
        elif subclusters:
            for i, sc in enumerate(subclusters):
                for n in sc:
                    if n in node_colors:
                        node_colors[n] = 'k'
                    else:
                        node_colors[n] = COLORS[i]
        return node_colors

    def draw_edges(self, net=None):
        if not net:
            net = self.net
        self.edge_collection = nx.draw_networkx_edges(net,
                                                      net.pos,
                                                      alpha=0.6,
                                                      edgelist=None,
                                                      ax=self.axes)

    def draw_messages(self, net=None):
        if not net:
            net = self.net
        self.messages = []
        msgCircles = []
        for node in net.nodes():
            for msg in node.outbox:
                # broadcast
                if msg.destination is None:
                    for neighbor in net.adj[node].keys():
                        nbr_msg = msg.copy()
                        nbr_msg.destination = neighbor
                        c = MessageCircle(nbr_msg,
                                          net,
                                          'out',
                                          3.0,
                                          lw=0,
                                          picker=3,
                                          zorder=3,
                                          color='b')
                        self.messages.append(nbr_msg)
                        msgCircles.append(c)
                else:
                    c = MessageCircle(msg,
                                      net,
                                      'out',
                                      3.0,
                                      lw=0,
                                      picker=3,
                                      zorder=3,
                                      color='b')
                    self.messages.append(msg)
                    msgCircles.append(c)
            for msg in node.inbox:
                c = MessageCircle(msg,
                                  net,
                                  'in',
                                  3.0,
                                  lw=0,
                                  picker=3,
                                  zorder=3,
                                  color='g')
                self.messages.append(msg)
                msgCircles.append(c)
        if self.messages:
            self.message_collection = PatchCollection(msgCircles,
                                                      match_original=True)
            self.message_collection.set_picker(3)
            self.axes.add_collection(self.message_collection)

    def draw_labels(self, net=None):
        if not net:
            net = self.net
        label_pos = {}
        for n in net.nodes():
            label_pos[n] = net.pos[n].copy() + 10
        self.label_collection = nx.draw_networkx_labels(net,
                                                        label_pos,
                                                        labels=net.labels,
                                                        ax=self.axes)

    def refresh_visibility(self):
        try:
            self.node_collection.set_visible(self.ui.showNodes.isChecked())
            self.edge_collection.set_visible(self.ui.showEdges.isChecked())
            for label in self.label_collection.values():
                label.set_visible(self.ui.showLabels.isChecked())
            self.tree_collection.set_visible(self.ui.treeGroupBox.isChecked())
            self.ini_error_collection.set_visible(self.ui.propagationError\
                                                    .isChecked())
            self.propagation_error_collection.set_visible(self.ui\
                                                          .propagationError\
                                                          .isChecked())
            # sould be last, sometimes there are no messages
            self.message_collection.set_visible(self.ui.showMessages\
                                                    .isChecked())
        except AttributeError:
            print 'Refresh visibility warning'
        self.canvas.draw()

    def draw_tree(self, treeKey, net=None):
        """
        Show tree representation of network.

        Attributes:
            treeKey (str):
                key in nodes memory (dictionary) where tree data is stored
                storage format can be a list off tree neighbors or a dict:
                    {'parent': parent_node,
                     'children': [child_node1, child_node2 ...]}
        """
        if not net:
            net = self.net
        treeNet = net.get_tree_net(treeKey)
        if treeNet:
            self.tree_collection = draw_networkx_edges(treeNet,
                                                       treeNet.pos,
                                                       treeNet.edges(),
                                                       width=1.8,
                                                       alpha=0.6,
                                                       ax=self.axes)

    def draw_propagation_errors(self, locKey, net):
        SCALE_FACTOR = 0.6
        if not net:
            net = self.net
        if any([not locKey in node.memory for node in net.nodes()]):
            self.propagation_error_collection = []
            self.ini_error_collection = []
            return

        rms = {'iniRms': {}, 'stitchRms': {}}
        for node in net.nodes():
            rms['iniRms'][node] = get_rms(self.net.pos,
                                          (node.memory['iniLocs']), True) * \
                                          SCALE_FACTOR
            rms['stitchRms'][node] = get_rms(self.net.pos, node.memory[locKey],
                                             True) * SCALE_FACTOR
        self.propagation_error_collection = \
                            self.draw_nodes(net=net, node_colors='g',
                                            node_radius=rms['stitchRms'])
        self.ini_error_collection = self.draw_nodes(net=net,
                                                    node_colors='b',
                                                    node_radius=rms['iniRms'])

    def reset_zoom(self):
        self.axes.set_xlim((0, self.net.environment.im.shape[1]))
        self.axes.set_ylim((0, self.net.environment.im.shape[0]))

    def set_title(self, fname):
        new = ' - '.join([str(self.windowTitle()).split(' - ')[0], str(fname)])
        self.setWindowTitle(new)

    def refresh_network_inspector(self):
        niModel = DictionaryTreeModel(dic=self.net.get_dic())
        self.ui.networkInspector.setModel(niModel)
        self.ui.networkInspector.expandToDepth(0)

    """
    Callbacks
    """

    def on_actionRun_triggered(self):
        self.ui.logListWidget.clear()
        print 'running ...',
        self.sim.stepping = True
        self.sim.run_all()

    def on_actionStep_triggered(self):
        print 'next step ...',
        self.sim.run(self.ui.stepSize.value())

    def on_actionReset_triggered(self):
        print 'reset ...',
        self.sim.reset()
        self.redraw()

    def on_actionCopyInspectorData_triggered(self):
        string = 'Node inspector data\n-------------------'
        # raise()
        for qModelIndex in self.ui.nodeInspector.selectedIndexes():
            string += '\n' + qModelIndex.internalPointer().toString('    ')

        clipboard = app.clipboard()
        clipboard.setText(string)
        event = QEvent(QEvent.Clipboard)
        app.sendEvent(clipboard, event)

    def on_actionShowLocalizedSubclusters_triggered(self):
        if len(self.ui.nodeInspector.selectedIndexes()) == 1:
            qModelIndex = self.ui.nodeInspector.selectedIndexes()[0]
            treeItem = qModelIndex.internalPointer()
            assert (isinstance(treeItem.itemDataValue, Positions))

            estimated = deepcopy(treeItem.itemDataValue)
            estimatedsub = estimated.subclusters[0]
            # rotate, translate and optionally scale
            # w.r.t. original positions (pos)
            align_clusters(Positions.create(self.net.pos), estimated, True)
            net = self.net.subnetwork(estimatedsub.keys(), pos=estimatedsub)

            self.draw_network(net=net, drawMessages=False)

            edge_pos = numpy.asarray([
                (self.net.pos[node], estimatedsub[node][:2]) for node in net
            ])
            error_collection = LineCollection(edge_pos, colors='r')
            self.axes.add_collection(error_collection)

            rms = get_rms(self.net.pos, estimated, scale=False)
            self.update_log('rms = %.3f' % rms)
            self.update_log('localized = %.2f%% (%d/%d)' %
                            (len(estimatedsub) * 1. / len(self.net.pos) * 100,
                             len(estimatedsub), len(self.net.pos)))

    def on_actionSaveNetwork_triggered(self, *args):
        default_filetype = 'gz'
        start = datetime.now().strftime('%Y%m%d') + default_filetype

        filters = ['Network pickle (*.gz)', 'All files (*)']
        selectedFilter = 'Network pickle (gz)'
        filters = ';;'.join(filters)

        fname = QFileDialog.getSaveFileName(self, "Choose a filename", start,
                                            filters, selectedFilter)[0]
        if fname:
            try:
                write_pickle(self.net, fname)
            except Exception, e:
                QMessageBox.critical(self, "Error saving file", str(e),
                                     QMessageBox.Ok, QMessageBox.NoButton)
            else:
                self.set_title(fname)
Esempio n. 8
0
class CameraDisplay:

    """Camera Display using matplotlib.

    Parameters
    ----------
    geometry : `~ctapipe.io.CameraGeometry`
        Definition of the Camera/Image
    axis : `matplotlib.axes.Axes`
        A matplotlib axes object to plot on, or None to create a new one
    title : str
        Title to put on camera plot
    allow_pick : bool (default False)
        if True, allow user to click and select a pixel
    autoupdate : bool (default True)
        redraw automatically (otherwise need to call plt.draw())
    antialiased : bool  (default True)
        whether to draw in antialiased mode or not.

    Notes
    -----

    Speed: 
        CameraDisplay is not intended to be very fast (matplotlib
        is not a very speed performant graphics library, it is
        intended for nice output plots). However, most of the
        slowness of CameraDisplay is in the constructor.  Once one is
        displayed, changing the image that is displayed is relatively
        fast and efficient. Therefore it is best to initialize an
        instance, and change the data, rather than generating new
        CameraDisplays.

    Pixel Implementation: 
        Pixels are rendered as a
        `matplotlib.collections.PatchCollection` of Polygons (either 6
        or 4 sided).  You can access the PatchCollection directly (to
        e.g. change low-level style parameters) via
        `CameraDisplay.pixels`

    Output: 
        Since CameraDisplay uses matplotlib, any display can be
        saved to any output file supported via
        plt.savefig(filename). This includes `.pdf` and `.png`.

    """

    def __init__(self, geometry, axes=None, title="Camera",
                 allow_pick=False, autoupdate=True, antialiased=True):
        self.axes = axes if axes is not None else plt.gca()
        self.geom = geometry
        self.pixels = None
        self.cmap = plt.cm.jet
        self.autoupdate = autoupdate
        self._active_pixel = None
        self._active_pixel_label = None
        
        # initialize the plot and generate the pixels as a
        # RegularPolyCollection

        patches = []

        for xx, yy, aa in zip(u.Quantity(self.geom.pix_x).value,
                              u.Quantity(self.geom.pix_y).value,
                              u.Quantity(np.array(self.geom.pix_area))):
            if self.geom.pix_type.startswith("hex"):
                rr = sqrt(aa * 2 / 3 / sqrt(3))
                poly = RegularPolygon((xx, yy), 6, radius=rr,
                                      orientation=np.radians(0),
                                      fill=True)
            else:
                rr = sqrt(aa) * sqrt(2)
                poly = Rectangle((xx, yy), width=rr, height=rr,
                                 angle=np.radians(0),
                                 fill=True)

            patches.append(poly)

        self.pixels = PatchCollection(patches, cmap=self.cmap, linewidth=0)
        self.axes.add_collection(self.pixels)
        
        # Set up some nice plot defaults
        
        self.axes.set_aspect('equal', 'datalim')
        self.axes.set_title(title)
        self.axes.set_xlabel("X position ({})".format(self.geom.pix_x.unit))
        self.axes.set_ylabel("Y position ({})".format(self.geom.pix_y.unit))
        self.axes.autoscale_view()

        # set up a patch to display when a pixel is clicked (and
        # pixel_picker is enabled):
        
        self._active_pixel = copy.copy(patches[0])
        self._active_pixel.set_facecolor('r')
        self._active_pixel.set_alpha(0.5)
        self._active_pixel.set_linewidth(2.0)
        self._active_pixel.set_visible(False)
        self.axes.add_patch(self._active_pixel)
        
        self._active_pixel_label = plt.text(self._active_pixel.xy[0],
                                            self._active_pixel.xy[1],
                                            "0",
                                            horizontalalignment='center',
                                            verticalalignment='center')
        self._active_pixel_label.set_visible(False)
        
        # enable ability to click on pixel and do something (can be
        # enabled on-the-fly later as well:
        
        if allow_pick:
            self.enable_pixel_picker()

    def enable_pixel_picker(self):
        """ enable ability to click on pixels """
        self.pixels.set_picker(True)  # enable click
        self.pixels.set_pickradius(sqrt(u.Quantity(self.geom.pix_area[0])
                                        .value) / np.pi)
        self.pixels.set_snap(True)  # snap cursor to pixel center
        self.axes.figure.canvas.mpl_connect('pick_event', self._on_pick)

    def set_cmap(self, cmap):
        """ Change the color map 

        Parameters
        ----------
        self: type
            description
        cmap: `matplotlib.colors.ColorMap`
            a color map, e.g. from `matplotlib.pyplot.cm.*`
        """
        self.pixels.set_cmap(cmap)

    def set_image(self, image):
        """
        Change the image displayed on the Camera.

        Parameters
        ----------
        image: array_like
            array of values corresponding to the pixels in the CameraGeometry.
        """
        image = np.asanyarray(image)
        if image.shape != self.geom.pix_x.shape:
            raise ValueError("Image has a different shape {} than the"
                             "given CameraGeometry {}"
                             .format(image.shape, self.geom.pix_x.shape))
        self.pixels.set_array(image)
        self.update()

    def update(self):
        """ signal a redraw if necessary """
        if self.autoupdate:
            plt.draw()

    def add_colorbar(self):
        """ add a colobar to the camera plot """
        self.axes.figure.colorbar(self.pixels)

    def add_ellipse(self, centroid, length, width, angle, asymmetry=0.0,
                    **kwargs):
        """
        plot an ellipse on top of the camera

        Parameters
        ----------
        centroid: (float,float)
            position of centroid
        length: float
            major axis
        width: float
            minor axis
        angle: float
            rotation angle wrt "up" about the centroid, clockwise, in radians
        asymmetry: float
            3rd-order moment for directionality if known
        kwargs:
            any MatPlotLib style arguments to pass to the Ellipse patch

        """
        ellipse = Ellipse(xy=centroid, width=width, height=length,
                          angle=np.degrees(angle), fill=False, **kwargs)
        self.axes.add_patch(ellipse)
        self.update()
        return ellipse

    def overlay_moments(self, momparams, **kwargs):
        """helper to overlay ellipse from a `reco.MomentParameters` structure

        Parameters
        ----------
        momparams: `reco.MomentParameters`
            structuring containing Hillas-style parameterization
        kwargs: key=value
            any style keywords to pass to matplotlib (e.g. color='red'
            or linewidth=6)
        """

        el = self.add_ellipse(centroid=(momparams.cen_x, momparams.cen_y),
                              length=momparams.length,
                              width=momparams.width, angle=momparams.psi,
                              **kwargs)
        self.axes.text(momparams.cen_x, momparams.cen_y,
                       ("({:.02f},{:.02f})\n"
                        "[w={:.02f},l={:.02f}]")
                       .format(momparams.cen_x,
                               momparams.cen_y,
                               momparams.width, momparams.length),
                       color=el.get_edgecolor())

    def _on_pick(self, event):
        """ handler for when a pixel is clicked """
        pix_id = event.ind.pop()
        xx, yy = u.Quantity(self.geom.pix_x[pix_id]).value,\
                 u.Quantity(self.geom.pix_y[pix_id]).value
        self._active_pixel.xy = (xx, yy)
        self._active_pixel.set_visible(True)
        self._active_pixel_label.set_x(xx)
        self._active_pixel_label.set_y(yy)
        self._active_pixel_label.set_text("{:003d}".format(pix_id))
        self._active_pixel_label.set_visible(True)
        self.update()
        self.on_pixel_clicked(pix_id)  # call user-function

    def on_pixel_clicked(self, pix_id):
        """virtual function to overide in sub-classes to do something special
        when a pixel is clicked
        """
        print("Clicked pixel_id {}".format(pix_id))
Esempio n. 9
0
class CameraDisplay:

    """Camera Display using matplotlib.

    Parameters
    ----------
    geometry : `~ctapipe.io.CameraGeometry`
        Definition of the Camera/Image
    image: array_like
        array of values corresponding to the pixels in the CameraGeometry.
    ax : `matplotlib.axes.Axes`
        A matplotlib axes object to plot on, or None to create a new one
    title : str
        Title to put on camera plot
    allow_pick : bool (default False)
        if True, allow user to click and select a pixel
    autoupdate : bool (default True)
        redraw automatically (otherwise need to call plt.draw())
    antialiased : bool  (default True)
        whether to draw in antialiased mode or not.

    Notes
    -----

    Speed:
        CameraDisplay is not intended to be very fast (matplotlib
        is not a very speed performant graphics library, it is
        intended for nice output plots). However, most of the
        slowness of CameraDisplay is in the constructor.  Once one is
        displayed, changing the image that is displayed is relatively
        fast and efficient. Therefore it is best to initialize an
        instance, and change the data, rather than generating new
        CameraDisplays.

    Pixel Implementation:
        Pixels are rendered as a
        `matplotlib.collections.PatchCollection` of Polygons (either 6
        or 4 sided).  You can access the PatchCollection directly (to
        e.g. change low-level style parameters) via
        `CameraDisplay.pixels`

    Output:
        Since CameraDisplay uses matplotlib, any display can be
        saved to any output file supported via
        plt.savefig(filename). This includes `.pdf` and `.png`.

    """

    def __init__(self, geometry, image=None, ax=None, title="Camera",
                 allow_pick=False, autoupdate=True, antialiased=True):
        self.axes = ax if ax is not None else plt.gca()
        self.geom = geometry
        self.pixels = None
        self.autoupdate = autoupdate
        self._active_pixel = None
        self._active_pixel_label = None

        # initialize the plot and generate the pixels as a
        # RegularPolyCollection

        patches = []

        for xx, yy, aa in zip(u.Quantity(self.geom.pix_x).value,
                              u.Quantity(self.geom.pix_y).value,
                              u.Quantity(np.array(self.geom.pix_area))):
            if self.geom.pix_type.startswith("hex"):
                rr = sqrt(aa * 2 / 3 / sqrt(3))
                poly = RegularPolygon((xx, yy), 6, radius=rr,
                                      orientation=np.radians(0),
                                      fill=True)
            else:
                rr = sqrt(aa)
                poly = Rectangle((xx, yy), width=rr, height=rr,
                                 angle=np.radians(0),
                                 fill=True)

            patches.append(poly)

        self.pixels = PatchCollection(patches, cmap='hot', linewidth=0)
        self.axes.add_collection(self.pixels)

        # Set up some nice plot defaults

        self.axes.set_aspect('equal', 'datalim')
        self.axes.set_title(title)
        self.axes.set_xlabel("X position ({})".format(self.geom.pix_x.unit))
        self.axes.set_ylabel("Y position ({})".format(self.geom.pix_y.unit))
        self.axes.autoscale_view()

        # set up a patch to display when a pixel is clicked (and
        # pixel_picker is enabled):

        self._active_pixel = copy.copy(patches[0])
        self._active_pixel.set_facecolor('r')
        self._active_pixel.set_alpha(0.5)
        self._active_pixel.set_linewidth(2.0)
        self._active_pixel.set_visible(False)
        self.axes.add_patch(self._active_pixel)

        self._active_pixel_label = plt.text(self._active_pixel.xy[0],
                                            self._active_pixel.xy[1],
                                            "0",
                                            horizontalalignment='center',
                                            verticalalignment='center')
        self._active_pixel_label.set_visible(False)

        # enable ability to click on pixel and do something (can be
        # enabled on-the-fly later as well:

        if allow_pick:
            self.enable_pixel_picker()

        if image is not None:
            self.image = image
        else:
            self.image = np.zeros_like(self.geom.pix_id, dtype=np.float)

    def enable_pixel_picker(self):
        """ enable ability to click on pixels """
        self.pixels.set_picker(True)  # enable click
        self.pixels.set_pickradius(sqrt(u.Quantity(self.geom.pix_area[0])
                                        .value) / np.pi)
        self.pixels.set_snap(True)  # snap cursor to pixel center
        self.axes.figure.canvas.mpl_connect('pick_event', self._on_pick)

    def set_limits_minmax(self, zmin, zmax):
        """ set the color scale limits from min to max """
        self.pixels.set_clim(zmin, zmax)
        self.update()

    def set_limits_percent(self, percent=95):
        """ auto-scale the color range to percent of maximum """
        zmin = self.pixels.get_array().min()
        zmax = self.pixels.get_array().max()
        dz = zmax - zmin
        frac = percent / 100.0
        self.set_limits_minmax(zmin, zmax - (1.0 - frac) * dz)

    @property
    def cmap(self):
        return self.pixels.get_cmap()

    @cmap.setter
    def cmap(self, cmap):
        """ Change the color map

        Parameters
        ----------
        self: type
            description
        cmap: `matplotlib.colors.ColorMap`
            a color map, e.g. from `matplotlib.pyplot.cm.*`
        """
        self.pixels.set_cmap(cmap)
        self.update()

    @property
    def image(self):
        return self.pixels.get_array()

    @image.setter
    def image(self, image):
        """
        Change the image displayed on the Camera.

        Parameters
        ----------
        image: array_like
            array of values corresponding to the pixels in the CameraGeometry.
        """
        image = np.asanyarray(image)
        if image.shape != self.geom.pix_x.shape:
            raise ValueError(
                "Image has a different shape {} than the"
                "given CameraGeometry {}"
                .format(image.shape, self.geom.pix_x.shape)
            )

        self.pixels.set_array(image)
        self.axes._sci(self.pixels)

        self.update()

    def set_image(self, image):
        logger.warn("set_image(x) is deprecated:"
                    " use CameraDisplay.image = x instead")
        self.image = image
        
    def update(self):
        """ signal a redraw if necessary """
        if self.autoupdate:
            plt.draw()

    def add_colorbar(self):
        """ add a colobar to the camera plot """
        self.axes.figure.colorbar(self.pixels)

    def add_ellipse(self, centroid, length, width, angle, asymmetry=0.0,
                    **kwargs):
        """
        plot an ellipse on top of the camera

        Parameters
        ----------
        centroid: (float,float)
            position of centroid
        length: float
            major axis
        width: float
            minor axis
        angle: float
            rotation angle wrt "up" about the centroid, clockwise, in radians
        asymmetry: float
            3rd-order moment for directionality if known
        kwargs:
            any MatPlotLib style arguments to pass to the Ellipse patch

        """
        ellipse = Ellipse(xy=centroid, width=width, height=length,
                          angle=np.degrees(angle), fill=False, **kwargs)
        self.axes.add_patch(ellipse)
        self.update()
        return ellipse

    def overlay_moments(self, momparams, **kwargs):
        """helper to overlay ellipse from a `reco.MomentParameters` structure

        Parameters
        ----------
        momparams: `reco.MomentParameters`
            structuring containing Hillas-style parameterization
        kwargs: key=value
            any style keywords to pass to matplotlib (e.g. color='red'
            or linewidth=6)
        """

        el = self.add_ellipse(centroid=(momparams.cen_x, momparams.cen_y),
                              length=momparams.length,
                              width=momparams.width, angle=momparams.psi,
                              **kwargs)
        self.axes.text(momparams.cen_x, momparams.cen_y,
                       ("({:.02f},{:.02f})\n"
                        "[w={:.02f},l={:.02f}]")
                       .format(momparams.cen_x,
                               momparams.cen_y,
                               momparams.width, momparams.length),
                       color=el.get_edgecolor())

    def _on_pick(self, event):
        """ handler for when a pixel is clicked """
        pix_id = event.ind.pop()
        xx, yy = u.Quantity(self.geom.pix_x[pix_id]).value,\
                 u.Quantity(self.geom.pix_y[pix_id]).value
        self._active_pixel.xy = (xx, yy)
        self._active_pixel.set_visible(True)
        self._active_pixel_label.set_x(xx)
        self._active_pixel_label.set_y(yy)
        self._active_pixel_label.set_text("{:003d}".format(pix_id))
        self._active_pixel_label.set_visible(True)
        self.update()
        self.on_pixel_clicked(pix_id)  # call user-function

    def on_pixel_clicked(self, pix_id):
        """virtual function to overide in sub-classes to do something special
        when a pixel is clicked
        """
        print("Clicked pixel_id {}".format(pix_id))