Source code for ctapipe.containers

"""
Container structures for data that should be read or written to disk
"""

import enum
from functools import partial

import numpy as np
from astropy import units as u
from astropy.time import Time
from numpy import nan

from .core import Container, Field, Map

__all__ = [
    "ArrayEventContainer",
    "ConcentrationContainer",
    "DL0CameraContainer",
    "DL0Container",
    "DL1CameraContainer",
    "DL1Container",
    "DL2Container",
    "EventIndexContainer",
    "EventType",
    "HillasParametersContainer",
    "CoreParametersContainer",
    "ImageParametersContainer",
    "LeakageContainer",
    "CameraCalibrationContainer",
    "CameraMonitoringContainer",
    "TelescopeMonitoringContainer",
    "MonitoringContainer",
    "MorphologyContainer",
    "MuonRingContainer",
    "MuonEfficiencyContainer",
    "MuonParametersContainer",
    "MuonTelescopeContainer",
    "MuonContainer",
    "BaseHillasParametersContainer",
    "CameraHillasParametersContainer",
    "CameraTimingParametersContainer",
    "ParticleClassificationContainer",
    "PixelStatisticsContainer",
    "R0CameraContainer",
    "R0Container",
    "R1CameraContainer",
    "R1Container",
    "ReconstructedContainer",
    "ReconstructedEnergyContainer",
    "ReconstructedGeometryContainer",
    "DispContainer",
    "SimulatedCameraContainer",
    "SimulatedShowerContainer",
    "SimulatedShowerDistribution",
    "SimulationConfigContainer",
    "TelEventIndexContainer",
    "BaseTimingParametersContainer",
    "TimingParametersContainer",
    "TriggerContainer",
    "TelescopePointingContainer",
    "ArrayPointingContainer",
    "StatisticsContainer",
    "ChunkStatisticsContainer",
    "ImageStatisticsContainer",
    "IntensityStatisticsContainer",
    "PeakTimeStatisticsContainer",
    "SchedulingBlockContainer",
    "ObservationBlockContainer",
    "ObservingMode",
    "ObservationBlockState",
]


# see https://github.com/astropy/astropy/issues/6509
NAN_TIME = Time(0, format="mjd", scale="tai")

#: Used for unsigned integer obs_id or sb_id default values:
UNKNOWN_ID = np.uint64(np.iinfo(np.uint64).max)
#: Used for unsigned integer tel_id default value
UNKNOWN_TEL_ID = np.uint16(np.iinfo(np.uint16).max)


obs_id_field = partial(Field, UNKNOWN_ID, description="Observation Block ID")
event_id_field = partial(Field, UNKNOWN_ID, description="Array Event ID")
tel_id_field = partial(Field, UNKNOWN_TEL_ID, description="Telescope ID")


class SchedulingBlockType(enum.Enum):
    """
    Types of Scheduling Block
    """

    UNKNOWN = -1
    OBSERVATION = 0
    CALIBRATION = 1
    ENGINEERING = 2


