Skip to content

Commit 2c82d49

Browse files
kosackmaxnoe
andauthored
ObsConfig data model (#1973)
* implement a ObsConfig data model * define PointingMode and add UNKNOWNs to some Enums * use nan instead of None for quantities * added stub for EventSource.observation_config * added obs and scheduling block attributes * write SB and OB info * read back sb and ob * added simulation_config to base EventSource * fix types, test, and docs * include observation config in merge tool * made obs_ids return observation_blocks.keys() * add linking sb_id to ObservationBlock * better phrasing of PointingMode comment * add basic check for existence of merged ob/sb * better OB/SB for ToyEventSource * fix other dummy event source * fix spelling * fixed references * add missing docstrings * moved refs to bibliography Co-authored-by: Maximilian Nöthe <maximilian.noethe@tu-dortmund.de>
1 parent ba821d2 commit 2c82d49

File tree

11 files changed

+355
-43
lines changed

11 files changed

+355
-43
lines changed

ctapipe/containers.py

Lines changed: 147 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,20 +57,85 @@
5757
"StatisticsContainer",
5858
"IntensityStatisticsContainer",
5959
"PeakTimeStatisticsContainer",
60+
"SchedulingBlockContainer",
61+
"ObservationBlockContainer",
62+
"ObservingMode",
63+
"ObservationBlockState",
6064
]
6165

6266

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

70+
#: Used for unsigned integer obs_id or sb_id default values:
71+
UNKNOWN_ID = np.uint64(np.iinfo(np.uint64).max)
6672

