Source code for magicctapipe.utils.functions

"""
Miscellanea functions
"""
import numpy as np
import pandas as pd
from astropy import units as u
from astropy.coordinates import (
    AltAz,
    Angle,
    EarthLocation,
    SkyCoord,
    SkyOffsetFrame,
    angular_separation,
)
from ctapipe.coordinates import TelescopeFrame

__all__ = [
    "calculate_disp",
    "calculate_impact",
    "calculate_mean_direction",
    "calculate_off_coordinates",
    "transform_altaz_to_radec",
    "LON_ORM",
    "LAT_ORM",
    "HEIGHT_ORM",
]

# The geographic coordinate of ORM
LON_ORM = -17.89064 * u.deg
LAT_ORM = 28.76177 * u.deg
HEIGHT_ORM = 2199.835 * u.m


[docs] @u.quantity_input( pointing_alt=u.rad, pointing_az=u.rad, shower_alt=u.deg, shower_az=u.deg, cog_x=u.m, cog_y=u.m, ) def calculate_disp( pointing_alt, pointing_az, shower_alt, shower_az, cog_x, cog_y, camera_frame, ): """ Calculates the DISP parameter, i.e., the angular distance between the event arrival direction and the Center of Gravity (CoG) of the shower image. Parameters ---------- pointing_alt : astropy.units.quantity.Quantity Altitude of the telescope pointing direction pointing_az : astropy.units.quantity.Quantity Azimuth of the telescope pointing direction shower_alt : astropy.units.quantity.Quantity Altitude of the event arrival direction shower_az : astropy.units.quantity.Quantity Azimuth of the event arrival direction cog_x : astropy.units.quantity.Quantity Image CoG along the X coordinate of the camera geometry cog_y : astropy.units.quantity.Quantity Image CoG along the Y coordinate of the camera geometry camera_frame : ctapipe.coordinates.camera_frame.CameraFrame Camera frame of the telescope Returns ------- astropy.units.quantity.Quantity DISP parameter """ tel_pointing = AltAz(alt=pointing_alt, az=pointing_az) tel_frame = TelescopeFrame(telescope_pointing=tel_pointing) # Transform the image CoG position to the Alt/Az direction cog_coord = SkyCoord(cog_x, cog_y, frame=camera_frame) cog_coord = cog_coord.transform_to(tel_frame).altaz # Calculate the DISP parameter disp = angular_separation( lon1=cog_coord.az, lat1=cog_coord.alt, lon2=shower_az, lat2=shower_alt ) return disp
[docs] @u.quantity_input( shower_alt=u.deg, shower_az=u.deg, core_x=u.m, core_y=u.m, tel_pos_x=u.m, tel_pos_y=u.m, tel_pos_z=u.m, ) def calculate_impact( shower_alt, shower_az, core_x, core_y, tel_pos_x, tel_pos_y, tel_pos_z, ): """ Calculates the impact parameter, i.e., the closest distance between a shower axis and a telescope position. It uses the equations derived from a hand calculation, but the result is consistent with what calculated by MARS. Since ctapipe v0.16.0 a function to calculate the impact parameter is implemented, so we may replace it to the official one in future. Parameters ---------- shower_alt : astropy.units.quantity.Quantity Altitude of the event arrival direction shower_az : astropy.units.quantity.Quantity Azimuth of the event arrival direction core_x : astropy.units.quantity.Quantity Core position along the geographic north core_y : astropy.units.quantity.Quantity Core position along the geographic west tel_pos_x : astropy.units.quantity.Quantity Telescope position along the geographic north tel_pos_y : astropy.units.quantity.Quantity Telescope position along the geographic west tel_pos_z : astropy.units.quantity.Quantity Telescope height from the reference altitude Returns ------- astropy.units.quantity.Quantity Impact parameter """ diff_x = tel_pos_x - core_x diff_y = tel_pos_y - core_y param = ( diff_x * np.cos(shower_alt) * np.cos(shower_az) - diff_y * np.cos(shower_alt) * np.sin(shower_az) + tel_pos_z * np.sin(shower_alt) ) impact = np.sqrt( (param * np.cos(shower_alt) * np.cos(shower_az) - diff_x) ** 2 + (param * np.cos(shower_alt) * np.sin(shower_az) + diff_y) ** 2 + (param * np.sin(shower_alt) - tel_pos_z) ** 2 ) return impact
[docs] def calculate_mean_direction(lon, lat, unit, weights=None): """ Calculates the mean direction per shower event. Please note that the input is supposed to be the pandas Series with the multi index (`obs_id`, `event_id`) to group telescope events. Parameters ---------- lon : pandas.core.series.Series Longitude in a spherical coordinate lat : pandas.core.series.Series Latitude in a spherical coordinate unit : str Unit of the input (and output) angles - "deg", "degree", "rad" or "radian" are allowed weights : pandas.core.series.Series Weights for the input directions Returns ------- lon_mean : pandas.core.series.Series Longitude of the mean direction lat_mean : pandas.core.series.Series Latitude of the mean direction """ if unit in ["deg", "degree"]: lon = np.deg2rad(lon) lat = np.deg2rad(lat) # Transform the input directions to the cartesian coordinate and # then calculate the mean position for each axis x_coords = np.cos(lat) * np.cos(lon) y_coords = np.cos(lat) * np.sin(lon) z_coords = np.sin(lat) if weights is None: x_coord_mean = x_coords.groupby(["obs_id", "event_id"]).mean() y_coord_mean = y_coords.groupby(["obs_id", "event_id"]).mean() z_coord_mean = z_coords.groupby(["obs_id", "event_id"]).mean() else: df_cartesian = pd.DataFrame( data={ "weight": weights, "weighted_x_coord": x_coords * weights, "weighted_y_coord": y_coords * weights, "weighted_z_coord": z_coords * weights, } ) group_sum = df_cartesian.groupby(["obs_id", "event_id"]).sum() x_coord_mean = group_sum["weighted_x_coord"] / group_sum["weight"] y_coord_mean = group_sum["weighted_y_coord"] / group_sum["weight"] z_coord_mean = group_sum["weighted_z_coord"] / group_sum["weight"] coord_mean = SkyCoord( x=x_coord_mean, y=y_coord_mean, z=z_coord_mean, representation_type="cartesian" ) # Transform to the spherical coordinate coord_mean = coord_mean.spherical lon_mean = pd.Series(data=coord_mean.lon.to_value(unit), index=x_coord_mean.index) lat_mean = pd.Series(data=coord_mean.lat.to_value(unit), index=x_coord_mean.index) return lon_mean, lat_mean
[docs] @u.quantity_input( pointing_ra=u.deg, pointing_dec=u.deg, on_coord_ra=u.deg, on_coord_dec=u.deg ) def calculate_off_coordinates( pointing_ra, pointing_dec, on_coord_ra, on_coord_dec, n_regions, ): """ Calculates the coordinates of the centers of OFF regions to estimate the backgrounds for wobble observation data. It calculates the wobble rotation angle with equations derived from a hand calculation. Parameters ---------- pointing_ra : astropy.units.quantity.Quantity Right ascension of the telescope pointing direction pointing_dec : astropy.units.quantity.Quantity Declination of the telescope pointing direction on_coord_ra : astropy.units.quantity.Quantity Right ascension of the center of the ON region on_coord_dec : astropy.units.quantity.Quantity Declination of the center of the ON region n_regions : int Number of OFF regions to be created Returns ------- dict Coordinates of the centers of the OFF regions """ wobble_coord = SkyCoord(pointing_ra, pointing_dec, frame="icrs") # Calculate the wobble offset wobble_offset = angular_separation( lon1=pointing_ra, lat1=pointing_dec, lon2=on_coord_ra, lat2=on_coord_dec ) # Calculate the wobble rotation angle ra_diff = pointing_ra - on_coord_ra numerator = np.sin(pointing_dec) * np.cos(on_coord_dec) numerator -= np.cos(pointing_dec) * np.sin(on_coord_dec) * np.cos(ra_diff) denominator = np.cos(pointing_dec) * np.sin(ra_diff) wobble_rotation = np.arctan2(numerator, denominator) wobble_rotation = Angle(wobble_rotation).wrap_at("360 deg") # Calculate the rotation angles for the OFF regions. Here we remove # the angle 180 deg with which the OFF region will be created at the # same coordinate as the ON region. rotation_step = 360 / (n_regions + 1) rotations_off = np.arange(0, 359, rotation_step) * u.deg rotations_off = rotations_off[rotations_off.to_value("deg") != 180] rotations_off += wobble_rotation off_coords = {} # Loop over every rotation angle for i_off, rotation in enumerate(rotations_off, start=1): skyoffset_frame = SkyOffsetFrame(origin=wobble_coord, rotation=-rotation) # Calculate the OFF coordinate off_coord = SkyCoord(wobble_offset, "0 deg", frame=skyoffset_frame) off_coord = off_coord.transform_to("icrs") off_coords[i_off] = off_coord return off_coords
[docs] @u.quantity_input(alt=u.deg, az=u.deg) def transform_altaz_to_radec(alt, az, obs_time): """ Transforms the Alt/Az direction measured from ORM to the RA/Dec coordinate. Parameters ---------- alt : astropy.units.quantity.Quantity Altitude measured from ORM az : astropy.units.quantity.Quantity Azimuth measured from ORM obs_time : astropy.time.core.Time Time when the direction was measured Returns ------- ra : astropy.coordinates.Longitude Right ascension of the input direction dec : astropy.coordinates.Latitude Declination of the input direction """ location = EarthLocation.from_geodetic(lon=LON_ORM, lat=LAT_ORM, height=HEIGHT_ORM) horizon_frames = AltAz(location=location, obstime=obs_time) # Transform to the RA/Dec coordinate event_coord = SkyCoord(alt=alt, az=az, frame=horizon_frames) event_coord = event_coord.transform_to("icrs") ra = event_coord.ra dec = event_coord.dec return ra, dec