def angular_distance(event, hillas_containers):
    """
    find the angular distance between the true array that point to the origin of
    the shower of protons and the predicted point.

    Parameters
    ----------

    event :
        calibrated event

    hillas_containers :
        dictionary with telescope IDs as key and
        HillasParametersContainer instances as values

    Returns
    -------

    angular_distance:
        angular distance(in degrees) or False if the reconstruction can't be done due
        TooFewTelescopesException or InvalidWidthException

    """
    horizon_frame = AltAz()
    reco = HillasReconstructor()

    # SkyCoord
    array_pointing_event = SkyCoord(az=event.mcheader.run_array_direction[0],
                                    alt=event.mcheader.run_array_direction[1],
                                    frame=horizon_frame)

    try:

        reconstruction_cleaned = reco.predict(
            hillas_containers,
            event.inst,
            array_pointing_event,
        )

    except TooFewTelescopesException:
        return False

    except InvalidWidthException:
        return False

    array_pointing_reconstruction = SkyCoord(az=reconstruction_cleaned.az,
                                             alt=reconstruction_cleaned.alt,
                                             frame=horizon_frame)

    # angular distance between reconstructed and event image
    ang_dist = array_pointing_event.separation(array_pointing_reconstruction)
    return ang_dist
Example #2
0
def reconstruct_direction(array_event_id, group, instrument):

    reco = HillasReconstructor()
    # monkey patch this huansohn. this is super slow otherwise. who needs max h anyways
    reco.fit_h_max = dummy_function_h_max

    params = {}
    pointing_azimuth = {}
    pointing_altitude = {}
    for index, row in group.iterrows():
        tel_id = row.telescope_id
        # the data in each event has to be put inside these namedtuples to call reco.predict
        moments = SubMomentParameters(size=row.intensity,
                                      cen_x=row.x * u.m,
                                      cen_y=row.y * u.m,
                                      length=row.length * u.m,
                                      width=row.width * u.m,
                                      psi=row.psi * u.rad)
        params[tel_id] = moments
        pointing_azimuth[tel_id] = row.pointing_azimuth * u.rad
        pointing_altitude[tel_id] = row.pointing_altitude * u.rad

    try:
        reconstruction = reco.predict(params, instrument, pointing_azimuth,
                                      pointing_altitude)
    except NameError:
        return {
            'alt_prediction': np.nan,
            'az_prediction': np.nan,
            'core_x_prediction': np.nan,
            'core_y_prediction': np.nan,
            'array_event_id': array_event_id,
        }

    if reconstruction.alt.si.value == np.nan:
        print('Not reconstructed')
        print(params)

    return {
        'alt_prediction':
        ((np.pi / 2) - reconstruction.alt.si.value),  # TODO srsly now? FFS
        'az_prediction': reconstruction.az.si.value,
        'core_x_prediction': reconstruction.core_x.si.value,
        'core_y_prediction': reconstruction.core_y.si.value,
        'array_event_id': array_event_id,
        # 'h_max_prediction': reconstruction.h_max.si.value
    }
Example #3
0
        if params.width > 0:
            hillas_params[tel_id] = params



    array_pointing = SkyCoord(
        az=event.mcheader.run_array_direction[0],
        alt=event.mcheader.run_array_direction[1],
        frame=horizon_frame
    )

    if len(hillas_params) < 2:
        continue

    reco_result = reco.predict(
        hillas_params, source.subarray,
        array_pointing, telescope_pointings
    )

    # get angular offset between reconstructed shower direction and MC
    # generated shower direction
    off_angle = angular_separation(
        event.mc.az,
        event.mc.alt,
        reco_result.az,
        reco_result.alt
    )

    # Appending all estimated off angles
    off_angles.append(off_angle.to(u.deg).value)

