Skip to content

Commit 4bf9995

Browse files
committed
Merge tag 'v0.10.19' into merge-v011
2 parents c6bae5d + 46392e4 commit 4bf9995

23 files changed

+130
-111
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ jobs:
3939

4040
strategy:
4141
matrix:
42-
python-version: ["3.10", "3.11"]
43-
ctapipe-version: ["v0.19.2"]
42+
python-version: ["3.11", "3.12", "3.13"]
43+
ctapipe-version: ["v0.25.1"]
4444

4545
steps:
4646
- uses: actions/checkout@v4

environment.yml

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,26 @@ name: lst-dev
22
channels:
33
- conda-forge
44
dependencies:
5-
- python=3.11
5+
- python=3.13
66
- numpy
7-
- astropy=5
7+
- astropy>=6.1,<8
88
- pip
9-
- ctapipe=0.19.2
9+
- ctapipe >=0.25.0
10+
- ctapipe_io_lst >=0.26.0,<0.28.0a0
1011
- ctaplot~=0.6.4
11-
- gammapy=1.1
12+
- gammapy=1.3
1213
- h5py
1314
- ipython
1415
- jupyter
15-
- matplotlib=3.7
16+
- matplotlib=3.10
1617
- notebook
1718
- iminuit>=2
18-
- joblib~=1.2.0
19+
- joblib~=1.4.0
1920
- toml
20-
- protozfits=2.6
21+
- protozfits=2.7
2122
- pyparsing
22-
- scipy~=1.11.4
23-
- scikit-learn=1.2
23+
- scipy
24+
- scikit-learn=1.6
2425
- sphinx
2526
- sphinx-automodapi
2627
- sphinx_rtd_theme
@@ -32,6 +33,5 @@ dependencies:
3233
- pandas>=2
3334
- pymongo
3435
- seaborn
35-
- ctapipe_io_lst >=0.25.1,<0.26a0
3636
- pytest
37-
- pyirf~=0.10.0
37+
- pyirf~=0.12.0

lstchain/calib/camera/flatfield.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,15 +135,15 @@ def _extract_charge(self, event):
135135
peak_pos = 0
136136
if self.extractor:
137137
broken_pixels = event.mon.tel[self.tel_id].pixel_status.hardware_failing_pixels
138-
dl1 = self.extractor(waveforms, self.tel_id, no_gain_selection, broken_pixels=broken_pixels)
138+
dl1 = self.extractor(waveforms, self.tel_id, selected_gain_channel=None, broken_pixels=broken_pixels)
139139
charge = dl1.image
140140
peak_pos = dl1.peak_time
141141

142142
# shift the time if time shift is already defined
143143
# (e.g. drs4 waveform time shifts for LST)
144144
time_shift = event.calibration.tel[self.tel_id].dl1.time_shift
145145
if time_shift is not None:
146-
peak_pos -= time_shift
146+
peak_pos -= time_shift
147147

148148
return charge, peak_pos
149149

lstchain/calib/camera/pedestals.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,10 @@ def _extract_charge(self, event):
137137
peak_pos = 0
138138
if self.extractor:
139139
broken_pixels = event.mon.tel[self.tel_id].pixel_status.hardware_failing_pixels
140-
dl1 = self.extractor(waveforms, self.tel_id, no_gain_selection, broken_pixels=broken_pixels)
140+
141+
dl1 = self.extractor(waveforms, self.tel_id,
142+
selected_gain_channel=None,
143+
broken_pixels=broken_pixels)
141144
charge = dl1.image
142145
peak_pos = dl1.peak_time
143146

lstchain/data/lstchain_standard_config.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -287,13 +287,14 @@
287287
"write_interleaved_events":{
288288
"DataWriter": {
289289
"overwrite": true,
290-
"write_images": false,
291-
"write_parameters": false,
292-
"write_waveforms": true,
290+
"write_dl1_images": false,
291+
"write_dl1_parameters": false,
292+
"write_r0_waveforms": false,
293+
"write_r1_waveforms": true,
293294
"transform_waveform": true,
294295
"waveform_dtype": "uint16",
295296
"waveform_offset": 400,
296-
"waveform_scale": 80
297+
"waveform_scale": 60
297298
}
298299
}
299300
}

