Skip to content
Open
152 changes: 150 additions & 2 deletions src/ctapipe/containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@
"TimingParametersContainer",
"TriggerContainer",
"TelescopePointingContainer",
"TelescopeStructurePointingContainer",
"TelescopeStructureDisplacementContainer",
"CameraDisplacementContainer",
"TelescopeSkyPointingContainer",
"TelescopePointingCorrectionContainer",
"ArrayPointingContainer",
"StatisticsContainer",
"ChunkContainer",
Expand All @@ -71,6 +76,7 @@
"ObservationBlockContainer",
"ObservingMode",
"ObservationBlockState",
"WeatherMonitoringContainer",
]


Expand Down Expand Up @@ -1187,8 +1193,108 @@ class DL2Container(Container):
)


# Calibration containers
# Pointing containers
# Calibration and Monitoring containers


# Pointing-related containers
class TelescopeStructurePointingContainer(Container):
"""
Pointing of the telescope structure in local coordinates (Alt/Az)
as measured by the telescope hardware (e.g. drive encoders).
"""

default_prefix = "structure_pointing"
azimuth = Field(
nan * u.rad, "Raw azimuth measured by structure encoders", unit=u.rad
)
altitude = Field(
nan * u.rad, "Raw altitude measured by structure encoders", unit=u.rad
)


class TelescopeStructureDisplacementContainer(Container):
"""
Difference between ideal and real direction of the telescope axis for
a given elevation in local coordinates (e.g. structural bending model).
"""

default_prefix = "structure_displacement"
delta_azimuth = Field(
nan * u.rad,
"Azimuth offset due to structure bending/deformation",
unit=u.rad,
)
delta_altitude = Field(
nan * u.rad,
"Altitude offset due to structure bending/deformation",
unit=u.rad,
)


class CameraDisplacementContainer(Container):
"""
Displacement and tilt of the camera w.r.t. its nominal position in the
telescope structure.

@kosack kosack Aug 17, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhere in the docs you should be specific about the order of applying these transforms: rotate then translate is not the same as translate then rotate.

Also maybe name them like that? E.g. translation_x, rotation_z to be more standard? (tilt could mean a rotation or a shear, so it's not as clear)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which brings up one more question: do we need to worry about camera or optics shear? Or is it negligible?

"""

default_prefix = "camera_displacement"
delta_x = Field(
nan * u.m, "Camera displacement along X axis (horizontal)", unit=u.m
)
delta_y = Field(nan * u.m, "Camera displacement along Y axis (vertical)", unit=u.m)
delta_z = Field(
nan * u.m, "Camera displacement along optical Z axis (focal offset)", unit=u.m
)
tilt_x = Field(nan * u.rad, "Camera tilt angle around X axis (pitch)", unit=u.rad)
tilt_y = Field(nan * u.rad, "Camera tilt angle around Y axis (yaw)", unit=u.rad)
tilt_z = Field(
nan * u.rad, "Camera rotation angle around optical Z axis (roll)", unit=u.rad
)


class AuxiliaryPointingCorrectionContainer(Container):
"""
Pointing correction in celestial coordinates derived using auxiliary hardware, such as e.g. StarGuider camera.
"""

default_prefix = "aux_pointing_correction"
delta_ra = Field(
nan * u.rad, "Residual Right Ascension offset correction", unit=u.rad
)
delta_dec = Field(nan * u.rad, "Residual Declination offset correction", unit=u.rad)


class TelescopeSkyPointingContainer(Container):
"""
Fully corrected pointing mapping the camera center to
celestial coordinates (ICRS).
"""

default_prefix = "sky_pointing"
ra = Field(nan * u.rad, "Right Ascension of camera center (ICRS)", unit=u.rad)
Comment thread
mexanick marked this conversation as resolved.
Outdated
dec = Field(nan * u.rad, "Declination of camera center (ICRS)", unit=u.rad)


class TelescopePointingCorrectionContainer(Container):
"""
Residual sky-pointing corrections in celestial coordinates (RA/Dec/Rotation).
Comment thread
maxnoe marked this conversation as resolved.
Outdated

This container holds abstract celestial offsets regardless of how they were
derived (e.g. analytical StarTracker, auxiliary hardware devices,
or a combination/fusion model).
"""

default_prefix = "pointing_correction"
delta_ra = Field(
nan * u.rad, "Residual Right Ascension offset correction", unit=u.rad
)
delta_dec = Field(nan * u.rad, "Residual Declination offset correction", unit=u.rad)