# calculate theta square for angles which are not nan
Example #4
0
def test_reconstructors(reconstructors):
    """
    a test of the complete fit procedure on one event including:
    • tailcut cleaning
    • hillas parametrisation
    • HillasPlane creation
    • direction fit
    • position fit

    in the end, proper units in the output are asserted"""

    filename = get_dataset_path(
        "gamma_LaPalma_baseline_20Zd_180Az_prod3b_test.simtel.gz")

    source = EventSource(filename, max_events=10)
    subarray = source.subarray
    calib = CameraCalibrator(source.subarray)
    horizon_frame = AltAz()

    for event in source:
        calib(event)
        sim_shower = event.simulation.shower
        array_pointing = SkyCoord(az=sim_shower.az,
                                  alt=sim_shower.alt,
                                  frame=horizon_frame)

        telescope_pointings = {}

        for tel_id, dl1 in event.dl1.tel.items():

            geom = source.subarray.tel[tel_id].camera.geometry

            telescope_pointings[tel_id] = SkyCoord(
                alt=event.pointing.tel[tel_id].altitude,
                az=event.pointing.tel[tel_id].azimuth,
                frame=horizon_frame,
            )
            mask = tailcuts_clean(geom,
                                  dl1.image,
                                  picture_thresh=10.0,
                                  boundary_thresh=5.0)

            dl1.parameters = ImageParametersContainer()

            try:
                moments = hillas_parameters(geom[mask], dl1.image[mask])
            except HillasParameterizationError:
                dl1.parameters.hillas = HillasParametersContainer()
                continue

            # Make sure we provide only good images for the test
            if np.isnan(moments.width.value) or (moments.width.value == 0):
                dl1.parameters.hillas = HillasParametersContainer()
            else:
                dl1.parameters.hillas = moments

        hillas_dict = {
            tel_id: dl1.parameters.hillas
            for tel_id, dl1 in event.dl1.tel.items()
            if np.isfinite(dl1.parameters.hillas.intensity)
        }
        if len(hillas_dict) < 2:
            continue

        for count, reco_method in enumerate(reconstructors):
            if reco_method is HillasReconstructor:
                reconstructor = HillasReconstructor(subarray)

                reconstructor(event)

                event.dl2.stereo.geometry["HillasReconstructor"].alt.to(u.deg)
                event.dl2.stereo.geometry["HillasReconstructor"].az.to(u.deg)
                event.dl2.stereo.geometry["HillasReconstructor"].core_x.to(u.m)
                assert event.dl2.stereo.geometry[
                    "HillasReconstructor"].is_valid

            else:
                reconstructor = reco_method()

                try:
                    reconstructor_out = reconstructor.predict(
                        hillas_dict,
                        source.subarray,
                        array_pointing,
                        telescope_pointings,
                    )
                except InvalidWidthException:
                    continue

                reconstructor_out.alt.to(u.deg)
                reconstructor_out.az.to(u.deg)
                reconstructor_out.core_x.to(u.m)
                assert reconstructor_out.is_valid
Example #5
0
                                   picture_thresh=10,
                                   boundary_thresh=5)
        # set all rejected pixels to zero
        cleaned_image[~cleanmask] = 0

        # Calulate hillas parameters
        # It fails for empty pixels
        try:
            hillas_params[tel_id] = hillas_parameters(camgeom, cleaned_image)
        except:
            pass

    if len(hillas_params) < 2:
        continue

    reco_result = reco.predict(hillas_params, event.inst, point_altitude,
                               point_azimuth)

    # get angular offset between reconstructed shower direction and MC
    # generated shower direction
    off_angle = angular_separation(event.mc.az, event.mc.alt, reco_result.az,
                                   reco_result.alt)

    # Appending all estimated off angles
    off_angles.append(off_angle.to(u.deg).value)

# calculate theta square for angles which are not nan
off_angles = np.array(off_angles)
thetasquare = off_angles[np.isfinite(off_angles)]**2

