コード例 #1
0
class ManualGainSelector(GainSelector):
    """
    Manually choose a gain channel.
    """
    channel = traits.CaselessStrEnum(
        ["HIGH", "LOW"],
        default_value="HIGH",
        help="Which gain channel to retain").tag(config=True)

    def select_channel(self, waveforms):
        n_pixels = waveforms.shape[1]
        return np.full(n_pixels, GainChannel[self.channel])
コード例 #2
0
class HillasIntersection(Reconstructor):
    """
    This class is a simple re-implementation of Hillas parameter based event
    reconstruction. e.g. https://arxiv.org/abs/astro-ph/0607333

    In this case the Hillas parameters are all constructed in the shared
    angular ( Nominal) system. Direction reconstruction is performed by
    extrapolation of the major axes of the Hillas parameters in the nominal
    system and the weighted average of the crossing points is taken. Core
    reconstruction is performed by performing the same procedure in the
    tilted ground system.

    The height of maximum is reconstructed by the projection os the image
    centroid onto the shower axis, taking the weighted average of all images.

    Uncertainties on the positions are provided by taking the spread of the
    crossing points, however this means that no uncertainty can be provided
    for multiplicity 2 events.
    """

    atmosphere_profile_name = traits.CaselessStrEnum(
        [
            'paranal',
        ],
        default_value="paranal",
        help="name of atmosphere profile to use").tag(config=True)

    weighting = traits.CaselessStrEnum(
        ['Konrad', 'hess'],
        default_value='Konrad',
        help='Weighting Method name').tag(config=True)

    def __init__(self, config=None, parent=None, **kwargs):
        """
        Weighting must be a function similar to the weight_konrad already implemented
        """
        super().__init__(config=config, parent=parent, **kwargs)

        # We need a conversion function from height above ground to depth of maximum
        # To do this we need the conversion table from CORSIKA
        _ = get_atmosphere_profile_functions(self.atmosphere_profile_name)
        self.thickness_profile, self.altitude_profile = _

        # other weighting schemes can be implemented. just add them as additional methods
        if self.weighting == "Konrad":
            self._weight_method = self.weight_konrad

    def predict(self,
                hillas_dict,
                inst,
                array_pointing,
                telescopes_pointings=None):
        """

        Parameters
        ----------
        hillas_dict: dict
            Dictionary containing Hillas parameters for all telescopes
            in reconstruction
        inst : ctapipe.io.InstrumentContainer
            instrumental description
        array_pointing: SkyCoord[AltAz]
            pointing direction of the array
        telescopes_pointings: dict[SkyCoord[AltAz]]
            dictionary of pointing direction per each telescope

        Returns
        -------
        ReconstructedShowerContainer:

        """

        # filter warnings for missing obs time. this is needed because MC data has no obs time
        warnings.filterwarnings(action='ignore',
                                category=MissingFrameAttributeWarning)

        # stereoscopy needs at least two telescopes
        if len(hillas_dict) < 2:
            raise TooFewTelescopesException(
                "need at least two telescopes, have {}".format(
                    len(hillas_dict)))

        # check for np.nan or 0 width's as these screw up weights
        if any(
            [np.isnan(hillas_dict[tel]['width'].value)
             for tel in hillas_dict]):
            raise InvalidWidthException(
                "A HillasContainer contains an ellipse of width==np.nan")

        if any([hillas_dict[tel]['width'].value == 0 for tel in hillas_dict]):
            raise InvalidWidthException(
                "A HillasContainer contains an ellipse of width==0")

        if telescopes_pointings is None:
            telescopes_pointings = {
                tel_id: array_pointing
                for tel_id in hillas_dict.keys()
            }

        tilted_frame = TiltedGroundFrame(pointing_direction=array_pointing)

        ground_positions = inst.subarray.tel_coords
        grd_coord = GroundFrame(x=ground_positions.x,
                                y=ground_positions.y,
                                z=ground_positions.z)

        tilt_coord = grd_coord.transform_to(tilted_frame)

        tel_x = {
            tel_id: tilt_coord.x[tel_id - 1]
            for tel_id in list(hillas_dict.keys())
        }
        tel_y = {
            tel_id: tilt_coord.y[tel_id - 1]
            for tel_id in list(hillas_dict.keys())
        }

        nom_frame = NominalFrame(origin=array_pointing)

        hillas_dict_mod = copy.deepcopy(hillas_dict)

        for tel_id, hillas in hillas_dict_mod.items():
            # prevent from using rads instead of meters as inputs
            assert hillas.x.to(u.m).unit == u.Unit('m')

            focal_length = inst.subarray.tel[
                tel_id].optics.equivalent_focal_length

            camera_frame = CameraFrame(
                telescope_pointing=telescopes_pointings[tel_id],
                focal_length=focal_length,
            )
            cog_coords = SkyCoord(x=hillas.x, y=hillas.y, frame=camera_frame)
            cog_coords_nom = cog_coords.transform_to(nom_frame)
            hillas.x = cog_coords_nom.delta_alt
            hillas.y = cog_coords_nom.delta_az

        src_x, src_y, err_x, err_y = self.reconstruct_nominal(hillas_dict_mod)
        core_x, core_y, core_err_x, core_err_y = self.reconstruct_tilted(
            hillas_dict_mod, tel_x, tel_y)

        err_x *= u.rad
        err_y *= u.rad

        nom = SkyCoord(delta_az=src_x * u.rad,
                       delta_alt=src_y * u.rad,
                       frame=nom_frame)
        # nom = sky_pos.transform_to(nom_frame)
        sky_pos = nom.transform_to(array_pointing.frame)

        result = ReconstructedShowerContainer()
        result.alt = sky_pos.altaz.alt.to(u.rad)
        result.az = sky_pos.altaz.az.to(u.rad)

        tilt = SkyCoord(
            x=core_x * u.m,
            y=core_y * u.m,
            frame=tilted_frame,
        )
        grd = project_to_ground(tilt)
        result.core_x = grd.x
        result.core_y = grd.y

        x_max = self.reconstruct_xmax(
            nom.delta_az,
            nom.delta_alt,
            tilt.x,
            tilt.y,
            hillas_dict_mod,
            tel_x,
            tel_y,
            90 * u.deg - array_pointing.alt,
        )

        result.core_uncert = np.sqrt(core_err_x**2 + core_err_y**2) * u.m

        result.tel_ids = [h for h in hillas_dict_mod.keys()]
        result.average_intensity = np.mean(
            [h.intensity for h in hillas_dict_mod.values()])
        result.is_valid = True

        src_error = np.sqrt(err_x**2 + err_y**2)
        result.alt_uncert = src_error.to(u.rad)
        result.az_uncert = src_error.to(u.rad)
        result.h_max = x_max
        result.h_max_uncert = np.nan
        result.goodness_of_fit = np.nan

        return result

    def reconstruct_nominal(self, hillas_parameters):
        """
        Perform event reconstruction by simple Hillas parameter intersection
        in the nominal system

        Parameters
        ----------
        hillas_parameters: dict
            Hillas parameter objects

        Returns
        -------
        Reconstructed event position in the horizon system

        """
        if len(hillas_parameters) < 2:
            return None  # Throw away events with < 2 images

        # Find all pairs of Hillas parameters
        combos = itertools.combinations(list(hillas_parameters.values()), 2)
        hillas_pairs = list(combos)

        # Copy parameters we need to a numpy array to speed things up
        h1 = list(
            map(
                lambda h: [
                    h[0].psi.to_value(u.rad), h[0].x.to_value(u.rad), h[0].y.
                    to_value(u.rad), h[0].intensity
                ], hillas_pairs))
        h1 = np.array(h1)
        h1 = np.transpose(h1)

        h2 = list(
            map(
                lambda h: [
                    h[1].psi.to_value(u.rad), h[1].x.to_value(u.rad), h[1].y.
                    to_value(u.rad), h[1].intensity
                ], hillas_pairs))
        h2 = np.array(h2)
        h2 = np.transpose(h2)

        # Perform intersection
        sx, sy = self.intersect_lines(h1[1], h1[2], h1[0], h2[1], h2[2], h2[0])

        # Weight by chosen method
        weight = self._weight_method(h1[3], h2[3])
        # And sin of interception angle
        weight *= self.weight_sin(h1[0], h2[0])

        # Make weighted average of all possible pairs
        x_pos = np.average(sx, weights=weight)
        y_pos = np.average(sy, weights=weight)
        var_x = np.average((sx - x_pos)**2, weights=weight)
        var_y = np.average((sy - y_pos)**2, weights=weight)

        return x_pos, y_pos, np.sqrt(var_x), np.sqrt(var_y)

    def reconstruct_tilted(self, hillas_parameters, tel_x, tel_y):
        """
        Core position reconstruction by image axis intersection in the tilted
        system

        Parameters
        ----------
        hillas_parameters: dict
            Hillas parameter objects
        tel_x: dict
            Telescope X positions, tilted system
        tel_y: dict
            Telescope Y positions, tilted system

        Returns
        -------
        (float, float, float, float):
            core position X, core position Y, core uncertainty X,
            core uncertainty X
        """
        if len(hillas_parameters) < 2:
            return None  # Throw away events with < 2 images
        hill_list = list()
        tx = list()
        ty = list()

        # Need to loop here as dict is unordered
        for tel in hillas_parameters.keys():
            hill_list.append(hillas_parameters[tel])
            tx.append(tel_x[tel])
            ty.append(tel_y[tel])

        # Find all pairs of Hillas parameters
        hillas_pairs = list(itertools.combinations(hill_list, 2))
        tel_x = list(itertools.combinations(tx, 2))
        tel_y = list(itertools.combinations(ty, 2))

        tx = np.zeros((len(tel_x), 2))
        ty = np.zeros((len(tel_y), 2))
        for i, _ in enumerate(tel_x):
            tx[i][0], tx[i][1] = tel_x[i][0].to_value(
                u.m), tel_x[i][1].to_value(u.m)
            ty[i][0], ty[i][1] = tel_y[i][0].to_value(
                u.m), tel_y[i][1].to_value(u.m)

        tel_x = np.array(tx)
        tel_y = np.array(ty)

        # Copy parameters we need to a numpy array to speed things up
        hillas1 = map(lambda h: [h[0].psi.to_value(u.rad), h[0].intensity],
                      hillas_pairs)
        hillas1 = np.array(list(hillas1))
        hillas1 = np.transpose(hillas1)

        hillas2 = map(lambda h: [h[1].psi.to_value(u.rad), h[1].intensity],
                      hillas_pairs)
        hillas2 = np.array(list(hillas2))
        hillas2 = np.transpose(hillas2)

        # Perform intersection
        crossing_x, crossing_y = self.intersect_lines(tel_x[:, 0], tel_y[:, 0],
                                                      hillas1[0], tel_x[:, 1],
                                                      tel_y[:, 1], hillas2[0])

        # Weight by chosen method
        weight = self._weight_method(hillas1[1], hillas2[1])
        # And sin of interception angle
        weight *= self.weight_sin(hillas1[0], hillas2[0])

        # Make weighted average of all possible pairs
        x_pos = np.average(crossing_x, weights=weight)
        y_pos = np.average(crossing_y, weights=weight)
        var_x = np.average((crossing_x - x_pos)**2, weights=weight)
        var_y = np.average((crossing_y - y_pos)**2, weights=weight)

        return x_pos, y_pos, np.sqrt(var_x), np.sqrt(var_y)

    def reconstruct_xmax(self, source_x, source_y, core_x, core_y,
                         hillas_parameters, tel_x, tel_y, zen):
        """
        Geometrical depth of shower maximum reconstruction, assuming the shower
        maximum lies at the image centroid

        Parameters
        ----------
        source_x: float
            Source X position in nominal system
        source_y: float
            Source Y position in nominal system
        core_x: float
            Core X position in nominal system
        core_y: float
            Core Y position in nominal system
        hillas_parameters: dict
            Dictionary of hillas parameters objects
        tel_x: dict
            Dictionary of telescope X positions in tilted frame
        tel_y: dict
            Dictionary of telescope Y positions in tilted frame
        zen: float
            Zenith angle of shower

        Returns
        -------
        float:
            Estimated depth of shower maximum
        """
        cog_x = list()
        cog_y = list()
        amp = list()

        tx = list()
        ty = list()

        # Loops over telescopes in event
        for tel in hillas_parameters.keys():
            cog_x.append(hillas_parameters[tel].x.to_value(u.rad))
            cog_y.append(hillas_parameters[tel].y.to_value(u.rad))
            amp.append(hillas_parameters[tel].intensity)

            tx.append(tel_x[tel].to_value(u.m))
            ty.append(tel_y[tel].to_value(u.m))

        height = get_shower_height(source_x.to_value(u.rad),
                                   source_y.to_value(u.rad), np.array(cog_x),
                                   np.array(cog_y), core_x.to_value(u.m),
                                   core_y.to_value(u.m), np.array(tx),
                                   np.array(ty))
        weight = np.array(amp)
        mean_height = np.sum(height * weight) / np.sum(weight)

        # This value is height above telescope in the tilted system,
        # we should convert to height above ground
        mean_height *= np.cos(zen)

        # Add on the height of the detector above sea level
        mean_height += 2100  # TODO: replace with instrument info

        if mean_height > 100000 or np.isnan(mean_height):
            mean_height = 100000

        mean_height *= u.m
        # Lookup this height in the depth tables, the convert Hmax to Xmax
        # x_max = self.thickness_profile(mean_height.to(u.km))
        # Convert to slant depth
        # x_max /= np.cos(zen)

        return mean_height

    @staticmethod
    def intersect_lines(xp1, yp1, phi1, xp2, yp2, phi2):
        """
        Perform intersection of two lines. This code is borrowed from read_hess.
        Parameters
        ----------
        xp1: ndarray
            X position of first image
        yp1: ndarray
            Y position of first image
        phi1: ndarray
            Rotation angle of first image
        xp2: ndarray
            X position of second image
        yp2: ndarray
            Y position of second image
        phi2: ndarray
            Rotation angle of second image

        Returns
        -------
        ndarray of x and y crossing points for all pairs
        """
        sin_1 = np.sin(phi1)
        cos_1 = np.cos(phi1)
        a1 = sin_1
        b1 = -1 * cos_1
        c1 = yp1 * cos_1 - xp1 * sin_1

        sin_2 = np.sin(phi2)
        cos_2 = np.cos(phi2)

        a2 = sin_2
        b2 = -1 * cos_2
        c2 = yp2 * cos_2 - xp2 * sin_2

        det_ab = (a1 * b2 - a2 * b1)
        det_bc = (b1 * c2 - b2 * c1)
        det_ca = (c1 * a2 - c2 * a1)

        # if  math.fabs(det_ab) < 1e-14 : # /* parallel */
        #    return 0,0
        xs = det_bc / det_ab
        ys = det_ca / det_ab

        return xs, ys

    @staticmethod
    def weight_konrad(p1, p2):
        return (p1 * p2) / (p1 + p2)

    @staticmethod
    def weight_sin(phi1, phi2):
        return np.abs(np.sin(phi1 - phi2))