67-
class EventType(enum.Enum):
68-
"""Enum of EventTypes as defined in the CTA Data Model
6973

70-
These numbers come from the document *CTA R1/Event Data Model Specification*
71-
version 1 revision C. They may be updated in future revisions
74+
class SchedulingBlockType(enum.Enum):
75+
"""
76+
Types of Scheduling Block
77+
"""
78+
79+
UNKNOWN = -1
80+
OBSERVATION = 0
81+
CALIBRATION = 1
82+
ENGINEERING = 2
83+
84+
85+
class ObservationBlockState(enum.Enum):
86+
"""Observation Block States. Part of the Observation Configuration data
87+
model.
88+
"""
89+
90+
UNKNOWN = -1
91+
FAILED = 0
92+
COMPLETED_SUCCEDED = 1
93+
COMPLETED_CANCELED = 2
94+
COMPLETED_TRUNCATED = 3
95+
ARCHIVED = 4
96+
97+
98+
class ObservingMode(enum.Enum):
99+
"""How a scheduling block is observed. Part of the Observation Configuration
100+
data model.
101+
102+
"""
103+
104+
UNKNOWN = -1
105+
WOBBLE = 0
106+
ON_OFF = 1
107+
GRID = 2
108+
CUSTOM = 3
109+
110+
111+
class PointingMode(enum.Enum):
112+
"""Describes how the telescopes move. Part of the Observation Configuration
113+
data model.
114+
115+
"""
116+
117+
UNKNOWN = -1
118+
#: drives track a point that moves with the sky
119+
TRACK = 0
120+
#: drives stay fixed at an alt/az point while the sky drifts by
121+
DRIFT = 1
122+
123+
124+
class CoordinateFrameType(enum.Enum):
125+
"""types of coordinate frames used in ObservationBlockContainers. Part of
126+
the Observation Configuration data model.
127+
72128
"""
73129

130+
UNKNOWN = -1
131+
ALTAZ = 0
132+
ICRS = 1
133+
GALACTIC = 2
134+
135+
136+
class EventType(enum.Enum):
137+
"""Enum of EventTypes as defined in the CTA Data Model [cta_r1event]_"""
138+
74139
# calibrations are 0-15
75140
FLATFIELD = 0
76141
SINGLE_PE = 1
@@ -79,14 +144,14 @@ class EventType(enum.Enum):
79144
ELECTRONIC_PEDESTAL = 4
80145
OTHER_CALIBRATION = 15
81146

82-
# For mono-telescope triggers (not used in MC)
147+
#: For mono-telescope triggers (not used in MC)
83148
MUON = 16
84149
HARDWARE_STEREO = 17
85150

86-
# ACADA (DAQ) software trigger
151+
#: ACADA (DAQ) software trigger
87152
DAQ = 24
88153

89-
# Standard Physics stereo trigger
154+
#: Standard Physics stereo trigger
90155
SUBARRAY = 32
91156

92157
UNKNOWN = 255
@@ -1143,3 +1208,78 @@ class ArrayEventContainer(Container):
11431208
default_factory=MonitoringContainer,
11441209
description="container for event-wise monitoring data (MON)",
11451210
)
1211+
1212+
1213+
class SchedulingBlockContainer(Container):
1214+
"""Stores information about the scheduling block. This is a simplified
1215+
version of the SB model, only storing what is necessary for analysis. From
1216+
[cta_sb_ob]_
1217+
1218+
"""
1219+
1220+
default_prefix = ""
1221+
sb_id = Field(UNKNOWN_ID, "Scheduling block ID", type=np.uint64)
1222+
sb_type = Field(
1223+
SchedulingBlockType.UNKNOWN,
1224+
description="Type of scheduling block",
1225+
type=SchedulingBlockType,
1226+
)
1227+
producer_id = Field(
1228+
"unknown",
1229+
"Origin of the sb_id, i.e. name of the telescope site or 'simulation'",
1230+
type=str,
1231+
)
1232+
observing_mode = Field(
1233+
ObservingMode.UNKNOWN,
1234+
"Defines how observations within the Scheduling Block are distributed in space",
1235+
type=ObservingMode,
1236+
)
1237+
pointing_mode = Field(
1238+
PointingMode.UNKNOWN, "Defines how the telescope drives move", type=PointingMode
1239+
)
1240+
1241+
1242+
class ObservationBlockContainer(Container):
1243+
"""Stores information about the observation"""
1244+
1245+
default_prefix = ""
1246+
obs_id = Field(UNKNOWN_ID, "Observation Block ID", type=np.uint64)
1247+
sb_id = Field(UNKNOWN_ID, "ID of the parent SchedulingBlock", type=np.uint64)
1248+
producer_id = Field(
1249+
"unknown",
1250+
"Origin of the obs_id, i.e. name of the telescope site or 'simulation'",
1251+
type=str,
1252+
)
1253+
1254+
state = Field(
1255+
ObservationBlockState.UNKNOWN, "State of this OB", type=ObservationBlockState
1256+
)
1257+
1258+
subarray_pointing_lat = Field(
1259+
nan * u.deg,
1260+
"latitude of the nominal center coordinate of this observation",
1261+
unit=u.deg,
1262+
)
1263+
1264+
subarray_pointing_lon = Field(
1265+
nan * u.deg,
1266+
"longitude of the nominal center coordinate of this observation",
1267+
unit=u.deg,
1268+
)
1269+
1270+
subarray_pointing_frame = Field(
1271+
CoordinateFrameType.UNKNOWN,
1272+
(
1273+
"Frame in which the subarray_target is non-moving. If the frame is ALTAZ, "
1274+
"the meaning of (lon,lat) is (azimuth, altitude) while for ICRS it is "
1275+
"(right-ascension, declination)"
1276+
),
1277+
type=CoordinateFrameType,
1278+
)
1279+
1280+
scheduled_duration = Field(
1281+
nan * u.min, "expected duration from scheduler", unit=u.min
1282+
)
1283+
scheduled_start_time = Field(NAN_TIME, "expected start time from scheduler")
1284+
actual_start_time = Field(NAN_TIME, "true start time")
1285+
actual_duration = Field(nan * u.min, "true duration", unit=u.min)

ctapipe/io/datawriter.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def _get_tel_index(event, tel_id):
4040
)
4141

4242

43-
# define the version of the DL1 data model written here. This should be updated
43+
# define the version of the data model written here. This should be updated
4444
# when necessary:
4545
# - increase the major number if there is a breaking change to the model
4646
# (meaning readers need to update scripts)
@@ -61,6 +61,7 @@ def _get_tel_index(event, tel_id):
6161
- The reference_location (EarthLocation origin of the telescope coordinates)
6262
is now included in SubarrayDescription
6363
- Only unique optics are stored in the optics table
64+
- include observation configuration
6465
- v3.0.0: reconstructed core uncertainties splitted in their X-Y components
6566
- v2.2.0: added R0 and R1 outputs
6667
- v2.1.0: hillas and timing parameters are per default saved in telescope frame (degree) as opposed to camera frame (m)
@@ -137,7 +138,7 @@ def write_reference_metadata_headers(
137138

138139
class DataWriter(Component):
139140
"""
140-
Serialize a sequence of events into a HDF5 DL1 file, in the correct format
141+
Serialize a sequence of events into a HDF5 file, in the correct format
141142
142143
Examples
143144
--------
@@ -268,6 +269,7 @@ def __init__(self, event_source: EventSource, config=None, parent=None, **kwargs
268269

269270
def _setup_outputfile(self):
270271
self._subarray.to_hdf(self._writer.h5file)
272+
self._write_scheduling_and_observation_blocks()
271273
if self._is_simulation:
272274
self._write_simulation_configuration()
273275

@@ -321,7 +323,7 @@ def __call__(self, event: ArrayEventContainer):
321323

322324
def finish(self):
323325
"""called after all events are done"""
324-
self.log.info("Finishing DL1 output")
326+
self.log.info("Finishing output")
325327
if not self._at_least_one_event:
326328
self.log.warning("No events have been written to the output file")
327329
if self._writer:
@@ -496,6 +498,21 @@ def _write_subarray_pointing(self, event: ArrayEventContainer, writer: TableWrit
496498
writer.write("dl1/monitoring/subarray/pointing", [event.trigger, pnt])
497499
self._last_pointing = current_pointing
498500

501+
def _write_scheduling_and_observation_blocks(self):
502+
"""write out SB and OB info"""
503+
504+
self.log.debug(
505+
"writing %d sbs and %d obs",
506+
len(self.event_source.scheduling_blocks.values()),
507+
len(self.event_source.observation_blocks.values()),
508+
)
509+
510+
for sb in self.event_source.scheduling_blocks.values():
511+
self._writer.write("configuration/observation/scheduling_block", sb)
512+
513+
for ob in self.event_source.observation_blocks.values():
514+
self._writer.write("configuration/observation/observation_block", ob)
515+
499516
def _write_simulation_configuration(self):
500517
"""
501518
Write the simulation headers to a single row of a table. Later

ctapipe/io/eventsource.py

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,16 @@
33
"""
44
import warnings
55
from abc import abstractmethod
6-
from typing import Generator, List, Tuple
6+
from typing import Dict, Generator, List, Tuple
77

88
from traitlets.config.loader import LazyConfigValue
99

10-
from ..containers import ArrayEventContainer
10+
from ..containers import (
11+
ArrayEventContainer,
12+
ObservationBlockContainer,
13+
SchedulingBlockContainer,
14+
SimulationConfigContainer,
15+
)
1116
from ..core import Provenance, ToolConfigurationError
1217
from ..core.component import Component, find_config_in_hierarchy, non_abstract_children
1318
from ..core.traits import CInt, Int, Path, Set, TraitError, Undefined
@@ -205,11 +210,38 @@ def subarray(self) -> SubarrayDescription:
205210
206211
"""
207212

213+
@property
214+
def simulation_config(self) -> Dict[int, SimulationConfigContainer]:
215+
"""The simulation configurations of all observations provided by the
216+
EventSource, or None if the source does not provide simulated data
217+
218+
Returns
219+
-------
220+
Dict[int,ctapipe.containers.SimulationConfigContainer] | None
221+
"""
222+
return None
223+
224+
@property
225+
@abstractmethod
226+
def observation_blocks(self) -> Dict[int, ObservationBlockContainer]:
227+
"""
228+
Obtain the ObservationConfigurations from the EventSource, indexed by obs_id
229+
"""
230+
pass
231+
232+
@property
233+
@abstractmethod
234+
def scheduling_blocks(self) -> Dict[int, SchedulingBlockContainer]:
235+
"""
236+
Obtain the ObservationConfigurations from the EventSource, indexed by obs_id
237+
"""
238+
pass
239+
208240
@property
209241
@abstractmethod
210242
def is_simulation(self) -> bool:
211243
"""
212-
Weither the currently opened file is simulated
244+
Whether the currently opened file is simulated
213245
214246
Returns
215247
-------
@@ -240,7 +272,6 @@ def has_any_datalevel(self, datalevels) -> bool:
240272
return any(dl in self.datalevels for dl in datalevels)
241273

242274
@property
243-
@abstractmethod
244275
def obs_ids(self) -> List[int]:
245276
"""
246277
The observation ids of the runs located in the file
@@ -250,6 +281,7 @@ def obs_ids(self) -> List[int]:
250281
-------
251282
list[int]
252283
"""
284+
return list(self.observation_blocks.keys())
253285

254286
@abstractmethod
255287
def _generator(self) -> Generator[ArrayEventContainer, None, None]:

0 commit comments

Comments
 (0)