lstchain/datachecks/containers.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,8 @@ def fill_pixel_wise_info(self, table, mask, histogram_binnings,
329329
pointing = SkyCoord(az=self.mean_az_tel,
330330
alt=self.mean_alt_tel,
331331
frame=horizon_frame)
332-
bright_stars = get_bright_stars(pointing=pointing, radius=3*u.deg,
332+
bright_stars = get_bright_stars(time=obstime,
333+
pointing=pointing, radius=3*u.deg,
333334
magnitude_cut=8)
334335
# Account for average relative spot shift (outwards) due to coma
335336
# aberration:

lstchain/datachecks/dl1_checker.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -387,8 +387,8 @@ def plot_datacheck(datacheck_filename, out_path=None, batch=False,
387387
engineering_geom = geom.transform_to(EngineeringCameraFrame())
388388

389389
# For future bokeh-based display, turned off for now:
390-
# page1 = Panel()
391-
# page2 = Panel()
390+
# page1 = TabPanel()
391+
# page2 = TabPanel()
392392

393393
with PdfPages(pdf_filename) as pdf:
394394
# first deal with the DL1 datacheck file, created from DL1 event data:

lstchain/image/modifier.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -711,7 +711,7 @@ def initialise_waveforms(self, waveform, dt, tel_id):
711711
712712
"""
713713
log.info(f"Pre-generating nsb waveforms for nsb tuning and telescope id {tel_id}.")
714-
n_pixels, n_samples = waveform.shape
714+
_, n_pixels, n_samples = waveform.shape
715715
baseline_correction = -(self.added_nsb[tel_id] * dt).to_value("")
716716
nsb_waveforms = np.full((self.multiplicity * n_pixels, 2, n_samples), baseline_correction)
717717
duration = (self.extra_samples + n_samples) * dt
@@ -768,12 +768,12 @@ def _tune_nsb_on_waveform_precomputed(self, waveform, tel_id, is_high_gain, suba
768768
sampling_rate = readout.sampling_rate
769769
dt = (1.0 / sampling_rate).to(u.s)
770770
self.initialise_waveforms(waveform, dt, tel_id)
771-
n_pixels, _ = waveform.shape
771+
_, n_pixels, _ = waveform.shape
772772
# The nsb_waveform array is randomised along the first axis,
773773
# then the n_pixels first elements with correct gain are used for the injection
774774
self.rng.shuffle(self.nsb_waveforms[tel_id])
775-
waveform += ((is_high_gain == 0)[:, np.newaxis] * self.nsb_waveforms[tel_id][:n_pixels, 0] +
776-
(is_high_gain == 1)[:, np.newaxis] * self.nsb_waveforms[tel_id][:n_pixels, 1])
775+
waveform[0] += ((is_high_gain == 0)[:, np.newaxis] * self.nsb_waveforms[tel_id][:n_pixels, 0] +
776+
(is_high_gain == 1)[:, np.newaxis] * self.nsb_waveforms[tel_id][:n_pixels, 1])
777777

778778
def _tune_nsb_on_waveform_direct(self, waveform, tel_id, is_high_gain, subarray):
779779
"""
@@ -790,7 +790,7 @@ def _tune_nsb_on_waveform_direct(self, waveform, tel_id, is_high_gain, subarray)
790790
subarray: `ctapipe.instrument.subarray.SubarrayDescription`
791791
792792
"""
793-
n_pixels, n_samples = waveform.shape
793+
_, n_pixels, n_samples = waveform.shape
794794
readout = subarray.tel[tel_id].camera.readout
795795
sampling_rate = readout.sampling_rate
796796
dt = (1.0 / sampling_rate).to(u.s)

lstchain/image/muon/muon_analysis.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -248,9 +248,7 @@ def analyze_muon_event(subarray, tel_id, event_id, image, good_ring_config, plot
248248
pix_ring_2 = image[dist_mask_2]
249249

250250
muonparameters = MuonParametersContainer()
251-
muonparameters.containment = ring_containment(
252-
muonringparam.radius,
253-
muonringparam.center_fov_lon, muonringparam.center_fov_lat, cam_rad)
251+
muonparameters.containment = ring_containment(muonringparam, cam_rad)
254252

255253
radial_distribution = radial_light_distribution(
256254
muonringparam.center_fov_lon,
@@ -291,12 +289,12 @@ def analyze_muon_event(subarray, tel_id, event_id, image, good_ring_config, plot
291289

292290
# We do the calculation of the ring completeness (i.e. fraction of whole circle) using the pixels
293291
# within the "width" fitted using MuonIntensityFitter
292+
294293
muonparameters.completeness = ring_completeness(
295-
x[dist_ringwidth_mask], y[dist_ringwidth_mask],
296-
image[dist_ringwidth_mask],
297-
muonringparam.radius,
298-
muonringparam.center_fov_lon,
299-
muonringparam.center_fov_lat,
294+
pixel_fov_lon=x[dist_ringwidth_mask],
295+
pixel_fov_lat=y[dist_ringwidth_mask],
296+
weights=image[dist_ringwidth_mask],
297+
ring=muonringparam,
300298
threshold=params['ring_completeness_threshold'],
301299
bins=30)
302300

lstchain/image/tests/test_modifier.py

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,7 @@ def test_tune_nsb_on_waveform():
8585
from lstchain.image.modifier import WaveformNsbTuner
8686
from lstchain.data.normalised_pulse_template import NormalizedPulseTemplate
8787

88-
waveform = np.array(
89-
[[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
90-
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]]
91-
)
88+
waveform = np.zeros((2, 2, 22))
9289
added_nsb_rate = {1: 0.1 * u.GHz}
9390
subarray = LSTEventSource.create_subarray(1)
9491
amplitude_HG = np.zeros(40)
@@ -108,21 +105,19 @@ def test_tune_nsb_on_waveform():
108105
charge_spe_cumulative_pdf = interp1d(spe_integral, spe[0], kind='cubic',
109106
bounds_error=False, fill_value=0.,
110107
assume_sorted=True)
111-
nsb_tunner = WaveformNsbTuner(added_nsb_rate,
112-
pulse_templates,
113-
charge_spe_cumulative_pdf,
114-
pre_computed_multiplicity=0)
115-
nsb_tunner.tune_nsb_on_waveform(waveform, 1, gain, subarray)
108+
nsb_tuner = WaveformNsbTuner(added_nsb_rate,
109+
pulse_templates,
110+
charge_spe_cumulative_pdf,
111+
pre_computed_multiplicity=0)
112+
nsb_tuner.tune_nsb_on_waveform(waveform, 1, gain, subarray)
116113
assert np.any(waveform != 0)
117114
assert np.isclose(np.mean(waveform), 0.0, atol=0.2)
118-
waveform = np.array(
119-
[[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
120-
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]]
121-
)
122-
nsb_tunner = WaveformNsbTuner(added_nsb_rate,
123-
pulse_templates,
124-
charge_spe_cumulative_pdf,
125-
pre_computed_multiplicity=10)
126-
nsb_tunner.tune_nsb_on_waveform(waveform, 1, gain, subarray)
115+
116+
waveform = np.zeros((2, 2, 22))
117+
nsb_tuner = WaveformNsbTuner(added_nsb_rate,
118+
pulse_templates,
119+
charge_spe_cumulative_pdf,
120+
pre_computed_multiplicity=10)
121+
nsb_tuner.tune_nsb_on_waveform(waveform, 1, gain, subarray)
127122
assert np.any(waveform != 0)
128123
assert np.isclose(np.mean(waveform), 0.0, atol=0.2)

lstchain/mc/sensitivity.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import astropy.units as u
22
import numpy as np
33
import pandas as pd
4-
from astropy.coordinates.angle_utilities import angular_separation
4+
from astropy.coordinates import angular_separation
55
from gammapy.stats import WStatCountsStatistic
66

77
from lstchain.io import read_simu_info_merged_hdf5

lstchain/reco/r0_to_dl1.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ def r0_to_dl1(
510510
event.simulation.prefix = 'mc'
511511

512512
dl1_container.reset()
513-
513+
514514
# write sub tables
515515
if is_simu:
516516
write_subarray_tables(writer, event, metadata)
@@ -536,6 +536,13 @@ def r0_to_dl1(
536536
event.mon.tel[tel_id],
537537
new_ped=True, new_ff=True)
538538

539+
# Default of absolute_factor is None - needs to be set here
540+
event.calibration.tel[tel_id].dl1.absolute_factor = np.ones((2, PIXEL_INDEX.size))
541+
# PATCH: ctapipe expects relative_factor to be always (ngains, npixels)
542+
if event.calibration.tel[tel_id].dl1.relative_factor is not None:
543+
if event.calibration.tel[tel_id].dl1.relative_factor.ndim == 1:
544+
event.calibration.tel[tel_id].dl1.relative_factor = np.array(2*[event.calibration.tel[tel_id].dl1.relative_factor])
545+
539546
# flat-field or pedestal:
540547
if ((event.trigger.event_type == EventType.FLATFIELD or
541548
event.trigger.event_type == EventType.SKY_PEDESTAL) and
@@ -568,14 +575,10 @@ def r0_to_dl1(
568575

569576
r1 = event.r1.tel[tel_id]
570577
r1.selected_gain_channel = source.r0_r1_calibrator.gain_selector(event.r0.tel[tel_id].waveform)
571-
r1.waveform = r1.waveform[r1.selected_gain_channel, PIXEL_INDEX]
572-
573-
event.calibration.tel[tel_id].dl1.time_shift = \
574-
event.calibration.tel[tel_id].dl1.time_shift[r1.selected_gain_channel, PIXEL_INDEX]
575-
576-
event.calibration.tel[tel_id].dl1.relative_factor = \
577-
event.calibration.tel[tel_id].dl1.relative_factor[r1.selected_gain_channel, PIXEL_INDEX]
578+
# select gain but keep waveform 3d
579+
r1.waveform = r1.waveform[np.newaxis, r1.selected_gain_channel, PIXEL_INDEX]
578580

581+
579582
# Option to add nsb in waveforms
580583
if nsb_tuning:
581584
# FIXME? assumes same correction ratio for all telescopes
@@ -702,15 +705,15 @@ def r0_to_dl1(
702705
if tag_pix_thr(image):
703706

704707
# re-calibrate r1 to obtain new dl1, using a more adequate pulse integrator for muon rings
705-
numsamples = event.r1.tel[telescope_id].waveform.shape[1] # not necessarily the same as in r0!
708+
numsamples = event.r1.tel[telescope_id].waveform.shape[2] # not necessarily the same as in r0!
706709
bad_pixels_hg = calibration_mon.unusable_pixels[0]
707710
bad_pixels_lg = calibration_mon.unusable_pixels[1]
708711

709712
# Now set to 0 all samples in unreliable pixels. Important for global peak
710713
# integrator in case of crazy pixels! TBD: can this be done in a simpler
711714
# way?
712715
bad_pixels = bad_pixels_hg | bad_pixels_lg
713-
bad_waveform = np.transpose(np.array(numsamples * [bad_pixels]))
716+
bad_waveform = np.array([np.transpose(np.array(numsamples * [bad_pixels]))]) # (1, npixels, nsamples)
714717

715718
# print('hg bad pixels:',np.where(bad_pixels_hg))
716719
# print('lg bad pixels:',np.where(bad_pixels_lg))
@@ -745,8 +748,8 @@ def r0_to_dl1(
745748
mask_hg = bright_pixels & (selected_gain == 0)
746749
mask_lg = bright_pixels & (selected_gain == 1)
747750

748-
bright_pixels_waveforms_hg = event.r1.tel[telescope_id].waveform[mask_hg, :]
749-
bright_pixels_waveforms_lg = event.r1.tel[telescope_id].waveform[mask_lg, :]
751+
bright_pixels_waveforms_hg = event.r1.tel[telescope_id].waveform[0, mask_hg, :]
752+
bright_pixels_waveforms_lg = event.r1.tel[telescope_id].waveform[0, mask_lg, :]
750753
stacked_waveforms_hg = np.sum(bright_pixels_waveforms_hg, axis=0)
751754
stacked_waveforms_lg = np.sum(bright_pixels_waveforms_lg, axis=0)
752755

lstchain/reco/reconstructor.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -154,14 +154,14 @@ def get_ped_from_true_signal_less(self, source, nsb_tunner):
154154
if event.trigger.event_type == EventType.SUBARRAY:
155155
for tel_id in event.r1.tel.keys():
156156
if i == 0:
157-
n_pix[tel_id] = event.r1.tel[tel_id].waveform.shape[0]
157+
n_pix[tel_id] = event.r1.tel[tel_id].waveform.shape[1]
158158
mask = event.simulation.tel[tel_id].true_image == 0
159159
wave = event.r1.tel[tel_id].waveform
160160
if nsb_tunner is not None:
161161
selected_gains = event.r1.tel[tel_id].selected_gain_channel
162162
mask_high = (selected_gains == 0)
163163
nsb_tunner.tune_nsb_on_waveform(wave, tel_id, mask_high, self.subarray)
164-
waveforms[tel_id].append(wave[mask].flatten())
164+
waveforms[tel_id].append(wave[0, mask].flatten())
165165
for tel_id, tel_waveforms in waveforms.items():
166166
self.error[tel_id] = np.full(n_pix[tel_id], np.nanstd(np.concatenate(tel_waveforms)))
167167
self.allowed_pixels = np.ones(n_pix[tel_id], dtype=bool)
@@ -210,14 +210,15 @@ def call_setup(self, event, telescope_id, dl1_container):
210210
waveform = event.r1.tel[telescope_id].waveform
211211

212212
dl1_calib = event.calibration.tel[telescope_id].dl1
213-
time_shift = dl1_calib.time_shift
214213
# TODO check if this is correct here or if it is applied to r1 waveform earlier
215214
if dl1_calib.pedestal_offset is not None:
216215
waveform = waveform - dl1_calib.pedestal_offset[:, np.newaxis]
217216

218-
n_pixels, n_samples = waveform.shape
217+
n_samples = waveform.shape[2]
219218
times = np.arange(0, n_samples) * dt
220219
selected_gains = event.r1.tel[telescope_id].selected_gain_channel
220+
time_shift = dl1_calib.time_shift
221+
221222
is_high_gain = (selected_gains == 0)
222223

223224
# We assume that the time gradient is given in unit of 'geometry spatial unit'/ns
@@ -283,13 +284,15 @@ def call_setup(self, event, telescope_id, dl1_container):
283284
crosstalks = spatial_ones * self.crosstalk.tel[telescope_id]
284285

285286
times = (np.arange(0, n_samples) * dt)[mask_time]
287+
288+
time_shift = np.choose(selected_gains, time_shift)
286289
time_shift = time_shift[mask_pixel]
287290

288291
p_x = pix_x[mask_pixel]
289292
p_y = pix_y[mask_pixel]
290293
pix_area = geometry.pix_area[mask_pixel].to_value(unit ** 2)
291294

292-
data = waveform
295+
data = waveform[0]
293296
error = self.error
294297

295298
filter_pixels = np.nonzero(~mask_pixel)

lstchain/reco/tests/test_utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ def test_camera_to_altaz():
3030
def test_radec_to_camera():
3131
pointing_radec = SkyCoord.from_name("Crab")
3232
obstime = Time("2020-01-27T23:00", scale="utc")
33-
pointing_alt = u.Quantity(1.3748, u.rad, copy=False)
34-
pointing_az = u.Quantity(4.0975, u.rad, copy=False)
33+
pointing_alt = u.Quantity(1.3748, u.rad)
34+
pointing_az = u.Quantity(4.0975, u.rad)
3535
focal = 28 * u.m
3636
expected_source_pos_camera = np.array([0.0, 0.0]) * u.m
3737
pointing_pos_camera = utils.radec_to_camera(

0 commit comments

Comments
 (0)