コード例 #3
0
class CameraDemo(Tool):

    name = u"ctapipe-camdemo"
    description = "Display fake events in a demo camera"

    delay = traits.Int(50, help="Frame delay in ms", min=20).tag(config=True)
    cleanframes = traits.Int(100,
                             help="Number of frames between turning on "
                             "cleaning",
                             min=0).tag(config=True)
    autoscale = traits.Bool(False, help='scale each frame to max if '
                            'True').tag(config=True)
    blit = traits.Bool(False,
                       help='use blit operation to draw on screen ('
                       'much faster but may cause some draw '
                       'artifacts)').tag(config=True)
    camera = traits.CaselessStrEnum(
        CameraGeometry.get_known_camera_names(),
        default_value='NectarCam',
        help='Name of camera to display').tag(config=True)

    optics = traits.CaselessStrEnum(
        OpticsDescription.get_known_optics_names(),
        default_value='MST',
        help='Telescope optics description name').tag(config=True)

    aliases = traits.Dict({
        'delay': 'CameraDemo.delay',
        'cleanframes': 'CameraDemo.cleanframes',
        'autoscale': 'CameraDemo.autoscale',
        'blit': 'CameraDemo.blit',
        'camera': 'CameraDemo.camera',
        'optics': 'CameraDemo.optics',
    })

    def __init__(self):
        super().__init__()
        self._counter = 0
        self.imclean = False

    def start(self):
        self.log.info("Starting CameraDisplay for {}".format(self.camera))
        self._display_camera_animation()

    def _display_camera_animation(self):
        # plt.style.use("ggplot")
        fig = plt.figure(num="ctapipe Camera Demo", figsize=(7, 7))
        ax = plt.subplot(111)

        # load the camera
        tel = TelescopeDescription.from_name(optics_name=self.optics,
                                             camera_name=self.camera)
        geom = tel.camera

        # poor-man's coordinate transform from telscope to camera frame (it's
        # better to use ctapipe.coordiantes when they are stable)
        scale = tel.optics.effective_focal_length.to(geom.pix_x.unit).value
        fov = np.deg2rad(4.0)
        maxwid = np.deg2rad(0.01)
        maxlen = np.deg2rad(0.03)

        disp = CameraDisplay(geom,
                             ax=ax,
                             autoupdate=True,
                             title="{}, f={}".format(
                                 tel, tel.optics.effective_focal_length))
        disp.cmap = plt.cm.terrain

        def update(frame):

            centroid = np.random.uniform(-fov, fov, size=2) * scale
            width = np.random.uniform(0, maxwid) * scale
            length = np.random.uniform(0, maxlen) * scale + width
            angle = np.random.uniform(0, 360)
            intens = np.random.exponential(2) * 50
            model = toymodel.generate_2d_shower_model(centroid=centroid,
                                                      width=width,
                                                      length=length,
                                                      psi=angle * u.deg)
            image, sig, bg = toymodel.make_toymodel_shower_image(
                geom, model.pdf, intensity=intens, nsb_level_pe=5000)

            # alternate between cleaned and raw images
            if self._counter == self.cleanframes:
                plt.suptitle("Image Cleaning ON")
                self.imclean = True
            if self._counter == self.cleanframes * 2:
                plt.suptitle("Image Cleaning OFF")
                self.imclean = False
                self._counter = 0

            if self.imclean:
                cleanmask = tailcuts_clean(geom, image / 80.0)
                for ii in range(3):
                    dilate(geom, cleanmask)
                image[cleanmask == 0] = 0  # zero noise pixels

            self.log.debug("count = {}, image sum={} max={}".format(
                self._counter, image.sum(), image.max()))
            disp.image = image

            if self.autoscale:
                disp.set_limits_percent(95)
            else:
                disp.set_limits_minmax(-100, 4000)

            disp.axes.figure.canvas.draw()
            self._counter += 1
            return [
                ax,
            ]

        self.anim = FuncAnimation(fig,
                                  update,
                                  interval=self.delay,
                                  blit=self.blit)
        plt.show()
