Skip to content

Commit 8e683c6

Browse files
authored
Merge pull request #57 from cta-observatory/ctapipe_v0.17
Adapt to ctapipe v0.17
2 parents 77ca709 + 9742299 commit 8e683c6

File tree

10 files changed

+158
-91
lines changed

10 files changed

+158
-91
lines changed

.github/workflows/ci.yml

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,33 +12,39 @@ jobs:
1212
runs-on: ubuntu-latest
1313
strategy:
1414
matrix:
15-
python-version: [3.7]
16-
ctapipe-version: [v0.12.0]
15+
python-version: ["3.8", "3.9", "3.10"]
16+
ctapipe-version: ["v0.17.0",]
17+
18+
defaults:
19+
run:
20+
shell: bash -exl {0}
1721

1822
steps:
19-
- uses: actions/checkout@v2
23+
- uses: actions/checkout@v3
2024
with:
2125
fetch-depth: 0
2226

23-
- name: Set up Python ${{ matrix.python-version }}
24-
uses: actions/setup-python@v2
25-
with:
27+
- name: Set python version
28+
env:
2629
python-version: ${{ matrix.python-version }}
30+
run: |
31+
sed -i -e "s/- python=.*/- python=$PYTHON_VERSION/g" environment.yml
2732
28-
- name: Install dependencies
33+
- name: Create and activate env
34+
uses: conda-incubator/setup-miniconda@v2
35+
with:
36+
mamba-version: "*"
37+
use-mamba: true
38+
activate-environment: ci
39+
environment-file: environment.yml
40+
41+
- name: install
2942
env:
30-
PYTHON_VERSION: ${{ matrix.python-version }}
3143
CTAPIPE_VERSION: ${{ matrix.ctapipe-version }}
32-
3344
run: |
34-
. $CONDA/etc/profile.d/conda.sh
35-
conda config --set always_yes yes --set changeps1 no
36-
sed -i -e "s/- python=.*/- python=$PYTHON_VERSION/g" environment.yml
37-
conda env create -n ci -f environment.yml
38-
conda activate ci
45+
pip install -e .
3946
# we install ctapipe using pip to be able to select any commit, e.g. the current master
4047
pip install pytest-cov "git+https://github.com/cta-observatory/ctapipe@$CTAPIPE_VERSION"
41-
pip install -e .
4248
git describe --tags
4349
4450
- name: Download test data
@@ -53,9 +59,7 @@ jobs:
5359
run: |
5460
# github actions starts a new shell for each "step", so we need to
5561
# activate our env again
56-
source $CONDA/etc/profile.d/conda.sh
57-
conda activate ci
58-
coverage run -m pytest -v
59-
coverage xml
62+
python eventsource_subclasses.py | grep MAGICEventSource
63+
pytest --cov=ctapipe_io_magic --cov-report=xml
6064
6165
- uses: codecov/codecov-action@v1

README.md

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,25 @@
11
## *ctapipe* MAGIC event source
22