# Optional uncertainty tracking
sigma_ra = Field(nan * u.rad, "Uncertainty on RA offset correction", unit=u.rad)
sigma_dec = Field(nan * u.rad, "Uncertainty on Dec offset correction", unit=u.rad)


class TelescopePointingContainer(Container):
"""
Container holding pointing information for a single telescope
Expand All @@ -1214,6 +1320,28 @@ class ArrayPointingContainer(Container):
array_dec = Field(nan * u.rad, "Array pointing declination", unit=u.rad)


# Environmental Monitoring containers
class WeatherMonitoringContainer(Container):
"""
Environmental parameters at the observatory site.

Note: Astronomical refraction derived from weather measurements SHALL ONLY
be used when fitting star positions during offline optical pointing calibration.
It SHALL NOT be applied during Cherenkov atmospheric shower direction reconstruction.
"""

default_prefix = "weather"
temperature = Field(
nan * u.K, "Ambient air temperature at observatory site", unit=u.K
Comment thread
maxnoe marked this conversation as resolved.
)
pressure = Field(
nan * u.hPa, "Atmospheric pressure at observatory site", unit=u.hPa
)
relative_humidity = Field(
nan, "Relative humidity fraction (0.0 to 1.0)", unit=u.dimensionless_unscaled
)


# Camera containers
class CameraCalibrationContainer(Container):
"""
Expand Down Expand Up @@ -1347,6 +1475,22 @@ class TelescopeMonitoringContainer(Container):
default_factory=CameraMonitoringContainer,
description="Container for monitoring data for camera",
)
structure_pointing = Field(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following our discussion on slack on when / how lower-level monitoring is combined, I don't really see the need to add these containers to the event structure.

Is there a use case remaining?

I think we agreed that the more efficient approach is to compute the final correction on the monitoring time scales based on tabular data and interpolate that to the event.

In that scenario, we would not need event-wise data structures for these lower-level parts.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we still want them for the star tracking. However, in this case I should leave them as standalone containers, but remove from being linked to the event, so it is only filled on request and not for every event processed with ctapipe-process tool, right?

In this case, I will leave TelescopePointing and CameraDisplacement containers here (I believe there's no easy way around for Camera Displacement, as I believe it would be only used to modify image, so we just take it as is and interpolate to the event time).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean as output containers from the star tracking which would work on bunches of events? Or as intermediate input per event?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as intermediate event-wise input to star tracker. The output of star tracker would be a grid of corrections (common grid with the structure monitoring).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would the star-tracker not use the pointing filled from Structure + BendingModel into the telescope poiinting container?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will use the pointing from Structure + BendingModel as the seed, but it will also use CDM for a given event and weather values for the given event.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, weather is needed and should be added here.

But for the pointing, I would think that you configure the DL0MonitoringSource to read Structure, BendingModel and CDM files and use that to fill the already existing pointing container.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it will be done like that. However, I still need to define the TelescopeStructurePointingContainer, TelescopeStructureDisplacementContainer, etc for conversion, to abstract telescope-specific implementations. They, however, would not be added to the event monitoring structure.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand for what you need the containers then

default_factory=TelescopeStructurePointingContainer,
Comment thread
maxnoe marked this conversation as resolved.
Outdated
description="Pointing of the telescope structure in local coordinates (RAW).",
)
structure_displacement = Field(
default_factory=TelescopeStructureDisplacementContainer,
description="Difference between ideal and real direction of the telescope axis in local coordinates.",
)
camera_displacement = Field(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How are dual-mirror telescopes supported by this? They need camera, and primary mirror corrections, I guess?

default_factory=CameraDisplacementContainer,
description="Displacement and tilt of the camera w.r.t. its nominal position in the telescope structure.",
)
aux_pointing_correction = Field(
default_factory=AuxiliaryPointingCorrectionContainer,
description="Pointing correction in celestial coordinates derived using auxiliary hardware.",
)
pointing = Field(
default_factory=TelescopePointingContainer,
description="Telescope pointing positions",
Expand All @@ -1367,6 +1511,10 @@ class MonitoringContainer(Container):
default_factory=ArrayPointingContainer,
description="Array pointing positions",
)
weather = Field(
default_factory=WeatherMonitoringContainer,
description="Weather monitoring data",
)


# Muon containers
Expand Down
Loading