"""
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)