# To plot thetasquare The number of events in th data files for LSTCam is not
#  significantly high to give a nice thetasquare plot for gammas One can use
Example #6
0
            time_gradients[telescope_id] = hillas_c.skewness
        print(geom.camera_name, time_gradients[telescope_id])

        # make sure each telescope get's an arrow
        if abs(time_gradients[telescope_id]) < 0.2:
            time_gradients[telescope_id] = 1

    # ignore events with less than two telescopes
    if len(hillas_containers) < 2:
        continue
    array_pointing = SkyCoord(az=event.mcheader.run_array_direction[0],
                              alt=event.mcheader.run_array_direction[1],
                              frame=horizon_frame)
    stereo = reco.predict(
        hillas_containers,
        event_source.subarray,
        array_pointing,
    )

    plt.figure()
    angle_offset = event.mcheader.run_array_direction[0]
    disp = ArrayDisplay(event_source.subarray)

    disp.set_vector_hillas(hillas_containers,
                           time_gradient=time_gradients,
                           angle_offset=angle_offset,
                           length=500)
    plt.scatter(
        event.mc.core_x,
        event.mc.core_y,
        s=200,
Example #7
0
            time_gradients[telescope_id] = timing_c.slope.value
        else:
            time_gradients[telescope_id] = hillas_c.skewness
        print(camera.cam_id, time_gradients[telescope_id])

        # make sure each telescope get's an arrow
        if abs(time_gradients[telescope_id]) < 0.2:
            time_gradients[telescope_id] = 1

    # ignore events with less than two telescopes
    if len(hillas_containers) < 2:
        continue

    stereo = reco.predict(
        hillas_containers,
        event.inst,
        pointing_alt=pointing_altitude,
        pointing_az=pointing_azimuth
    )

    plt.figure()
    angle_offset = event.mcheader.run_array_direction[0]
    disp = ArrayDisplay(event.inst.subarray)

    disp.set_vector_hillas(
        hillas_containers,
        time_gradient=time_gradients,
        angle_offset=angle_offset,
        length=500
    )
    plt.scatter(
        event.mc.core_x, event.mc.core_y,
Example #8
0
            camgeom, image, picture_thresh=10, boundary_thresh=5
        )
        # set all rejected pixels to zero
        cleaned_image[~cleanmask] = 0

        # Calulate hillas parameters
        # It fails for empty pixels
        try:
            hillas_params[tel_id] = hillas_parameters(camgeom, cleaned_image)
        except:
            pass

    if len(hillas_params) < 2:
        continue

    reco_result = reco.predict(hillas_params, event.inst, point_altitude, point_azimuth)

    # get angular offset between reconstructed shower direction and MC
    # generated shower direction
    off_angle = angular_separation(
        event.mc.az,
        event.mc.alt,
        reco_result.az,
        reco_result.alt
    )

    # Appending all estimated off angles
    off_angles.append(off_angle.to(u.deg).value)

# calculate theta square for angles which are not nan
off_angles = np.array(off_angles)
def process_event(event, calibrator, config):
    '''Processes one event.
    Calls calculate_image_features and performs a stereo hillas reconstruction.
    ReconstructedShowerContainer will be filled with nans (+units) if hillas failed.

    Returns:
    --------
    ArrayEventContainer
    list(telescope_event_containers.values())
    '''

    logging.info(f'processing event {event.dl0.event_id}')
    calibrator(event)

    telescope_types = []
    hillas_reconstructor = HillasReconstructor()
    telescope_event_containers = {}
    horizon_frame = AltAz()
    telescope_pointings = {}
    array_pointing = SkyCoord(
        az=event.mcheader.run_array_direction[0],
        alt=event.mcheader.run_array_direction[1],
        frame=horizon_frame,
    )

    for telescope_id, dl1 in event.dl1.tel.items():
        cam_type = event.inst.subarray.tels[telescope_id].camera.cam_id
        if cam_type not in config.cleaning_level.keys():
            logging.info(f"No cleaning levels for camera {cam_type}. Skipping event")
            continue
        telescope_types.append(str(event.inst.subarray.tels[telescope_id].optics))
        try:
            telescope_event_containers[telescope_id] = calculate_image_features(
                telescope_id, event, dl1, config
            )
        except HillasParameterizationError:
            logging.info(
                f'Error calculating hillas features for event {event.dl0.event_id, telescope_id}',
                exc_info=True,
            )
            continue
        except Exception:
            logging.info(
                f'Error calculating image features for event {event.dl0.event_id, telescope_id}',
                exc_info=True,
            )
            continue
        telescope_pointings[telescope_id] = SkyCoord(
            alt=telescope_event_containers[telescope_id].pointing.altitude,
            az=telescope_event_containers[telescope_id].pointing.azimuth,
            frame=horizon_frame,
        )

    if len(telescope_event_containers) < 1:
        raise Exception('None of the allowed telescopes triggered for event %s', event.dl0.event_id)

    parameters = {tel_id: telescope_event_containers[tel_id].hillas for tel_id in telescope_event_containers}

    try:
        reconstruction_container = hillas_reconstructor.predict(
            parameters, event.inst, array_pointing, telescopes_pointings=telescope_pointings
        )
        reconstruction_container.prefix = ''
    except Exception:
        logging.info(
            'Not enough telescopes for which Hillas parameters could be reconstructed', exc_info=True
        )
        reconstruction_container = ReconstructedShowerContainer()
        reconstruction_container.alt = u.Quantity(np.nan, u.rad)
        reconstruction_container.alt_uncert = u.Quantity(np.nan, u.rad)
        reconstruction_container.az = u.Quantity(np.nan, u.rad)
        reconstruction_container.az_uncert = np.nan
        reconstruction_container.core_x = u.Quantity(np.nan, u.m)
        reconstruction_container.core_y = u.Quantity(np.nan, u.m)
        reconstruction_container.core_uncert = np.nan
        reconstruction_container.h_max = u.Quantity(np.nan, u.m)
        reconstruction_container.h_max_uncert = np.nan
        reconstruction_container.is_valid = False
        reconstruction_container.tel_ids = []
        reconstruction_container.average_intensity = np.nan
        reconstruction_container.goodness_of_fit = np.nan
        reconstruction_container.prefix = ''

    calculate_distance_to_core(telescope_event_containers, event, reconstruction_container)

    mc_container = copy.deepcopy(event.mc)
    mc_container.tel = None
    mc_container.prefix = 'mc'

    counter = Counter(telescope_types)
    array_event = ArrayEventContainer(
        array_event_id=event.dl0.event_id,
        run_id=event.r0.obs_id,
        reco=reconstruction_container,
        total_intensity=sum([t.hillas.intensity for t in telescope_event_containers.values()]),
        num_triggered_lst=counter['LST'],
        num_triggered_mst=counter['MST'],
        num_triggered_sst=counter['SST'],
        num_triggered_telescopes=len(telescope_types),
        mc=mc_container,
    )

    return array_event, list(telescope_event_containers.values())
Example #10
0
def process_event(
        event,
        telescopes,
        subarray,
        stereo,
        calib,
        apply_quality_cuts):
    
    if stereo:
        hillas_containers = {}
        telescope_pointings = {}
    
    horizon_frame = AltAz()
    reco = HillasReconstructor()
    
    # Calibrate the event
    calib(event)
    
    # Container to count what telescopes types were triggered in this event
    event_tels = []
    tel_data = []

    for tel_id, dl1 in event.dl1.tel.items():

        # If specific telescopes were specified, skip those that weren't
        if telescopes is not None:
            if (tel_id not in telescopes):
                continue

        tel = subarray.tels[tel_id]

        # Process the telescope event
        tel_type, tel_data_dict, hillas_c = process_telescope(tel, dl1, stereo)

        # Add the telescope event data if it wasn't skipped
        if tel_type is not None:

            if apply_quality_cuts:
                if tel_data_dict['intensity'] < 60 or tel_data_dict['nislands'] > 3 or tel_data_dict['n_survived_pixels'] < 6 or tel_data_dict['intensity_width_1'] > 0.1:
                    continue

            # Calculate mc impact distance
            x1 = event.mc.core_x.value
            y1 = event.mc.core_y.value
            x2 = subarray.positions[tel_id][0].value
            y2 = subarray.positions[tel_id][1].value

            v = [x2 - x1, y2 - y1]
            mc_impact_distance = np.linalg.norm(v)


            # Adding a few extras to the telescope event data
            tel_data_dict.update({
                'array_event_id': event.index.event_id,
                'run_id': event.index.obs_id,
                'telescope_id': tel_id,
                'telescope_event_id': tel_id,
                'mc_impact_distance': mc_impact_distance})

            # Add data to the tables used for geometric reconstruction
            if stereo:
                
                tel_data_dict.update({'impact_distance': np.nan})

                hillas_containers[tel_id] = hillas_c

                telescope_pointings[tel_id] = SkyCoord(
                    alt=event.mc.tel[tel_id].altitude_raw * u.rad,
                    az=event.mc.tel[tel_id].azimuth_raw * u.rad,
                    frame=horizon_frame)

            event_tels.append(tel_type)
            tel_data.append(tel_data_dict)

    # Skip event if no telescopes were processed
    if len(event_tels) == 0:
        return None, None

    arr_data = {}

    if stereo and len(event_tels) > 1:
        array_pointing = SkyCoord(
            az=event.mcheader.run_array_direction[0],
            alt=event.mcheader.run_array_direction[1],
            frame=horizon_frame)

        # Do geometric direction reconstruction
        reconst = reco.predict(
            hillas_containers,
            subarray,
            array_pointing,
            telescope_pointings)
        
        # Calculate impact distance from reconstructed core position
        for i in range(len(tel_data)):
            x1 = reconst.core_x.value
            y1 = reconst.core_y.value
            x2 = subarray.positions[tel_data[i]['telescope_id']][0].value
            y2 = subarray.positions[tel_data[i]['telescope_id']][1].value

            v = [x2 - x1, y2 - y1]
            impact_distance = np.linalg.norm(v)
            
            tel_data[i]['impact_distance'] = impact_distance
        
        # Grab the extra stereo info for array_events

        reconst_az = reconst.az.value
        if reconst_az < -np.pi:
            reconst_az += 2*np.pi
        if reconst_az > np.pi:
            reconst_az -= 2*np.pi
        
        arr_data.update({
            'alt': reconst.alt.value,
            'alt_uncert': reconst.alt_uncert.value,
            'average_intensity': reconst.average_intensity,
            'az': reconst_az,
            'az_uncert': reconst.az_uncert,
            'core_uncert': reconst.core_uncert,
            'core_x': reconst.core_x.value,
            'core_y': reconst.core_y.value,
            'goodness_of_fit': reconst.goodness_of_fit,
            'h_max': reconst.h_max.value,
            'h_max_uncert': reconst.h_max_uncert,
            'is_valid': reconst.is_valid,
            'stereo_flag': True})

    if stereo and len(event_tels) == 1:

        # If only one telescope is triggered in a stereo array, replace
        # stereo features with NaN
        arr_data.update({
            'alt': np.nan,
            'alt_uncert': np.nan,
            'average_intensity': np.nan,
            'az': np.nan,
            'az_uncert': np.nan,
            'core_uncert': np.nan,
            'core_x': np.nan,
            'core_y': np.nan,
            'goodness_of_fit': np.nan,
            'h_max': np.nan,
            'h_max_uncert': np.nan,
            'is_valid': np.nan,
            'stereo_flag': False})

    # Grab info for array event data
    arr_data.update({
        'array_event_id': event.index.event_id,
        'run_id': event.index.obs_id,
        'azimuth_raw': event.mc.tel[tel_id].azimuth_raw,
        'altitude_raw': event.mc.tel[tel_id].altitude_raw,
        'azimuth_cor': event.mc.tel[tel_id].azimuth_cor,
        'altitude_cor': event.mc.tel[tel_id].altitude_cor,
        'num_triggered_lst': event_tels.count('LST'),
        'num_triggered_mst': event_tels.count('MST'),
        'num_triggered_sst': event_tels.count('SST'),

        'mc_energy': event.mc.energy.value,
        'true_source_alt': event.mc.alt.value,
        'true_source_az': event.mc.az.value,
        'mc_core_x': event.mc.core_x.value,
        'mc_core_y': event.mc.core_y.value,
        'mc_h_first_int': event.mc.h_first_int.value,
        'mc_x_max': event.mc.x_max.value,
        'mc_shower_primary_id': event.mc.shower_primary_id})

    return tel_data, arr_data
Example #11
0
            # display.image = cleaned
            # display.add_colorbar()
            #
            # display.overlay_moments(hillas_c, color='xkcd:red')
            # plt.figure()
            # plt.show()

        # array pointing needed for the creation of the TiltedFrame to perform the impact point reconstruction
        array_pointing = SkyCoord(az=event.mc.az, alt=event.mc.alt, frame=horizon_frame)

        event_info_c.event_id = event.r0.event_id
        event_info_c.obs_id = event.r0.obs_id

        if len(hillas_dict) > 1:
            stereo = reco.predict(
                hillas_dict, event.inst, array_pointing, telescope_pointings
            )

            writer.write(table_name='reconstructed',
                         containers=[stereo, event_info_c])
            writer.write(table_name='true', containers=[event.mc, event_info_c])

        # save event for plotting later
        # if event.count == 3:
        #     plotting_event = deepcopy(event)
        #     plotting_hillas = hillas_dict
        #     #plotting_timing = time_gradients
        #     plotting_stereo = stereo


# thrown the simtel energy histogram needs to loop over all the simtel file so we don't dump in case of tests
            time_gradients[telescope_id] = hillas_c.skewness
        print(camera.cam_id, time_gradients[telescope_id])

        # make sure each telescope get's an arrow
        if abs(time_gradients[telescope_id]) < 0.2:
            time_gradients[telescope_id] = 1

    # ignore events with less than two telescopes
    if len(hillas_containers) < 2:
        continue
    array_pointing = SkyCoord(az=event.mcheader.run_array_direction[0],
                              alt=event.mcheader.run_array_direction[1],
                              frame=horizon_frame)
    stereo = reco.predict(
        hillas_containers,
        event.inst,
        array_pointing,
    )

    plt.figure()
    angle_offset = event.mcheader.run_array_direction[0]
    disp = ArrayDisplay(event.inst.subarray)

    disp.set_vector_hillas(hillas_containers,
                           time_gradient=time_gradients,
                           angle_offset=angle_offset,
                           length=500)
    plt.scatter(
        event.mc.core_x,
        event.mc.core_y,
        s=200,
def main():

    input_url = find_in_path(
        'gamma_20deg_0deg_run101___cta-prod3-lapalma3-2147m-LaPalma.simtel.gz',
        '/home/matteo/ctasoft/resources')
    events = event_source(input_url, max_events=1)

    calibrator = CameraCalibrator()
    horizon_frame = AltAz()
    reco = HillasReconstructor()

    hillas_containers = {}

    for event in events:
        calibrator(event)

        print("energy: ", event.mc.energy)

        for telescope_id, dl1 in event.dl1.tel.items():

            image = dl1.image
            # peakpos = dl1.pulse_time
            camera = event.inst.subarray.tels[telescope_id].camera

            # display dl1 images
            """fig, axs = plt.subplots(1, 1, figsize=(6, 3))
            d1 = CameraDisplay(camera, ax=axs)
    
            axs.set_title('Image ' + camera.cam_id)
            d1.image = dl1.image
            plt.show()
            plt.close(fig)"""

            # cleaning
            boundary, picture, min_neighbors = cleaning_level[camera.cam_id]
            clean = tailcuts_clean(camera,
                                   image,
                                   boundary_thresh=boundary,
                                   picture_thresh=picture,
                                   min_number_picture_neighbors=min_neighbors)

            if clean.sum() < 5:
                continue

            # SkyCoord
            array_pointing_event = SkyCoord(
                az=event.mcheader.run_array_direction[0],
                alt=event.mcheader.run_array_direction[1],
                frame=horizon_frame)

            hillas_c = hillas_parameters(camera[clean], image[clean])
            hillas_containers[telescope_id] = hillas_c

            if len(hillas_containers) < 2:
                continue

            reconstruction_cleaned = reco.predict(
                hillas_containers,
                event.inst,
                array_pointing_event,
            )

            array_pointing_reconstruction = SkyCoord(
                az=reconstruction_cleaned.az,
                alt=reconstruction_cleaned.alt,
                frame=horizon_frame)

            # angular distance between reconstructed and event image
            print(
                "angular distance: ",
                array_pointing_event.separation(array_pointing_reconstruction))

            # ground truth difference
            cleaned_image = dl1.image.copy()
            cleaned_image[~clean] = 0.0
            ground_truth_image = event.mc.tel[
                telescope_id].photo_electron_image
            diff = np.linalg.norm(cleaned_image - ground_truth_image)
            print("average ground truth difference: ", np.average(diff))

            cl = closest_approach_distance(hillas_containers[telescope_id])

            print("closest approach: ", cl)
            """# display ground truth image
Example #14
0
        # It fails for empty pixels
        try:
            hillas_params[tel_id] = hillas_parameters(camgeom, cleaned_image)
        except:
            pass

    array_pointing = SkyCoord(
        az=event.mcheader.run_array_direction[0],
        alt=event.mcheader.run_array_direction[1],
        frame=horizon_frame
    )

    if len(hillas_params) < 2:
        continue

    reco_result = reco.predict(hillas_params, event.inst, array_pointing, telescope_pointings)

    # get angular offset between reconstructed shower direction and MC
    # generated shower direction
    off_angle = angular_separation(
        event.mc.az,
        event.mc.alt,
        reco_result.az,
        reco_result.alt
    )

    # Appending all estimated off angles
    off_angles.append(off_angle.to(u.deg).value)

# calculate theta square for angles which are not nan
off_angles = np.array(off_angles)
Example #15
0
class Reconstructor:
    def __init__(self,
                 events_path: str,
                 telescopes_path: str,
                 replace_folder: str = None,
                 version="ML1",
                 telescopes: list = None):
        if version == "ML2":
            raise NotImplementedError(
                "This reconstructor is not implemented to work with ML2 yet")
        self.version = version
        self.dataset = load_dataset(events_path,
                                    telescopes_path,
                                    replace_folder=replace_folder)

        if telescopes is not None:
            if isinstance(telescopes, str):
                telescopes = [telescopes]
            self.dataset = filter_dataset(self.dataset, telescopes)

        self.reconstructor = HillasReconstructor()
        self.array_directions = dict()
        for hdf5_file in self.hdf5_files:
            self.array_directions[hdf5_file] = load_array_direction(hdf5_file)

        self.cameras_by_event = dict(
            (event_id, []) for event_id in self.event_uids)
        self.n_tels_by_event = {}

        tel_ids = dict()
        repeated_tel_ids = list()
        for event_id, tel_type, tel_id, obs_id, folder, source, x, y in zip(
                self.dataset["event_unique_id"], self.dataset["type"],
                self.dataset["telescope_id"],
                self.dataset["observation_indice"], self.dataset["folder"],
                self.dataset["source"], self.dataset["x"], self.dataset["y"]):
            if event_id not in tel_ids:
                tel_ids[event_id] = list()
            if event_id not in self.n_tels_by_event:
                self.n_tels_by_event[event_id] = {}
            if tel_type not in self.n_tels_by_event[event_id]:
                self.n_tels_by_event[event_id][tel_type] = 1
            else:
                self.n_tels_by_event[event_id][tel_type] += 1

            if tel_id in tel_ids[event_id]:
                repeated_tel_ids.append(tel_id)
            tel_ids[event_id].append(tel_id)
            self.cameras_by_event[event_id].append(
                (obs_id, tel_id, tel_type, folder, source, x, y))

    @property
    def event_uids(self) -> list:
        return self.dataset["event_unique_id"].unique()

    @property
    def hdf5_files(self) -> np.ndarray:
        return (self.dataset["folder"] + "/" + self.dataset["source"]).unique()

    def get_event_hdf5_file(self, event_id: str, tel_id: str):
        event_group = self.dataset.groupby("event_unique_id").get_group(
            event_id)
        tel_row: DataFrame = event_group.loc[event_group["telescope_id"] ==
                                             tel_id]
        tel_row = tel_row.loc[tel_row.index[0]]
        return tel_row["folder"] + "/" + tel_row["source"]

    @property
    def camera_radius(self):
        return get_camera_radius(self.dataset)

    @property
    def mc_values(self):
        values = dict()
        events = self.dataset[[
            "event_unique_id", "alt", "az", "mc_energy", "core_x", "core_y"
        ]].drop_duplicates().set_index(["event_unique_id"])
        for event_uid in self.event_uids:
            event = events.loc[event_uid]
            values[event_uid] = dict(alt=event["alt"],
                                     az=event["az"],
                                     mc_energy=event["mc_energy"],
                                     core_x=event["core_x"],
                                     core_y=event["core_y"])
        return values

    def reconstruct_event(self,
                          event_id: str,
                          event_cutflow: CutFlow,
                          obs_cutflow: CutFlow,
                          energy_regressor=None,
                          loose=False) -> Union[None, dict, Tuple[dict, dict]]:

        run_array_direction = None
        n_valid_tels = 0

        hillas_containers = dict()
        hillas_by_obs = dict()
        time_gradients = dict()
        leakages = dict()
        types = dict()
        n_islands = dict()
        positions = dict()
        meta = dict()
        for obs_id, tel_id, tel_type, folder, source, x, y in self.cameras_by_event[
                event_id]:
            _, camera_name = split_tel_type(tel_type)

            charge, peak = load_camera(source,
                                       folder,
                                       tel_type,
                                       obs_id,
                                       version=self.version)
            params = get_observation_parameters(charge,
                                                peak,
                                                camera_name,
                                                obs_cutflow,
                                                cut=not loose)
            if params is None:
                continue
            moments, leakage_c, _, time_gradient, obs_n_islands = params

            # assert tel_id not in hillas_containers.keys()
            hillas_containers[tel_id] = moments

            assert (tel_type, obs_id) not in hillas_by_obs.keys()
            meta[tel_id] = (tel_type, obs_id)
            hillas_by_obs[(tel_type, obs_id)] = moments
            positions[tel_id] = (x, y)
            time_gradients[(tel_type, obs_id)] = time_gradient
            leakages[(tel_type, obs_id)] = leakage_c
            n_islands[(tel_type, obs_id)] = obs_n_islands
            types[tel_id] = tel_type

            hdf5_file = self.get_event_hdf5_file(event_id, tel_id)
            telescope_alias = TELESCOPES_ALIAS[self.version][tel_type]
            run_array_direction = self.array_directions[hdf5_file][
                telescope_alias][tel_id]

            n_valid_tels += 1
        subarray = generate_subarray_description(self.dataset, event_id)
        if event_cutflow.cut(CFE_MIN_TELS_RECO, len(hillas_containers)):
            return

        array_pointing = SkyCoord(az=run_array_direction[0] * u.rad,
                                  alt=run_array_direction[1] * u.rad,
                                  frame="altaz")
        reco = self.reconstructor.predict(hillas_containers, subarray,
                                          array_pointing)
        mc = self.mc_values

        if energy_regressor is None:
            energy = None
        else:
            energy = energy_regressor.predict_event(positions, types,
                                                    hillas_containers, reco,
                                                    time_gradients, leakages,
                                                    n_islands, meta)

        return dict(pred_az=2 * np.pi * u.rad + reco.az,
                    pred_alt=reco.alt,
                    pred_core_x=reco.core_x,
                    pred_core_y=reco.core_y,
                    h_max=reco.h_max,
                    alt=mc[event_id]["alt"] * u.rad,
                    az=mc[event_id]["az"] * u.rad,
                    core_x=mc[event_id]["core_x"],
                    core_y=mc[event_id]["core_y"],
                    mc_energy=mc[event_id]["mc_energy"],
                    energy=energy,
                    time_gradients=time_gradients,
                    leakages=leakages,
                    event_id=event_id,
                    n_islands=n_islands,
                    hillas=hillas_by_obs,
                    run_array_direction=run_array_direction)

    def get_event_cams_and_foclens(self, event_id: str):
        subarray = generate_subarray_description(self.dataset, event_id)
        tel_types = subarray.telescope_types
        return {
            tel_types[i].camera.camera_name:
            tel_types[i].optics.equivalent_focal_length.value
            for i in range(len(tel_types))
        }

    def reconstruct_all(self,
                        max_events=None,
                        min_valid_observations=2,
                        energy_regressor=None,
                        npix_bounds: Tuple[float, float] = None,
                        charge_bounds: Tuple[float, float] = None,
                        ellipticity_bounds: Tuple[float, float] = None,
                        nominal_distance_bounds: Tuple[float, float] = None,
                        save_to: str = None,
                        save_hillas: str = None,
                        loose: bool = False) -> dict:
        event_ids = self.event_uids
        if max_events is not None and max_events < len(event_ids):
            event_ids = event_ids[:max_events]

        event_cutflow = generate_event_cutflow(min_valid_observations)
        obs_cutflow = generate_observation_cutflow(self.camera_radius,
                                                   npix_bounds, charge_bounds,
                                                   ellipticity_bounds,
                                                   nominal_distance_bounds)

        reconstructions = {}
        for event_id in tqdm(event_ids):
            reco = self.reconstruct_event(event_id,
                                          event_cutflow,
                                          obs_cutflow,
                                          energy_regressor=energy_regressor,
                                          loose=loose)
            if reco is None:
                continue
            reconstructions[event_id] = reco

        print()
        print("==Observations Cutflow Summary==")
        for cut in obs_cutflow.cuts.items():
            print(cut[0], cut[1][1])
        print()
        print("==Event Cutflow Summary==")
        for cut in event_cutflow.cuts.items():
            print(cut[0], cut[1][1])
        print()
        perc_reconstructed = 100 * len(reconstructions) / len(event_ids)
        print(
            f"N. Events Reconstructed: {len(reconstructions)}/{len(event_ids)} ({perc_reconstructed}%)"
        )

        if save_to is not None:
            self.save_predictions(reconstructions, save_to)

        if save_hillas is not None:
            self.save_hillas_params(reconstructions, save_hillas)

        return reconstructions

    def plot_metrics(self,
                     max_events: int = None,
                     min_valid_observations=2,
                     energy_regressor: EnergyModel = None,
                     plot_charges: bool = False,
                     save_to: str = None,
                     save_hillas: str = None,
                     save_plots: str = None):
        import ctaplot

        reco = self.reconstruct_all(
            max_events,
            min_valid_observations=min_valid_observations,
            energy_regressor=energy_regressor)

        preds = list(reco.values())

        if save_plots is not None:
            reco_alt = np.array(
                [pred['pred_alt'] / (1 * u.rad) for pred in preds])
            reco_az = np.array(
                [pred['pred_az'] / (1 * u.rad) for pred in preds])

            alt = np.array([pred['alt'] / (1 * u.rad) for pred in preds])
            az = np.array([pred['az'] / (1 * u.rad) for pred in preds])
            if energy_regressor is not None:
                energy = np.array([pred['energy'] for pred in preds])
            mc_energy = np.array([pred['mc_energy'] for pred in preds])

            _, (ax1, ax2) = plt.subplots(1, 2)
            ctaplot.plot_angular_resolution_per_energy(reco_alt,
                                                       reco_az,
                                                       alt,
                                                       az,
                                                       mc_energy,
                                                       ax=ax1)
            if energy_regressor is not None:
                ctaplot.plot_energy_resolution(mc_energy, energy, ax=ax2)

            plt.savefig(save_plots)

    @staticmethod
    def plot_prediction_vs_real(pred, real, ax, title):
        ax.scatter(real, pred)
        ax.set_title(title)
        ax.grid('on')

    @staticmethod
    def plot(results: DataFrame, save_to: str):
        import os
        from gerumo import plot_error_and_angular_resolution

        results['true_alt'] = results['alt']
        results['true_az'] = results['az']

        results['true_mc_energy'] = results['mc_energy']

        plot_error_and_angular_resolution(results,
                                          save_to=os.path.join(
                                              save_to,
                                              "angular_resolution.png"),
                                          ylim=[0, 2])

        fig, axes = plt.subplots(1, 2)
        ax1, ax2 = axes
        Reconstructor.plot_prediction_vs_real(results['pred_alt'],
                                              results['alt'], ax1, "Altitude")
        Reconstructor.plot_prediction_vs_real(
            results['pred_az'].apply(
                lambda rad: np.arctan2(np.sin(rad), np.cos(rad))),
            results['az'].apply(
                lambda rad: np.arctan2(np.sin(rad), np.cos(rad))), ax2,
            "Azimuth")

        fig.savefig(os.path.join(save_to, "scatter.png"))

    @staticmethod
    def save_predictions(reco: dict, path: str):
        reco = {
            event_id: dict(pred_az=r["pred_az"].value,
                           pred_alt=r["pred_alt"].value,
                           pred_core_x=r["pred_core_x"].value,
                           pred_core_y=r["pred_core_y"].value,
                           alt=r["alt"].value,
                           az=r["az"].value,
                           core_x=r["core_x"],
                           core_y=r["core_y"],
                           mc_energy=r["mc_energy"],
                           energy=r["energy"],
                           h_max=r["h_max"].value)
            for event_id, r in reco.items()
        }
        df = DataFrame.from_dict(reco, orient="index")
        df.to_csv(path, sep=',', index_label="event_unique_id")

    @staticmethod
    def save_hillas_params(reco: dict, path: str):
        columns = [
            "event_unique_id", "observation_indice", "type", "intensity",
            "kurtosis", "length", "phi", "psi", "r", "skewness", "width", "x",
            "y", "wl", "time_gradient", "leakage_intensity_width_2",
            "n_islands", "telescope_az", "telescope_alt"
        ]

        params = list()
        for event_id, r in reco.items():
            run_array_direction = r["run_array_direction"]
            for (tel_type, obs_id), moments in r["hillas"].items():
                time_gradient = r["time_gradients"][(tel_type, obs_id)]
                leakage_c = r["leakages"][(tel_type, obs_id)]
                n_islands = r["n_islands"][(tel_type, obs_id)]
                obs_params = (event_id, obs_id, tel_type, moments.intensity,
                              moments.kurtosis, moments.length.value,
                              moments.phi.value, moments.psi.value,
                              moments.r.value, moments.skewness,
                              moments.width.value, moments.x.value,
                              moments.y.value, moments.width.value /
                              moments.length.value, time_gradient,
                              leakage_c.intensity_width_2, n_islands,
                              run_array_direction[0], run_array_direction[1])
                params.append(obs_params)
        df = DataFrame(params, columns=columns).set_index(columns[:3])
        df.to_csv(path, sep=',')