3-
EventSource plugin for *ctapipe*, needed to read the calibrated data of the MAGIC telescope system. It requires the [*ctapipe*](https://github.com/cta-observatory/ctapipe) (v0.12.0) and [*uproot*](https://github.com/scikit-hep/uproot4) (>=4.1) packages to run.
3+
EventSource plugin for *ctapipe*, needed to read the calibrated data of the MAGIC telescope system. It requires the [*ctapipe*](https://github.com/cta-observatory/ctapipe) (v0.17.0) and [*uproot*](https://github.com/scikit-hep/uproot4) (>=5) packages to run.
44

55
#### Installation
66

7-
Provided that *ctapipe* is already installed, the installation can be done via *pip* (the module is available in PyPI):
7+
If *ctapipe* is already installed, the installation can be done via *pip* (the module is available in PyPI):
88

99
```bash
1010
pip install ctapipe_io_magic
1111
```
1212

1313
Alternatively, you can always clone the repository and install like in the following:
1414

15-
```bash
16-
git clone https://github.com/cta-observatory/ctapipe_io_magic.git
17-
pip install ./ctapipe_io_magic/
18-
```
19-
20-
This installation via *pip* (provided, *pip* is installed) has the advantage to be nicely controlled for belonging to a given conda environment (and to be uninstalled). Alternatively, do
21-
2215
```bash
2316
git clone https://github.com/cta-observatory/ctapipe_io_magic.git
2417
cd ctapipe_io_magic
25-
python setup.py install --user
18+
conda env create -n ctapipe-io_magic -f environment.yml
19+
conda activate ctapipe-io_magic
20+
pip install .
2621
```
2722

28-
In all cases, using *pip* will check if the version of *ctapipe* and *uproot* is compatible with the requested version of *ctapipe_io_magic*.
29-
3023
#### Usage
3124

3225
```python
@@ -118,3 +111,4 @@ Some general information about the simulated data, useful for IRF calculation, a
118111
- v0.4.5: fixed automatic tests, add possibility to choose between effective and nominal focal length
119112
- v0.4.6: add support to read in data taken in mono mode (full for real data, partial for MCs). Fixed bug in recognition of mono/stereo or standard trigger/SumT data (added also for MC)
120113
- v0.4.7: add full support to read in real and MC data taken in mono mode, and with SumT. Added treatment of unsuitable pixels for MC data. Added readout of true XMax value from MC data (usually not available, filled with 0 otherwise)
114+
- v0.5.0: release compatible with ctapipe 0.17. Also, the equivalent focal length is set to the correct value used in MAGIC simulations (i.e. 16.97 meters)

ctapipe_io_magic/__init__.py

Lines changed: 92 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,17 @@
1818

1919
from ctapipe.io import EventSource, DataLevel
2020
from ctapipe.core import Provenance
21-
from ctapipe.core.traits import Bool, CaselessStrEnum
21+
from ctapipe.core.traits import Bool, UseEnum
2222
from ctapipe.coordinates import CameraFrame
2323

2424
from ctapipe.containers import (
2525
EventType,
2626
ArrayEventContainer,
2727
SimulationConfigContainer,
2828
SimulatedEventContainer,
29+
SchedulingBlockContainer,
30+
ObservationBlockContainer,
31+
PointingMode,
2932
)
3033

3134
from ctapipe.instrument import (
@@ -35,6 +38,9 @@
3538
CameraDescription,
3639
CameraGeometry,
3740
CameraReadout,
41+
SizeType,
42+
ReflectorShape,
43+
FocalLengthKind,
3844
)
3945

4046
from .mars_datalevels import MARSDataLevel
@@ -112,7 +118,7 @@ class MAGICEventSource(EventSource):
112118
Data level according to MARS convention
113119
metadata : dict
114120
Dictionary containing metadata
115-
run_numbers : int
121+
run_id : int
116122
Run number of the file
117123
simulation_config : SimulationConfigContainer
118124
Container filled with the information about the simulation
@@ -137,9 +143,9 @@ class MAGICEventSource(EventSource):
137143
help='Use mono events in MC stereo data (needed for mono analysis).'
138144
).tag(config=True)
139145

140-
focal_length_choice = CaselessStrEnum(
141-
['nominal', 'effective'],
142-
default_value='effective',
146+
focal_length_choice = UseEnum(
147+
FocalLengthKind,
148+
default_value=FocalLengthKind.EFFECTIVE,
143149
help='Which focal length to use when constructing the SubarrayDescription.',
144150
).tag(config=True)
145151

@@ -199,7 +205,7 @@ def __init__(self, input_url=None, config=None, parent=None, **kwargs):
199205
self.files_ = [uproot.open(rootf) for rootf in self.file_list]
200206
run_info = self.parse_run_info()
201207

202-
self.run_numbers = run_info[0]
208+
self.run_id = run_info[0][0]
203209
self.is_mc = run_info[1][0]
204210
self.telescope = run_info[2][0]
205211
self.mars_datalevel = run_info[3][0]
@@ -210,7 +216,7 @@ def __init__(self, input_url=None, config=None, parent=None, **kwargs):
210216
self.datalevel = DataLevel.DL0
211217

212218
if self.is_simulation:
213-
self.simulation_config = self.parse_simulation_header()
219+
self._simulation_config = self.parse_simulation_header()
214220

215221
self.is_stereo, self.is_sumt = self.parse_data_info()
216222

@@ -234,6 +240,24 @@ def __init__(self, input_url=None, config=None, parent=None, **kwargs):
234240
# Get the arrival time differences
235241
self.event_time_diffs = self.get_event_time_difference()
236242

243+
pointing_mode = PointingMode.TRACK
244+
245+
self._scheduling_blocks = {
246+
self.run_id: SchedulingBlockContainer(
247+
sb_id=np.uint64(self.run_id),
248+
producer_id=f"MAGIC-{self.telescope}",
249+
pointing_mode=pointing_mode,
250+
)
251+
}
252+
253+
self._observation_blocks = {
254+
self.run_id: ObservationBlockContainer(
255+
obs_id=np.uint64(self.run_id),
256+
sb_id=np.uint64(self.run_id),
257+
producer_id=f"MAGIC-{self.telescope}",
258+
)
259+
}
260+
237261
def __exit__(self, exc_type, exc_val, exc_tb):
238262
"""
239263
Releases resources (e.g. open files).
@@ -620,23 +644,51 @@ def prepare_subarray_info(self):
620644
2: [-34.99, 24.02, 0.00] * u.m
621645
}
622646

623-
if self.focal_length_choice == 'effective':
624-
# Use the effective focal length that the coma aberration is corrected
625-
focal_length = u.Quantity(17*1.0713, u.m)
626-
else:
627-
focal_length = u.Quantity(17, u.m)
647+
equivalent_focal_length = u.Quantity(16.97, u.m)
648+
effective_focal_length = u.Quantity(17*1.0713, u.m)
628649

629650
OPTICS = OpticsDescription(
630-
'MAGIC',
631-
num_mirrors=1,
632-
equivalent_focal_length=focal_length,
651+
name='MAGIC',
652+
size_type=SizeType.LST,
653+
n_mirrors=1,
654+
n_mirror_tiles=964,
655+
reflector_shape=ReflectorShape.PARABOLIC,
656+
equivalent_focal_length=equivalent_focal_length,
657+
effective_focal_length=effective_focal_length,
633658
mirror_area=u.Quantity(239.0, u.m**2),
634-
num_mirror_tiles=964,
635659
)
636660

661+
if self.focal_length_choice is FocalLengthKind.EFFECTIVE:
662+
focal_length = effective_focal_length
663+
elif self.focal_length_choice is FocalLengthKind.EQUIVALENT:
664+
focal_length = equivalent_focal_length
665+
else:
666+
raise ValueError(
667+
f"Invalid focal length choice: {self.focal_length_choice}"
668+
)
669+
637670
# camera info from MAGICCam.camgeom.fits.gz file
638671
camera_geom = load_camera_geometry()
639672

673+
n_pixels = camera_geom.n_pixels
674+
675+
n_samples_array_list = ["MRawRunHeader.fNumSamplesHiGain"]
676+
n_samples_list = []
677+
678+
for rootf in self.files_:
679+
nsample_info = rootf['RunHeaders'].arrays(n_samples_array_list, library="np")
680+
n_samples_file = int(nsample_info['MRawRunHeader.fNumSamplesHiGain'][0])
681+
n_samples_list.append(n_samples_file)
682+
683+
n_samples_list = np.unique(n_samples_list).tolist()
684+
685+
if len(n_samples_list) > 1:
686+
raise ValueError(
687+
"Loaded files contain different number of readout samples. \
688+
Please load files with the same readout configuration.")
689+
690+
n_samples = n_samples_list[0]
691+
640692
pulse_shape_lo_gain = np.array([0., 1., 2., 1., 0.])
641693
pulse_shape_hi_gain = np.array([1., 2., 3., 2., 1.])
642694
pulse_shape = np.vstack((pulse_shape_lo_gain, pulse_shape_hi_gain))
@@ -645,18 +697,21 @@ def prepare_subarray_info(self):
645697
u.GHz
646698
)
647699
camera_readout = CameraReadout(
648-
camera_name='MAGICCam',
700+
name='MAGICCam',
649701
sampling_rate=sampling_speed,
650702
reference_pulse_shape=pulse_shape,
651-
reference_pulse_sample_width=u.Quantity(0.5, u.ns)
703+
reference_pulse_sample_width=u.Quantity(0.5, u.ns),
704+
n_channels=1,
705+
n_pixels=n_pixels,
706+
n_samples=n_samples,
652707
)
653708

654709
camera = CameraDescription('MAGICCam', camera_geom, camera_readout)
655710

656-
camera.geometry.frame = CameraFrame(focal_length=OPTICS.equivalent_focal_length)
711+
camera.geometry.frame = CameraFrame(focal_length=focal_length)
657712

658713
MAGIC_TEL_DESCRIPTION = TelescopeDescription(
659-
name='MAGIC', tel_type='MAGIC', optics=OPTICS, camera=camera
714+
name='MAGIC', optics=OPTICS, camera=camera
660715
)
661716

662717
MAGIC_TEL_DESCRIPTIONS = {1: MAGIC_TEL_DESCRIPTION, 2: MAGIC_TEL_DESCRIPTION}
@@ -731,7 +786,7 @@ def parse_metadata_info(self):
731786

732787
metadata = dict()
733788
metadata["file_list"] = self.file_list
734-
metadata['run_numbers'] = self.run_numbers
789+
metadata['run_numbers'] = self.run_id
735790
metadata['is_simulation'] = self.is_simulation
736791
metadata['telescope'] = self.telescope
737792
metadata['subrun_number'] = []
@@ -818,7 +873,7 @@ def parse_simulation_header(self):
818873

819874
simulation_config = dict()
820875

821-
for run_number, rootf in zip(self.run_numbers, self.files_):
876+
for rootf in self.files_:
822877

823878
run_header_tree = rootf['RunHeaders']
824879
spectral_index = run_header_tree['MMcCorsikaRunHeader.fSlopeSpec'].array(library="np")[0]
@@ -848,13 +903,13 @@ def parse_simulation_header(self):
848903
max_wavelength = run_header_tree['MMcRunHeader_1.fCWaveUpper'].array(library="np")[0]
849904
min_wavelength = run_header_tree['MMcRunHeader_1.fCWaveLower'].array(library="np")[0]
850905

851-
simulation_config[run_number] = SimulationConfigContainer(
906+
simulation_config[self.run_id] = SimulationConfigContainer(
852907
corsika_version=corsika_version,
853908
energy_range_min=u.Quantity(e_low, u.GeV).to(u.TeV),
854909
energy_range_max=u.Quantity(e_high, u.GeV).to(u.TeV),
855910
prod_site_alt=u.Quantity(site_height, u.cm).to(u.m),
856911
spectral_index=spectral_index,
857-
num_showers=n_showers,
912+
n_showers=n_showers,
858913
shower_reuse=1,
859914
# shower_reuse not written in the magic root file, but since the
860915
# sim_events already include shower reuse we artificially set it
@@ -968,14 +1023,26 @@ def subarray(self):
9681023
def is_simulation(self):
9691024
return self.is_mc
9701025

1026+
@property
1027+
def observation_blocks(self):
1028+
return self._observation_blocks
1029+
1030+
@property
1031+
def scheduling_blocks(self):
1032+
return self._scheduling_blocks
1033+
1034+
@property
1035+
def simulation_config(self):
1036+
return self._simulation_config
1037+
9711038
@property
9721039
def datalevels(self):
9731040
return (self.datalevel, )
9741041

9751042
@property
9761043
def obs_ids(self):
9771044
# ToCheck: will this be compatible in the future, e.g. with merged MC files
978-
return self.run_numbers
1045+
return list(self.observation_blocks)
9791046

9801047
def _get_badrmspixel_mask(self, event):
9811048
"""
-98 Bytes
Binary file not shown.

ctapipe_io_magic/tests/test_magic_event_source.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ def test_run_info(dataset):
171171
is_mc = [i[1] for i in run_info][0]
172172
telescope = [i[2] for i in run_info][0]
173173
datalevel = [i[3] for i in run_info][0]
174-
assert run_numbers == source.run_numbers
174+
assert run_numbers == [source.run_id]
175175
assert is_mc == source.is_simulation
176176
assert telescope == source.telescope
177177
assert datalevel == source.mars_datalevel
@@ -239,16 +239,17 @@ def test_geom(dataset):
239239
def test_focal_length_choice(dataset):
240240
from astropy import units as u
241241
from ctapipe_io_magic import MAGICEventSource
242+
from ctapipe.instrument import FocalLengthKind
242243

243-
with MAGICEventSource(input_url=dataset, process_run=False, focal_length_choice='nominal') as source:
244-
assert source.subarray.tel[1].optics.equivalent_focal_length == u.Quantity(17, u.m)
245-
assert source.subarray.tel[2].optics.equivalent_focal_length == u.Quantity(17, u.m)
246-
assert source.subarray.tel[1].camera.geometry.frame.focal_length == u.Quantity(17, u.m)
247-
assert source.subarray.tel[2].camera.geometry.frame.focal_length == u.Quantity(17, u.m)
244+
with MAGICEventSource(input_url=dataset, process_run=False, focal_length_choice=FocalLengthKind.EQUIVALENT) as source:
245+
assert source.subarray.tel[1].optics.equivalent_focal_length == u.Quantity(16.97, u.m)
246+
assert source.subarray.tel[2].optics.equivalent_focal_length == u.Quantity(16.97, u.m)
247+
assert source.subarray.tel[1].camera.geometry.frame.focal_length == u.Quantity(16.97, u.m)
248+
assert source.subarray.tel[2].camera.geometry.frame.focal_length == u.Quantity(16.97, u.m)
248249

249-
with MAGICEventSource(input_url=dataset, process_run=False, focal_length_choice='effective') as source:
250-
assert source.subarray.tel[1].optics.equivalent_focal_length == u.Quantity(17*1.0713, u.m)
251-
assert source.subarray.tel[2].optics.equivalent_focal_length == u.Quantity(17*1.0713, u.m)
250+
with MAGICEventSource(input_url=dataset, process_run=False, focal_length_choice=FocalLengthKind.EFFECTIVE) as source:
251+
assert source.subarray.tel[1].optics.effective_focal_length == u.Quantity(17*1.0713, u.m)
252+
assert source.subarray.tel[2].optics.effective_focal_length == u.Quantity(17*1.0713, u.m)
252253
assert source.subarray.tel[1].camera.geometry.frame.focal_length == u.Quantity(17*1.0713, u.m)
253254
assert source.subarray.tel[2].camera.geometry.frame.focal_length == u.Quantity(17*1.0713, u.m)
254255

0 commit comments

Comments
 (0)