[docs] class ObservationBlockState(enum.Enum): """Observation Block States. Part of the Observation Configuration data model. """ UNKNOWN = -1 FAILED = 0 COMPLETED_SUCCEDED = 1 COMPLETED_CANCELED = 2 COMPLETED_TRUNCATED = 3 ARCHIVED = 4
[docs] class ObservingMode(enum.Enum): """How a scheduling block is observed. Part of the Observation Configuration data model. """ UNKNOWN = -1 WOBBLE = 0 ON_OFF = 1 GRID = 2 CUSTOM = 3
class PointingMode(enum.Enum): """Describes how the telescopes move. Part of the Observation Configuration data model. """ UNKNOWN = -1 #: drives track a point that moves with the sky TRACK = 0 #: drives stay fixed at an alt/az point while the sky drifts by DRIFT = 1 class CoordinateFrameType(enum.Enum): """types of coordinate frames used in ObservationBlockContainers. Part of the Observation Configuration data model. """ UNKNOWN = -1 ALTAZ = 0 ICRS = 1 GALACTIC = 2
[docs] class EventType(enum.Enum): """R1/DL0 EventType Defined in :cite:p:`ctao-r1-event-data-model`. """ #: Flatfield event FLATFIELD = 0 #: Single PE event SINGLE_PE = 1 #: Generic pedestal, assigned if no further distinction on the type could be made PEDESTAL = 2 #: Dark pedestal, taken with HV on, shutter closed. DARK_PEDESTAL = 3 #: Electronics pedestal, taken with HV off. ELECTRONIC_PEDESTAL = 4 #: Sky pedestal, taken with HV on, shutter open SKY_PEDESTAL = 5 OTHER_CALIBRATION = 15 #: Muon event candidate MUON = 16 #: (LST) hardware-stereo trigger HARDWARE_STEREO = 17 #: Random mono trigger, used in simulations to force storage #: of event regardless of subarray trigger for certain studies. RANDOM_MONO = 18 #: ACADA (DAQ) software trigger DAQ = 24 #: Standard Physics trigger SUBARRAY = 32 #: Standard Physics trigger with extended readout window LONG_EVENT = 33 UNKNOWN = 255
class VarianceType(enum.Enum): """Enum of variance types used for the VarianceContainer""" # Simple variance of waveform WAVEFORM = 0 # Variance of integrated samples of a waveform INTEGRATED = 1 class PixelStatus(enum.IntFlag): """ Pixel status information See DL0 Data Model specification: https://redmine.cta-observatory.org/dmsf/files/17552/view """ DVR_0 = enum.auto() DVR_1 = enum.auto() HIGH_GAIN_STORED = enum.auto() LOW_GAIN_STORED = enum.auto() SATURATED = enum.auto() PIXEL_TRIGGER_0 = enum.auto() PIXEL_TRIGGER_1 = enum.auto() PIXEL_TRIGGER_2 = enum.auto() #: DVR status uses two bits: #: 0 = not stored; #: 1 = stored, passthrough (i.e. no DVR algorithm applied in R1->DL0 step); #: 2 = stored, but not identified as signal (e.g. neighbors of signal pixels); #: 3 = stored, identified as signal; DVR_STATUS = DVR_0 | DVR_1 #: Pixel trigger information, TBD PIXEL_TRIGGER = PIXEL_TRIGGER_0 | PIXEL_TRIGGER_1 | PIXEL_TRIGGER_2 @staticmethod def get_dvr_status(pixel_status): """ Return only the bits corresponding to the DVR_STATUS Returns ------- dvr_status: int or array[uint8] 0 = pixel not stored 1 = stored, passthrough (i.e. no DVR algorithm applied in R1->DL0 step) 2 = stored, but not identified as signal (e.g. neighbors of signal pixels) 3 = stored, identified as signal """ return pixel_status & PixelStatus.DVR_STATUS @staticmethod def get_channel_info(pixel_status): """ Return only the bits corresponding to the channel info (high/low gain stored) Returns ------- channel_info: int or array[uint8] 0 = pixel broken / disabled 1 = only high gain read out 2 = only low gain read out 3 = both gains read out """ gain_bits = PixelStatus.HIGH_GAIN_STORED | PixelStatus.LOW_GAIN_STORED return (pixel_status & gain_bits) >> 2 @staticmethod def is_invalid(pixel_status): """Return if pixel values are marked as invalid This is encoded in the data model as neither high gain nor low gain marked as stored """ gain_bits = PixelStatus.HIGH_GAIN_STORED | PixelStatus.LOW_GAIN_STORED return (pixel_status & gain_bits) == 0 class TelescopeConfigurationIndexContainer(Container): """Index to include for per-OB telescope configuration""" default_prefix = "" obs_id = obs_id_field() tel_id = tel_id_field()
[docs] class EventIndexContainer(Container): """Index columns to include in event lists. Common to all data levels """ default_prefix = "" obs_id = obs_id_field() event_id = event_id_field()
[docs] class TelEventIndexContainer(Container): """ index columns to include in telescope-wise event lists Common to all data levels that have telescope-wise information. """ default_prefix = "" obs_id = obs_id_field() event_id = event_id_field() tel_id = tel_id_field()
[docs] class BaseHillasParametersContainer(Container): """ Base container for hillas parameters to allow the CameraHillasParametersContainer to be assigned to an ImageParametersContainer as well. """ intensity = Field(nan, "total intensity (size)") skewness = Field(nan, "measure of the asymmetry") kurtosis = Field(nan, "measure of the tailedness")
[docs] class CameraHillasParametersContainer(BaseHillasParametersContainer): """ Hillas Parameters in the camera frame. The cog position is given in meter from the camera center. """ default_prefix = "camera_frame_hillas" x = Field(nan * u.m, "centroid x coordinate", unit=u.m) y = Field(nan * u.m, "centroid x coordinate", unit=u.m) r = Field(nan * u.m, "radial coordinate of centroid", unit=u.m) phi = Field(nan * u.deg, "polar coordinate of centroid", unit=u.deg) length = Field(nan * u.m, "standard deviation along the major-axis", unit=u.m) length_uncertainty = Field(nan * u.m, "uncertainty of length", unit=u.m) width = Field(nan * u.m, "standard spread along the minor-axis", unit=u.m) width_uncertainty = Field(nan * u.m, "uncertainty of width", unit=u.m) psi = Field(nan * u.deg, "rotation angle of ellipse", unit=u.deg) psi_uncertainty = Field(nan * u.deg, "uncertainty of psi", unit=u.deg) transverse_cog_uncertainty = Field( nan * u.m, "uncertainty on the center of gravity along the transverse axis of the image", unit=u.m, )
[docs] class HillasParametersContainer(BaseHillasParametersContainer): """ Hillas Parameters in a spherical system centered on the pointing position (TelescopeFrame). The cog position is given as offset in longitude and latitude in degree. """ default_prefix = "hillas" fov_lon = Field( nan * u.deg, "longitude angle in a spherical system centered on the pointing position", unit=u.deg, ) fov_lat = Field( nan * u.deg, "latitude angle in a spherical system centered on the pointing position", unit=u.deg, ) r = Field(nan * u.deg, "radial coordinate of centroid", unit=u.deg) phi = Field(nan * u.deg, "polar coordinate of centroid", unit=u.deg) length = Field(nan * u.deg, "standard deviation along the major-axis", unit=u.deg) length_uncertainty = Field(nan * u.deg, "uncertainty of length", unit=u.deg) width = Field(nan * u.deg, "standard spread along the minor-axis", unit=u.deg) width_uncertainty = Field(nan * u.deg, "uncertainty of width", unit=u.deg) psi = Field(nan * u.deg, "rotation angle of ellipse", unit=u.deg) psi_uncertainty = Field(nan * u.deg, "uncertainty of psi", unit=u.deg) transverse_cog_uncertainty = Field( nan * u.deg, "uncertainty on the center of gravity along the transverse axis of the image", unit=u.deg, )
[docs] class LeakageContainer(Container): """ Fraction of signal in 1 or 2-pixel width border from the edge of the camera, measured in number of signal pixels or in intensity. """ default_prefix = "leakage" pixels_width_1 = Field( nan, "fraction of pixels after cleaning that are in camera border of width=1" ) pixels_width_2 = Field( nan, "fraction of pixels after cleaning that are in camera border of width=2" ) intensity_width_1 = Field( np.float32(nan), "Intensity in photo-electrons after cleaning" " that are in the camera border of width=1 pixel", ) intensity_width_2 = Field( np.float32(nan), "Intensity in photo-electrons after cleaning" " that are in the camera border of width=2 pixels", )
[docs] class ConcentrationContainer(Container): """ Concentrations are ratios between light amount in certain areas of the image and the full image. """ default_prefix = "concentration" cog = Field( nan, "Percentage of photo-electrons inside one pixel diameter of the cog" ) core = Field(nan, "Percentage of photo-electrons inside the hillas ellipse") pixel = Field(nan, "Percentage of photo-electrons in the brightest pixel")
[docs] class BaseTimingParametersContainer(Container): """ Base container for timing parameters to allow the CameraTimingParametersContainer to be assigned to an ImageParametersContainer as well. """ intercept = Field(nan, "intercept of arrival times along main shower axis") deviation = Field( nan, "Root-mean-square deviation of the pulse times " "with respect to the predicted time", )
[docs] class CameraTimingParametersContainer(BaseTimingParametersContainer): """ Slope and Intercept of a linear regression of the arrival times along the shower main axis in the camera frame. """ default_prefix = "camera_frame_timing" slope = Field( nan / u.m, "Slope of arrival times along main shower axis", unit=1 / u.m )
[docs] class TimingParametersContainer(BaseTimingParametersContainer): """ Slope and Intercept of a linear regression of the arrival times along the shower main axis in a spherical system centered on the pointing position (TelescopeFrame) """ default_prefix = "timing" slope = Field( nan / u.deg, "Slope of arrival times along main shower axis", unit=1 / u.deg )
[docs] class MorphologyContainer(Container): """Parameters related to pixels surviving image cleaning""" n_pixels = Field(-1, "Number of usable pixels") n_islands = Field(-1, "Number of distinct islands in the image") n_small_islands = Field(-1, "Number of <= 2 pixel islands") n_medium_islands = Field(-1, "Number of 2-50 pixel islands") n_large_islands = Field(-1, "Number of > 50 pixel islands")
[docs] class ImageStatisticsContainer(Container): """Store descriptive image statistics""" max = Field(np.float32(nan), "value of pixel with maximum intensity") min = Field(np.float32(nan), "value of pixel with minimum intensity") mean = Field(np.float32(nan), "mean intensity") std = Field(np.float32(nan), "standard deviation of intensity") skewness = Field(nan, "skewness of intensity") kurtosis = Field(nan, "kurtosis of intensity")
[docs] class IntensityStatisticsContainer(ImageStatisticsContainer): default_prefix = "intensity"
[docs] class PeakTimeStatisticsContainer(ImageStatisticsContainer): default_prefix = "peak_time"
[docs] class CoreParametersContainer(Container): """Telescope-wise shower's direction in the Tilted/Ground Frame""" default_prefix = "core" psi = Field(nan * u.deg, "Image direction in the Tilted/Ground Frame", unit="deg")
[docs] class ImageParametersContainer(Container): """Collection of image parameters""" default_prefix = "params" hillas = Field( default_factory=HillasParametersContainer, description="Hillas Parameters", type=BaseHillasParametersContainer, ) timing = Field( default_factory=TimingParametersContainer, description="Timing Parameters", type=BaseTimingParametersContainer, ) leakage = Field( default_factory=LeakageContainer, description="Leakage Parameters", ) concentration = Field( default_factory=ConcentrationContainer, description="Concentration Parameters", ) morphology = Field( default_factory=MorphologyContainer, description="Image Morphology Parameters" ) intensity_statistics = Field( default_factory=IntensityStatisticsContainer, description="Intensity image statistics", ) peak_time_statistics = Field( default_factory=PeakTimeStatisticsContainer, description="Peak time image statistics", ) core = Field( default_factory=CoreParametersContainer, description="Image direction in the Tilted/Ground Frame", )
[docs] class DL1CameraContainer(Container): """ Storage of output of camera calibration e.g the final calibrated image in intensity units and the pulse time. """ image = Field( None, "Numpy array of camera image, after waveform extraction." "Shape: (n_pixel) if n_channels is 1 or data is gain selected" "else: (n_channels, n_pixel)", ) peak_time = Field( None, "Numpy array containing position of the peak of the pulse as determined by " "the extractor." "Shape: (n_pixel) if n_channels is 1 or data is gain selected" "else: (n_channels, n_pixel)", ) image_mask = Field( None, "Boolean numpy array where True means the pixel has passed cleaning." " Shape: (n_pixel, )", dtype=np.bool_, ndim=1, ) is_valid = Field( False, ( "True if image extraction succeeded, False if failed " "or in the case of TwoPass methods, that the first " "pass only was returned." ), ) parameters = Field( None, description="Image parameters", type=ImageParametersContainer )
[docs] class DL1Container(Container): """DL1 Calibrated Camera Images and associated data""" tel = Field( default_factory=partial(Map, DL1CameraContainer), description="map of tel_id to DL1CameraContainer", )
[docs] class R0CameraContainer(Container): """ Storage of raw data from a single telescope """ waveform = Field( None, ("numpy array containing ADC samples: (n_channels, n_pixels, n_samples)") )
[docs] class R0Container(Container): """ Storage of a Merged Raw Data Event """ tel = Field( default_factory=partial(Map, R0CameraContainer), description="map of tel_id to R0CameraContainer", )
[docs] class R1CameraContainer(Container): """ Storage of r1 calibrated data from a single telescope """ event_type = Field(EventType.UNKNOWN, "type of event", type=EventType) event_time = Field(NAN_TIME, "event timestamp") waveform = Field( None, ( "numpy array containing a set of images, one per ADC sample" "Shape: (n_channels, n_pixels, n_samples)" ), ) pixel_status = Field( None, "Array of pixel status values, see PixelStatus for definition of the values", ndim=1, dtype=np.uint8, ) first_cell_id = Field( None, "Array of first cell ids of the readout chips. Only used by LST and SST.", dtype=np.uint16, ndim=1, ) module_hires_local_clock_counter = Field( None, "Clock counter values of the camera modules. See R1 data model for details.", dtype=np.uint64, ndim=1, ) pedestal_intensity = Field( None, "Pedestal intensity in each pixel in DC", dtype=np.float32, ndim=1, ) calibration_monitoring_id = Field( None, "ID of the CalibrationMonitoringSet containing the applied pre-calibration parameters", ) selected_gain_channel = Field( None, ( "Numpy array containing the gain channel chosen for each pixel. " "Note: should be replaced by using ``pixel_status`` " "Shape: (n_pixels)" ), )
[docs] class R1Container(Container): """ Storage of a r1 calibrated Data Event """ tel = Field( default_factory=partial(Map, R1CameraContainer), description="map of tel_id to R1CameraContainer", )
[docs] class DL0CameraContainer(Container): """ Storage of data volume reduced dl0 data from a single telescope. See DL0 Data Model specification: https://redmine.cta-observatory.org/dmsf/files/17552/view """ event_type = Field(EventType.UNKNOWN, "type of event", type=EventType) event_time = Field(NAN_TIME, "event timestamp") waveform = Field( None, ( "numpy array containing data volume reduced " "p.e. samples" "(n_channels, n_pixels, n_samples). Note this may be a masked array, " "if pixels or time slices are zero-suppressed" ), ) pixel_status = Field( None, "Array of pixel status values, see PixelStatus for definition of the values", dtype=np.uint8, ndim=1, ) first_cell_id = Field( None, "Array of first cell ids of the readout chips. Only used by LST and SST.", dtype=np.uint16, ndim=1, ) calibration_monitoring_id = Field( None, "ID of the CalibrationMonitoringSet containing the applied pre-calibration parameters", ) selected_gain_channel = Field( None, ( "Numpy array containing the gain channel chosen for each pixel. " "Note: this should be replaced by only using ``pixel_status`` " "Shape: (n_pixels)" ), )
[docs] class DL0Container(Container): """ Storage of a data volume reduced Event """ tel = Field( default_factory=partial(Map, DL0CameraContainer), description="map of tel_id to DL0CameraContainer", )
class TelescopeImpactParameterContainer(Container): """ Impact Parameter computed from reconstructed shower geometry """ default_prefix = "impact" distance = Field( nan * u.m, "distance of the telescope to the shower axis", unit=u.m ) distance_uncert = Field(nan * u.m, "uncertainty in impact_parameter", unit=u.m) # Simulation containers
[docs] class SimulatedShowerContainer(Container): default_prefix = "true" energy = Field(nan * u.TeV, "Simulated Energy", unit=u.TeV) alt = Field(nan * u.deg, "Simulated altitude", unit=u.deg) az = Field(nan * u.deg, "Simulated azimuth", unit=u.deg) core_x = Field(nan * u.m, "Simulated core position (x)", unit=u.m) core_y = Field(nan * u.m, "Simulated core position (y)", unit=u.m) h_first_int = Field(nan * u.m, "Height of first interaction", unit=u.m) x_max = Field(nan * u.g / u.cm**2, "Simulated Xmax value", unit=u.g / u.cm**2) starting_grammage = Field( nan * u.g / u.cm**2, "Grammage (mass overburden) where the particle was injected into the atmosphere", unit=u.g / u.cm**2, ) shower_primary_id = Field( np.int16(np.iinfo(np.int16).max), "Simulated shower primary ID 0 (gamma), 1(e-)," "2(mu-), 100*A+Z for nucleons and nuclei," "negative for antimatter.", )
[docs] class SimulatedCameraContainer(Container): """ True images and parameters derived from them, analogous to the `DL1CameraContainer` but for simulated data. """ default_prefix = "" true_image_sum = Field( np.int32(-1), "Total number of detected Cherenkov photons in the camera" ) true_image = Field( None, "Numpy array of camera image in PE as simulated before noise has been added. " "Shape: (n_pixel)", dtype=np.int32, ndim=1, ) true_parameters = Field( None, description="Parameters derived from the true_image", type=ImageParametersContainer, ) impact = Field( default_factory=TelescopeImpactParameterContainer, description="true impact parameter", )
class SimulatedEventContainer(Container): shower = Field( default_factory=SimulatedShowerContainer, description="True event information", ) tel = Field(default_factory=partial(Map, SimulatedCameraContainer))
[docs] class SimulationConfigContainer(Container): """ Configuration parameters of the simulation """ run_number = Field(np.int32(-1), description="Original sim_telarray run number") corsika_version = Field(nan, description="CORSIKA version * 1000") simtel_version = Field(nan, description="sim_telarray version * 1000") energy_range_min = Field( nan * u.TeV, description="Lower limit of energy range of primary particle", unit=u.TeV, ) energy_range_max = Field( nan * u.TeV, description="Upper limit of energy range of primary particle", unit=u.TeV, ) prod_site_B_total = Field( nan * u.uT, description="total geomagnetic field", unit=u.uT ) prod_site_B_declination = Field( nan * u.rad, description="magnetic declination", unit=u.rad ) prod_site_B_inclination = Field( nan * u.rad, description="magnetic inclination", unit=u.rad ) prod_site_alt = Field( nan * u.m, description="height of observation level", unit=u.m ) spectral_index = Field(nan, description="Power-law spectral index of spectrum") shower_prog_start = Field( nan, description="Time when shower simulation started, CORSIKA: only date" ) shower_prog_id = Field(nan, description="CORSIKA=1, ALTAI=2, KASCADE=3, MOCCA=4") detector_prog_start = Field( nan, description="Time when detector simulation started" ) detector_prog_id = Field(nan, description="simtelarray=1") n_showers = Field(nan, description="Number of showers simulated") shower_reuse = Field(nan, description="Numbers of uses of each shower") max_alt = Field(nan * u.rad, description="Maximum shower altitude", unit=u.rad) min_alt = Field(nan * u.rad, description="Minimum shower altitude", unit=u.rad) max_az = Field(nan * u.rad, description="Maximum shower azimuth", unit=u.rad) min_az = Field(nan * u.rad, description="Minimum shower azimuth", unit=u.rad) diffuse = Field(False, description="Diffuse Mode On/Off") max_viewcone_radius = Field( nan * u.deg, description="Maximum viewcone radius", unit=u.deg ) min_viewcone_radius = Field( nan * u.deg, description="Minimum viewcone radius", unit=u.deg ) max_scatter_range = Field(nan * u.m, description="Maximum scatter range", unit=u.m) min_scatter_range = Field(nan * u.m, description="Minimum scatter range", unit=u.m) core_pos_mode = Field( nan, description="Core Position Mode (0=Circular, 1=Rectangular)" ) atmosphere = Field(nan * u.m, description="Atmospheric model number") corsika_iact_options = Field( nan, description="CORSIKA simulation options for IACTs" ) corsika_low_E_model = Field( nan, description="CORSIKA low-energy simulation physics model" ) corsika_high_E_model = Field( nan, "CORSIKA physics model ID for high energies " "(1=VENUS, 2=SIBYLL, 3=QGSJET, 4=DPMJET, 5=NeXus, 6=EPOS) ", ) corsika_bunchsize = Field(nan, description="Number of Cherenkov photons per bunch") corsika_wlen_min = Field( nan * u.m, description="Minimum wavelength of cherenkov light", unit=u.nm ) corsika_wlen_max = Field( nan * u.m, description="Maximum wavelength of cherenkov light", unit=u.nm ) corsika_low_E_detail = Field( nan, description="More details on low E interaction model (version etc.)" ) corsika_high_E_detail = Field( nan, description="More details on high E interaction model (version etc.)" )
[docs] class SimulatedShowerDistribution(Container): """ 2D histogram of simulated number of showers simulated as function of energy and core distance. """ default_prefix = "" obs_id = obs_id_field() hist_id = Field(-1, description="Histogram ID") n_entries = Field(-1, description="Number of entries in the histogram") bins_energy = Field( None, description="array of energy bin lower edges, as in np.histogram", unit=u.TeV, ) bins_core_dist = Field( None, description="array of core-distance bin lower edges, as in np.histogram", unit=u.m, ) histogram = Field( None, description="array of histogram entries, size (n_bins_x, n_bins_y)" )
# Trigger containers class TelescopeTriggerContainer(Container): default_prefix = "" time = Field(NAN_TIME, description="Telescope trigger time") event_type = Field(EventType.UNKNOWN, description="Event type") n_trigger_pixels = Field( -1, description="Number of trigger groups (sectors) listed" ) trigger_pixels = Field(None, description="pixels involved in the camera trigger")
[docs] class TriggerContainer(Container): default_prefix = "" time = Field(NAN_TIME, description="central average time stamp") tels_with_trigger = Field( None, description="List of telescope ids that triggered the array event" ) event_type = Field(EventType.SUBARRAY, description="Event type") tel = Field( default_factory=partial(Map, TelescopeTriggerContainer), description="telescope-wise trigger information", )
# Reconstruction containers
[docs] class ReconstructedGeometryContainer(Container): """ Standard output of algorithms reconstructing shower geometry """ default_prefix = "" alt = Field(nan * u.deg, "reconstructed altitude", unit=u.deg) alt_uncert = Field(nan * u.deg, "reconstructed altitude uncertainty", unit=u.deg) az = Field(nan * u.deg, "reconstructed azimuth", unit=u.deg) az_uncert = Field(nan * u.deg, "reconstructed azimuth uncertainty", unit=u.deg) ang_distance_uncert = Field( nan * u.deg, "uncertainty radius of reconstructed altitude-azimuth position", unit=u.deg, ) core_x = Field( nan * u.m, "reconstructed x coordinate of the core position", unit=u.m ) core_y = Field( nan * u.m, "reconstructed y coordinate of the core position", unit=u.m ) core_uncert_x = Field( nan * u.m, "reconstructed core position uncertainty along ground frame X axis", unit=u.m, ) core_uncert_y = Field( nan * u.m, "reconstructed core position uncertainty along ground frame Y axis", unit=u.m, ) core_tilted_x = Field( nan * u.m, "reconstructed x coordinate of the core position", unit=u.m ) core_tilted_y = Field( nan * u.m, "reconstructed y coordinate of the core position", unit=u.m ) core_tilted_uncert_x = Field( nan * u.m, "reconstructed core position uncertainty along tilted frame X axis", unit=u.m, ) core_tilted_uncert_y = Field( nan * u.m, "reconstructed core position uncertainty along tilted frame Y axis", unit=u.m, ) h_max = Field( nan * u.m, "reconstructed vertical height above sea level of the shower maximum", unit=u.m, ) h_max_uncert = Field(nan * u.m, "uncertainty of h_max", unit=u.m) is_valid = Field( False, ( "Geometry validity flag. True if the shower geometry" "was properly reconstructed by the algorithm" ), ) average_intensity = Field( nan, "average intensity of the intensities used for reconstruction" ) goodness_of_fit = Field(nan, "measure of algorithm success (if fit)") telescopes = Field(None, "Telescopes used if stereo, or None if Mono")
[docs] class ReconstructedEnergyContainer(Container): """ Standard output of algorithms estimating energy """ default_prefix = "" energy = Field(nan * u.TeV, "reconstructed energy", unit=u.TeV) energy_uncert = Field(nan * u.TeV, "reconstructed energy uncertainty", unit=u.TeV) is_valid = Field( False, ( "energy reconstruction validity flag. True if " "the energy was properly reconstructed by the " "algorithm" ), ) goodness_of_fit = Field(nan, "goodness of the algorithm fit") telescopes = Field(None, "Telescopes used if stereo, or None if Mono")
[docs] class ParticleClassificationContainer(Container): """ Standard output of gamma/hadron classification algorithms """ default_prefix = "" prediction = Field( nan, ( " prediction of the classifier, defined between [0,1]" ", where values close to 1 mean that the positive class" " (e.g. gamma in gamma-ray analysis) is more likely" ), ) is_valid = Field(False, "true if classification parameters are valid") goodness_of_fit = Field(nan, "goodness of the algorithm fit") telescopes = Field(None, "Telescopes used if stereo, or None if Mono")
[docs] class DispContainer(Container): """ Standard output of disp reconstruction algorithms for origin reconstruction """ default_prefix = "disp" parameter = Field( nan * u.deg, "reconstructed value for disp (= sign * norm)", unit=u.deg ) sign_score = Field( nan, "Score for how certain the disp sign classification was." " 0 means completely uncertain, 1 means very certain.", )
[docs] class ReconstructedContainer(Container): """Reconstructed shower info from multiple algorithms""" # Note: there is a reason why the hiererchy is # `event.dl2.stereo.geometry[algorithm]` and not # `event.dl2[algorithm].stereo.geometry` and that is because when writing # the data, the former makes it easier to only write information that a # particular reconstructor generates, e.g. only write the geometry in cases # where energy is not yet computed. Some algorithms will compute all three, # but most will compute only fill or two of these sub-Contaiers: geometry = Field( default_factory=partial(Map, ReconstructedGeometryContainer), description="map of algorithm to reconstructed shower parameters", ) energy = Field( default_factory=partial(Map, ReconstructedEnergyContainer), description="map of algorithm to reconstructed energy parameters", ) classification = Field( default_factory=partial(Map, ParticleClassificationContainer), description="map of algorithm to classification parameters", )
class TelescopeReconstructedContainer(ReconstructedContainer): """Telescope-wise reconstructed quantities""" impact = Field( default_factory=partial(Map, TelescopeImpactParameterContainer), description="map of algorithm to impact parameter info", ) disp = Field( default_factory=partial(Map, DispContainer), description="map of algorithm to reconstructed disp parameters", )
[docs] class DL2Container(Container): """Reconstructed Shower information for a given reconstruction algorithm, including optionally both per-telescope mono reconstruction and per-shower stereo reconstructions """ tel = Field( default_factory=partial(Map, TelescopeReconstructedContainer), description="map of tel_id to single-telescope reconstruction (DL2a)", ) stereo = Field( default_factory=ReconstructedContainer, description="Stereo Shower reconstruction results", )
# Calibration containers # Pointing containers
[docs] class TelescopePointingContainer(Container): """ Container holding pointing information for a single telescope after all necessary correction and calibration steps. These values should be used in the reconstruction to transform between camera and sky coordinates. """ default_prefix = "telescope_pointing" azimuth = Field(nan * u.rad, "Azimuth, measured N->E", unit=u.rad) altitude = Field(nan * u.rad, "Altitude", unit=u.rad)
[docs] class ArrayPointingContainer(Container): """ Container holding pointing information for the combined array after all necessary correction and calibration steps. """ array_azimuth = Field(nan * u.rad, "Array pointing azimuth", unit=u.rad) array_altitude = Field(nan * u.rad, "Array pointing altitude", unit=u.rad) array_ra = Field(nan * u.rad, "Array pointing right ascension", unit=u.rad) array_dec = Field(nan * u.rad, "Array pointing declination", unit=u.rad)
# Camera containers
[docs] class CameraCalibrationContainer(Container): """ Storage of camera calibration coefficients for a given time. """ time = Field( NAN_TIME, "validity time of the camera calibration coefficients.", ) pedestal_offset = Field( None, "Residual mean pedestal of the waveforms for each pixel." " This value is subtracted from the waveforms of each pixel before" " the pulse extraction. Shape: (n_channels, n_pixels)", ) factor = Field( None, "Multiplicative coefficients for the calibration of extracted charge" " into physical units (e.g. photoelectrons or photons) for each pixel." " The coefficients include the relative correction between pixels to" " achieve a uniform charge response. Shape: (n_channels, n_pixels)", ) time_shift = Field( None, "Additive coefficients for the timing correction before charge extraction" " for each pixel. Shape: (n_channels, n_pixels)", ) outlier_mask = Field( None, "Boolean mask indicating which pixels are considered outliers." " Shape: (n_channels, n_pixels)", ) is_valid = Field( False, ( "True if the coefficients are valid, False if they are not valid or " "if a high fraction of faulty pixels exceeding the pre-defined threshold " "is detected during the time period." ), )
[docs] class StatisticsContainer(Container): """Store descriptive statistics of a pixel-wise quantity for each channel""" mean = Field( None, "mean of a pixel-wise quantity for each channel" "Type: float; Shape: (n_channels, n_pixel)", ) median = Field( None, "median of a pixel-wise quantity for each channel" "Type: float; Shape: (n_channels, n_pixel)", ) std = Field( None, "standard deviation of a pixel-wise quantity for each channel" "Type: float; Shape: (n_channels, n_pixel)", ) n_events = Field(-1, "number of events used for the extraction of the statistics") outlier_mask = Field( None, "Boolean mask indicating which pixels are considered outliers." " Shape: (n_channels, n_pixels)", ) is_valid = Field( False, ( "True if the pixel statistics are valid, False if they are not valid or " "if a high fraction of faulty pixels exceeding the pre-defined threshold " "is detected across the chunk of images." ), )
[docs] class ChunkStatisticsContainer(StatisticsContainer): """Store descriptive statistics of a chunk of images""" time_start = Field(NAN_TIME, "high resolution start time of the chunk") time_end = Field(NAN_TIME, "high resolution end time of the chunk") event_id_start = Field(None, "event id of the first event of the chunk") event_id_end = Field(None, "event id of the last event of the chunk")
[docs] class PixelStatisticsContainer(Container): """ Container for pixel statistics from flat-field and sky pedestal events """ flatfield_image = Field( default_factory=StatisticsContainer, description="Statistical description from the image charge of flat-field event distributions", ) flatfield_peak_time = Field( default_factory=StatisticsContainer, description="Statistical description from the peak arrival time of flat-field event distributions", ) sky_pedestal_image = Field( default_factory=StatisticsContainer, description="Statistical description from the image charge of sky pedestal event distributions", )
[docs] class CameraMonitoringContainer(Container): """ Container for camera monitoring data """ pixel_statistics = Field( default_factory=PixelStatisticsContainer, description="Pixel statistics from the flat-field and sky pedestal events", ) coefficients = Field( default_factory=CameraCalibrationContainer, description="Camera calibration coefficients calculated from the monitoring data", )
[docs] class TelescopeMonitoringContainer(Container): """ Root container for telescope monitoring data (MON) """ # create the camera container camera = Field( default_factory=CameraMonitoringContainer, description="Container for monitoring data for camera", ) pointing = Field( default_factory=TelescopePointingContainer, description="Telescope pointing positions", )
[docs] class MonitoringContainer(Container): """ Root container for monitoring data (MON) """ # create the camera container tel = Field( default_factory=partial(Map, TelescopeMonitoringContainer), description="map of tel_id to TelescopeMonitoringContainer", ) pointing = Field( default_factory=ArrayPointingContainer, description="Array pointing positions", )
# Muon containers
[docs] class MuonRingContainer(Container): """Container for the result of a ring fit in telescope frame""" center_fov_lon = Field( nan * u.deg, "center (fov_lon) of the fitted muon ring", unit=u.deg ) center_fov_lat = Field( nan * u.deg, "center (fov_lat) of the fitted muon ring", unit=u.deg ) radius = Field(nan * u.deg, "radius of the fitted muon ring", unit=u.deg) center_fov_lon_err = Field( nan * u.deg, "center (fov_lon) of the fitted muon ring error", unit=u.deg ) center_fov_lat_err = Field( nan * u.deg, "center (fov_lat) of the fitted muon ring error", unit=u.deg ) radius_err = Field(nan * u.deg, "radius of the fitted muon ring error", unit=u.deg) center_phi = Field( nan * u.deg, "Angle of ring center within camera plane", unit=u.deg ) center_distance = Field( nan * u.deg, "Distance from the ring center to camera center", unit=u.deg )
[docs] class MuonEfficiencyContainer(Container): width = Field(nan * u.deg, "width of the muon ring in degrees") impact = Field(nan * u.m, "distance of muon impact position from center of mirror") impact_x = Field(nan * u.m, "impact parameter x position") impact_y = Field(nan * u.m, "impact parameter y position") optical_efficiency = Field(nan, "optical efficiency muon") is_valid = Field(False, "True if the fit converged successfully") parameters_at_limit = Field( False, "True if any bounded parameter was fitted close to a bound" ) likelihood_value = Field(nan, "cost function value at the minimum")
[docs] class MuonParametersContainer(Container): containment = Field(nan, "containment of the ring inside the camera") completeness = Field( nan, "Complenetess of the muon ring" ", estimated by dividing the ring into segments" " and counting segments above a threshold", ) intensity_ratio = Field(nan, "Intensity ratio of pixels in the ring to all pixels") mean_squared_error = Field( nan * u.deg**2, "MSE of the deviation of all pixels after cleaning from the ring fit", ) ring_intensity = Field( nan, "Sum of the pixel charges inside the integration area around a ring" ) intensity_outside_ring = Field(nan, "Sum of the pixel charges outside the ring") n_pixels_in_ring = Field( -1, "Number of pixels inside the ring integration area that passed the cleaning", ) mean_intensity_outside_ring = Field( nan, "Mean intensity of pixels inside the region limited by ring integration width and outer ring width.", ) radial_std_dev = Field( nan * u.deg, "Standard deviation of the radial light distribution.", ) skewness = Field(nan, "Skewness of the light distribution along the ring radius.") excess_kurtosis = Field( nan, "Excess kurtosis of the light distribution along the ring radius." )
[docs] class MuonTelescopeContainer(Container): """ Container for muon analysis """ ring = Field(default_factory=MuonRingContainer, description="muon ring fit") parameters = Field( default_factory=MuonParametersContainer, description="muon parameters" ) efficiency = Field( default_factory=MuonEfficiencyContainer, description="muon efficiency" )
[docs] class MuonContainer(Container): """Root container for muon parameters""" tel = Field( default_factory=partial(Map, MuonTelescopeContainer), description="map of tel_id to MuonTelescopeContainer", )
# Top-level array event container
[docs] class ArrayEventContainer(Container): """Top-level container for all event information""" index = Field( default_factory=EventIndexContainer, description="event indexing information" ) r0 = Field(default_factory=R0Container, description="Raw Data") r1 = Field(default_factory=R1Container, description="R1 Calibrated Data") dl0 = Field( default_factory=DL0Container, description="DL0 Data Volume Reduced Data" ) dl1 = Field(default_factory=DL1Container, description="DL1 Calibrated image") dl2 = Field(default_factory=DL2Container, description="DL2 reconstruction info") simulation = Field( None, description="Simulated Event Information", type=SimulatedEventContainer ) trigger = Field( default_factory=TriggerContainer, description="central trigger information" ) count = Field(0, description="number of events processed") monitoring = Field( default_factory=MonitoringContainer, description="container for event-wise monitoring data (MON)", ) muon = Field( default_factory=MuonContainer, description="Container for muon analysis results" )
[docs] class SchedulingBlockContainer(Container): """Stores information about the scheduling block. This is a simplified version of the SB model, only storing what is necessary for analysis. From :cite:p:`cta-sb-ob-data-model`. """ default_prefix = "" sb_id = Field(UNKNOWN_ID, "Scheduling block ID", type=np.uint64) sb_type = Field( SchedulingBlockType.UNKNOWN, description="Type of scheduling block", type=SchedulingBlockType, ) producer_id = Field( "unknown", "Origin of the sb_id, i.e. name of the telescope site or 'simulation'", type=str, ) observing_mode = Field( ObservingMode.UNKNOWN, "Defines how observations within the Scheduling Block are distributed in space", type=ObservingMode, ) pointing_mode = Field( PointingMode.UNKNOWN, "Defines how the telescope drives move", type=PointingMode )
[docs] class ObservationBlockContainer(Container): """Stores information about the observation""" default_prefix = "" obs_id = obs_id_field() sb_id = Field(UNKNOWN_ID, "ID of the parent SchedulingBlock", type=np.uint64) producer_id = Field( "unknown", "Origin of the obs_id, i.e. name of the telescope site or 'simulation'", type=str, ) state = Field( ObservationBlockState.UNKNOWN, "State of this OB", type=ObservationBlockState ) subarray_pointing_lat = Field( nan * u.deg, "latitude of the nominal center coordinate of this observation", unit=u.deg, ) subarray_pointing_lon = Field( nan * u.deg, "longitude of the nominal center coordinate of this observation", unit=u.deg, ) subarray_pointing_frame = Field( CoordinateFrameType.UNKNOWN, ( "Frame in which the subarray_target is non-moving. If the frame is ALTAZ, " "the meaning of (lon,lat) is (azimuth, altitude) while for ICRS it is " "(right-ascension, declination)" ), type=CoordinateFrameType, ) scheduled_duration = Field( nan * u.min, "expected duration from scheduler", unit=u.min ) scheduled_start_time = Field(NAN_TIME, "expected start time from scheduler") actual_start_time = Field(NAN_TIME, "true start time") actual_duration = Field(nan * u.min, "true duration", unit=u.min)