コード例 #4
0
class CameraDemo(Tool):
    name = "ctapipe-camdemo"
    description = "Display fake events in a demo camera"

    delay = traits.Int(50, help="Frame delay in ms", min=20).tag(config=True)
    cleanframes = traits.Int(20, help="Number of frames between turning on "
                                       "cleaning", min=0).tag(config=True)
    autoscale = traits.Bool(False, help='scale each frame to max if '
                                        'True').tag(config=True)
    blit = traits.Bool(False, help='use blit operation to draw on screen ('
                                   'much faster but may cause some draw '
                                   'artifacts)').tag(config=True)
    camera = traits.CaselessStrEnum(
        CameraDescription.get_known_camera_names(),
        default_value='NectarCam',
        help='Name of camera to display').tag(config=True)

    optics = traits.CaselessStrEnum(
        OpticsDescription.get_known_optics_names(),
        default_value='MST',
        help='Telescope optics description name'
    ).tag(config=True)

    num_events = traits.Int(0, help='events to show before exiting (0 for '
                                    'unlimited)').tag(config=True)

    display = traits.Bool(True, "enable or disable display (for "
                                "testing)").tag(config=True)

    aliases = traits.Dict({
        'delay': 'CameraDemo.delay',
        'cleanframes': 'CameraDemo.cleanframes',
        'autoscale': 'CameraDemo.autoscale',
        'blit': 'CameraDemo.blit',
        'camera': 'CameraDemo.camera',
        'optics': 'CameraDemo.optics',
        'num-events': 'CameraDemo.num_events'
    })

    def __init__(self):
        super().__init__()
        self._counter = 0
        self.imclean = False

    def start(self):
        self.log.info(f"Starting CameraDisplay for {self.camera}")
        self._display_camera_animation()

    def _display_camera_animation(self):
        # plt.style.use("ggplot")
        fig = plt.figure(num="ctapipe Camera Demo", figsize=(7, 7))
        ax = plt.subplot(111)

        # load the camera
        tel = TelescopeDescription.from_name(optics_name=self.optics,
                                             camera_name=self.camera)
        geom = tel.camera.geometry

        # poor-man's coordinate transform from telscope to camera frame (it's
        # better to use ctapipe.coordiantes when they are stable)
        foclen = tel.optics.equivalent_focal_length.to(geom.pix_x.unit).value
        fov = np.deg2rad(4.0)
        scale = foclen
        minwid = np.deg2rad(0.1)
        maxwid = np.deg2rad(0.3)
        maxlen = np.deg2rad(0.5)

        self.log.debug(f"scale={scale} m, wid=({minwid}-{maxwid})")

        disp = CameraDisplay(
            geom, ax=ax, autoupdate=True,
            title=f"{tel}, f={tel.optics.equivalent_focal_length}"
        )
        disp.cmap = plt.cm.terrain

        def update(frame):
            x, y = np.random.uniform(-fov, fov, size=2) * scale
            width = np.random.uniform(0, maxwid - minwid) * scale + minwid
            length = np.random.uniform(0, maxlen) * scale + width
            angle = np.random.uniform(0, 360)
            intens = np.random.exponential(2) * 500
            model = toymodel.Gaussian(
                x=x * u.m,
                y=y * u.m,
                width=width * u.m,
                length=length * u.m,
                psi=angle * u.deg,
            )
            self.log.debug(
                "Frame=%d width=%03f length=%03f intens=%03d",
                frame, width, length, intens
            )

            image, _, _ = model.generate_image(
                geom,
                intensity=intens,
                nsb_level_pe=3,
            )

            # alternate between cleaned and raw images
            if self._counter == self.cleanframes:
                plt.suptitle("Image Cleaning ON")
                self.imclean = True
            if self._counter == self.cleanframes * 2:
                plt.suptitle("Image Cleaning OFF")
                self.imclean = False
                self._counter = 0
                disp.clear_overlays()

            if self.imclean:
                cleanmask = tailcuts_clean(geom, image,
                                           picture_thresh=10.0,
                                           boundary_thresh=5.0)
                for ii in range(2):
                    dilate(geom, cleanmask)
                image[cleanmask == 0] = 0  # zero noise pixels
                try:
                    hillas = hillas_parameters(geom, image)
                    disp.overlay_moments(hillas, with_label=False,
                                         color='red', alpha=0.7,
                                         linewidth=2, linestyle='dashed')
                except HillasParameterizationError:
                    disp.clear_overlays()
                    pass

            self.log.debug("Frame=%d  image_sum=%.3f max=%.3f",
                           self._counter, image.sum(), image.max())
            disp.image = image

            if self.autoscale:
                disp.set_limits_percent(95)
            else:
                disp.set_limits_minmax(-5, 200)

            disp.axes.figure.canvas.draw()
            self._counter += 1
            return [ax, ]

        frames = None if self.num_events == 0 else self.num_events
        repeat = True if self.num_events == 0 else False

        self.log.info(f"Running for {frames} frames")
        self.anim = FuncAnimation(fig, update,
                                  interval=self.delay,
                                  frames=frames,
                                  repeat=repeat,
                                  blit=self.blit)

        if self.display:
            plt.show()