Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Likelihood reconstruction update #1200

Merged
merged 6 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 42 additions & 11 deletions lstchain/data/lstchain_lhfit_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -141,39 +141,69 @@
"lhfit_width",
"lhfit_length",
"lhfit_length_asymmetry",
"lhfit_time_gradient"
"lhfit_time_gradient",
"leakage_intensity_width_2",
"lhfit_r",
"lhfit_psi",
"sin_az_tel",
"alt_tel"
],

"disp_method": "disp_vector",
"disp_method": "disp_norm_sign",

"disp_regression_features": [
"lhfit_log_intensity",
"lhfit_width",
"lhfit_length",
"lhfit_wl",
"lhfit_length_asymmetry",
"lhfit_time_gradient",
"leakage_intensity_width_2",
"lhfit_r",
"lhfit_psi",
"lhfit_time_gradient"
"lhfit_phi",
"sin_az_tel",
"alt_tel"
],

"disp_classification_features": [
"lhfit_log_intensity",
"lhfit_width",
"lhfit_length",
"lhfit_wl",
"lhfit_length_asymmetry",
"lhfit_time_gradient",
"leakage_intensity_width_2",
"lhfit_r",
"lhfit_wl",
"lhfit_psi",
"lhfit_time_gradient"
"lhfit_phi",
"sin_az_tel",
"alt_tel"
],

"particle_classification_features": [
"lhfit_log_intensity",
"lhfit_width",
"lhfit_length",
"lhfit_wl",
"lhfit_length_asymmetry",
"lhfit_time_gradient",
"log_intensity",
"width",
"length",
"x",
"y",
"wl",
"signed_skewness",
"kurtosis",
"signed_time_gradient",
SeiyaNozaki marked this conversation as resolved.
Show resolved Hide resolved
"leakage_intensity_width_2",
"lhfit_r",
"lhfit_wl",
"lhfit_psi",
"lhfit_phi",
"log_reco_energy",
"reco_disp_dx",
"reco_disp_dy"
"reco_disp_norm",
"sin_az_tel",
"alt_tel"
],

"allowed_tels": [1],
Expand Down Expand Up @@ -212,6 +242,7 @@

"LSTCalibrationCalculator":{
"systematic_correction_path": null,
"npe_median_cut_outliers": [-5,5],
"squared_excess_noise_factor": 1.222,
"flatfield_product": "FlasherFlatFieldCalculator",
"pedestal_product": "PedestalIntegrator",
Expand All @@ -236,7 +267,7 @@
"tel_id":1,
"time_sampling_correction_path": null,
"charge_product":"LocalPeakWindowSum",
"charge_median_cut_outliers": [-0.5,0.5],
"charge_median_cut_outliers": [-0.9,2],
"charge_std_cut_outliers": [-10,10],
"time_cut_outliers": [2,38],
"LocalPeakWindowSum":{
Expand Down Expand Up @@ -272,7 +303,7 @@
],
"n_peaks": 0,
"no_asymmetry": false,
"use_weight": false,
"use_interleaved": true,
"verbose": 0
}
}
29 changes: 20 additions & 9 deletions lstchain/reco/dl1_to_dl2.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,11 @@
effective_focal_length = OPTICS.effective_focal_length

df_gamma = update_disp_with_effective_focal_length(df_gamma, effective_focal_length=effective_focal_length)
if 'lh_fit_config' in config.keys():
lhfit_df_gamma = pd.read_hdf(filegammas, key=dl1_likelihood_params_lstcam_key)
df_gamma = pd.concat([df_gamma, lhfit_df_gamma], axis=1)
lhfit_df_proton = pd.read_hdf(fileprotons, key=dl1_likelihood_params_lstcam_key)
df_proton = pd.concat([df_proton, lhfit_df_proton], axis=1)

Check warning on line 367 in lstchain/reco/dl1_to_dl2.py

View check run for this annotation

Codecov / codecov/patch

lstchain/reco/dl1_to_dl2.py#L364-L367

Added lines #L364 - L367 were not covered by tests

if config['source_dependent']:
# if source-dependent parameters are already in dl1 data, just read those data
Expand Down Expand Up @@ -395,13 +400,6 @@

df_proton = pd.concat([df_proton, src_dep_df_proton['on']], axis=1)

if 'lh_fit_config' in config.keys():
lhfit_df_gamma = pd.read_hdf(filegammas, key=dl1_likelihood_params_lstcam_key)
df_gamma = pd.concat([df_gamma, lhfit_df_gamma], axis=1)

lhfit_df_proton = pd.read_hdf(fileprotons, key=dl1_likelihood_params_lstcam_key)
df_proton = pd.concat([df_proton, lhfit_df_proton], axis=1)

# Normalize all azimuth angles to the range [0, 360) degrees
df_gamma.az_tel = Angle(df_gamma.az_tel, u.rad).wrap_at(360 * u.deg).rad
df_proton.az_tel = Angle(df_proton.az_tel, u.rad).wrap_at(360 * u.deg).rad
Expand Down Expand Up @@ -794,8 +792,6 @@
src_dep_params['expected_src_x'] = expected_src_pos_x_m
src_dep_params['expected_src_y'] = expected_src_pos_y_m

src_dep_params['dist'] = np.sqrt((data['x'] - expected_src_pos_x_m) ** 2 + (data['y'] - expected_src_pos_y_m) ** 2)

disp, miss = camera_to_shower_coordinates(
expected_src_pos_x_m,
expected_src_pos_y_m,
Expand All @@ -805,6 +801,21 @@

src_dep_params['time_gradient_from_source'] = data['time_gradient'] * np.sign(disp) * -1
src_dep_params['skewness_from_source'] = data['skewness'] * np.sign(disp) * -1

if 'lhfit_x' in data.keys():
# Use lhfit parameters for 'dist' and 'alpha'
disp, miss = camera_to_shower_coordinates(

Check warning on line 807 in lstchain/reco/dl1_to_dl2.py

View check run for this annotation

Codecov / codecov/patch

lstchain/reco/dl1_to_dl2.py#L807

Added line #L807 was not covered by tests
expected_src_pos_x_m,
expected_src_pos_y_m,
data['lhfit_x'],
data['lhfit_y'],
data['lhfit_psi'])
src_dep_params['dist'] = np.sqrt((data['lhfit_x'] - expected_src_pos_x_m) ** 2 +

Check warning on line 813 in lstchain/reco/dl1_to_dl2.py

View check run for this annotation

Codecov / codecov/patch

lstchain/reco/dl1_to_dl2.py#L813

Added line #L813 was not covered by tests
(data['lhfit_y'] - expected_src_pos_y_m) ** 2)
else:
src_dep_params['dist'] = np.sqrt((data['x'] - expected_src_pos_x_m) ** 2 +
(data['y'] - expected_src_pos_y_m) ** 2)

src_dep_params['alpha'] = np.rad2deg(np.arctan(np.abs(miss / disp)))

return src_dep_params
Expand Down
9 changes: 6 additions & 3 deletions lstchain/reco/r0_to_dl1.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,9 +303,7 @@
try:
lhfit_container = fitter(event=event, telescope_id=telescope_id, dl1_container=dl1_container)
except Exception:
logger.error("Unexpected error encountered in likelihood reconstruction.\n"
"Compiled likelihood reconstruction numbaCC functions may be missing.\n"
"In this case you should run: lstchain/scripts/numba_compil_lhfit.py")
logger.error("Unexpected error encountered in likelihood reconstruction.")

Check warning on line 306 in lstchain/reco/r0_to_dl1.py

View check run for this annotation

Codecov / codecov/patch

lstchain/reco/r0_to_dl1.py#L306

Added line #L306 was not covered by tests
raise
else:
lhfit_container = DL1LikelihoodParametersContainer(lhfit_call_status=-10)
Expand Down Expand Up @@ -445,6 +443,11 @@
if 'lh_fit_config' in config.keys():
lhfit_fitter_config = {'TimeWaveformFitter': config['lh_fit_config']}
lhfit_fitter = TimeWaveformFitter(subarray=subarray, config=Config(lhfit_fitter_config))
if lhfit_fitter_config['TimeWaveformFitter']['use_interleaved'] and not is_simu:
tmp_source = EventSource(input_url=input_filename,

Check warning on line 447 in lstchain/reco/r0_to_dl1.py

View check run for this annotation

Codecov / codecov/patch

lstchain/reco/r0_to_dl1.py#L447

Added line #L447 was not covered by tests
config=Config(config["source_config"]))
lhfit_fitter.get_ped_from_interleaved(tmp_source)
del tmp_source

Check warning on line 450 in lstchain/reco/r0_to_dl1.py

View check run for this annotation

Codecov / codecov/patch

lstchain/reco/r0_to_dl1.py#L449-L450

Added lines #L449 - L450 were not covered by tests

# initialize the writer of the interleaved events
interleaved_writer = None
Expand Down
41 changes: 34 additions & 7 deletions lstchain/reco/reconstructor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@

from lstchain.data.normalised_pulse_template import NormalizedPulseTemplate

from ctapipe.containers import EventType
from ctapipe.core import TelescopeComponent
from ctapipe.core.traits import Bool, Float, FloatTelescopeParameter, Int, Path
from ctapipe.core.traits import Bool, Float, FloatTelescopeParameter, Int
from lstchain.io.lstcontainers import DL1LikelihoodParametersContainer
from lstchain.reco.reconstructorCC import log_pdf as log_pdf

Expand All @@ -33,12 +34,10 @@
time_after_shower = FloatTelescopeParameter(default_value=20,
help='Additional time at the end of the fit temporal window.',
allow_none=False).tag(config=True)
use_weight = Bool(False, help='If True, the brightest sample is twice as important as the dimmest pixel in the '
'likelihood. If false all samples are equivalent.', allow_none=False).tag(config=True)
no_asymmetry = Bool(False, help='If true, the asymmetry of the spatial model is fixed to 0.',
allow_none=False).tag(config=True)
use_interleaved = Path(None, help='Location of the dl1 file used to estimate the pedestal exploiting interleaved'
' events.', allow_none=True).tag(config=True)
use_interleaved = Bool(None, help='If true, the std deviation of pedestals and dimmed pixels are estimated on '
'interleaved events', allow_none=True).tag(config=True)
n_peaks = Int(0, help='Maximum brightness (p.e.) for which the full likelihood computation is used. '
'If the Poisson term for Np.e.>n_peak is more than 1e-6 a Gaussian approximation is used.',
allow_none=False).tag(config=True)
Expand Down Expand Up @@ -96,6 +95,8 @@
self.transition_charges = {}
for tel_id in subarray.tel:
self.transition_charges[tel_id] = transition_charges[self.crosstalk.tel[tel_id]]
self.error = None
self.allowed_pixels = True

self.start_parameters = None
self.names_parameters = None
Expand All @@ -104,6 +105,29 @@
self.bound_parameters = None
self.fcn = None

def get_ped_from_interleaved(self, source):
"""
Parameters
----------
source
"""
self.error = {}
waveforms = {}
for tel_id in self.subarray.tel:
waveforms[tel_id] = []
for i, event in enumerate(source):
if event.trigger.event_type == EventType.SKY_PEDESTAL:
source.r0_r1_calibrator.calibrate(event)
for tel_id in event.r1.tel.keys():
waveforms[tel_id].append(event.r1.tel[tel_id].waveform.squeeze())
for tel_id, tel_waveforms in waveforms.items():
x=np.concatenate(np.asarray(tel_waveforms), axis=1)
std=[]
for elt in x:
std.append(np.nanstd(elt))
self.error[tel_id] = std
self.allowed_pixels = (std > 0.5 * np.median(std))

Check warning on line 129 in lstchain/reco/reconstructor.py

View check run for this annotation

Codecov / codecov/patch

lstchain/reco/reconstructor.py#L114-L129

Added lines #L114 - L129 were not covered by tests

def call_setup(self, event, telescope_id, dl1_container):
"""
Extract all event dependent quantities used for the fit.
Expand Down Expand Up @@ -212,6 +236,7 @@
}

mask_pixel, mask_time = self.clean_data(pix_x, pix_y, pix_radius, times, start_parameters, telescope_id)
mask_pixel = mask_pixel & self.allowed_pixels
spatial_ones = np.ones(np.sum(mask_pixel))

is_high_gain = is_high_gain[mask_pixel]
Expand All @@ -226,14 +251,16 @@
pix_area = geometry.pix_area[mask_pixel].to_value(unit ** 2)

data = waveform
error = None # TODO include option to use calibration data
error = self.error

filter_pixels = np.nonzero(~mask_pixel)
filter_times = np.nonzero(~mask_time)

if error is None:
std = np.std(data[~mask_pixel])
error = np.full(data.shape[0], std)
else:
error = self.error[telescope_id]

Check warning on line 263 in lstchain/reco/reconstructor.py

View check run for this annotation

Codecov / codecov/patch

lstchain/reco/reconstructor.py#L263

Added line #L263 was not covered by tests

data = np.delete(data, filter_pixels, axis=0)
data = np.delete(data, filter_times, axis=1)
Expand All @@ -247,7 +274,7 @@
template.t0, template.amplitude_LG,
template.amplitude_HG, self.n_peaks,
self.transition_charges[telescope_id],
self.use_weight, self.factorial]
self.factorial]

self.start_parameters = start_parameters
self.names_parameters = start_parameters.keys()
Expand Down
31 changes: 8 additions & 23 deletions lstchain/reco/reconstructorCC.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@


@njit(cache=True)
def log_pdf_ll(mu, waveform, error, crosstalk, sig_s, templates, factorial, n_peaks, weight):
def log_pdf_ll(mu, waveform, error, crosstalk, sig_s, templates, factorial, n_peaks):
"""
Performs the sum log likelihood for low luminosity pixels in TimeWaveformFitter.
The log likelihood is sum(pixels) sum(times) of the log single sample likelihood.
Expand All @@ -33,8 +33,6 @@
n_peaks: int64
Size of the factorial array and possible number of photo-electron
in a pixel with relevant Poisson probability
weight : float 64 2D array
Weight to use in the likelihood for each sample

Returns
----------
Expand Down Expand Up @@ -65,12 +63,11 @@
if sumlh_k <= 0:
return -np.inf
# Add the log single sample likelihood to the full log likelihood
# An optional weight increasing high signal sample importance is supported
sumlh += weight[i, j] * np.log(sumlh_k)
sumlh += np.log(sumlh_k)

Check warning on line 66 in lstchain/reco/reconstructorCC.py

View check run for this annotation

Codecov / codecov/patch

lstchain/reco/reconstructorCC.py#L66

Added line #L66 was not covered by tests
return sumlh

@njit(cache=True)
def log_pdf_hl(mu, waveform, error, crosstalk, templates, weight):
def log_pdf_hl(mu, waveform, error, crosstalk, templates):
"""
Performs the sum log likelihood for high luminosity pixels in TimeWaveformFitter.log_pdf
The log likelihood is sum(pixels) sum(times) of the log single sample likelihood.
Expand All @@ -88,8 +85,6 @@
Crosstalk factor for each pixel
templates: float64 2D array
Value of the pulse template evaluated in each pixel at each observed time
weight : float 64 2D array
Weight to use in the likelihood for each sample

Returns
----------
Expand All @@ -109,8 +104,7 @@
log_gauss = (-(waveform[i, j] - mean) * (waveform[i, j] - mean) / 2.0 / sigma / sigma
- np.log(np.sqrt(2 * np.pi) * sigma))
# Add the log single sample likelihood to the full log likelihood
# An optional weight increasing high signal sample importance is supported
sumlh += weight[i, j] * log_gauss
sumlh += log_gauss

Check warning on line 107 in lstchain/reco/reconstructorCC.py

View check run for this annotation

Codecov / codecov/patch

lstchain/reco/reconstructorCC.py#L107

Added line #L107 was not covered by tests
return sumlh


Expand Down Expand Up @@ -297,7 +291,7 @@
def log_pdf(charge, t_cm, x_cm, y_cm, length, wl, psi, v, rl,
data, error, is_high_gain, sig_s, crosstalks, times, time_shift,
p_x, p_y, pix_area, template_dt, template_t0, template_lg,
template_hg, n_peaks, transition_charge, use_weight, factorial):
template_hg, n_peaks, transition_charge, factorial):
"""
Compute the log likelihood of the model used for a set of input parameters.

Expand Down Expand Up @@ -344,8 +338,6 @@
Maximum number of p.e. term used in the low luminosity likelihood
transition_charge: float32
Model charge above which the Gaussian approximation (log_pdf_hl) is used
use_weight: bool
If True, the brightest sample are made more important in the likelihood computation
factorial: unsigned int64
Pre-computed table of factorials

Expand Down Expand Up @@ -384,28 +376,21 @@
mask_LL = (mu <= transition_charge) & (mu > 0)
mask_HL = ~mask_LL

if use_weight:
weight = 1.0 + (data / np.max(data))
else:
weight = np.ones(data.shape)

log_pdf_faint = log_pdf_ll(mu[mask_LL],
data[mask_LL],
error[mask_LL],
crosstalks[mask_LL],
sig_s[mask_LL],
templates[mask_LL],
factorial,
n_peaks,
weight[mask_LL])
n_peaks)

log_pdf_bright = log_pdf_hl(mu[mask_HL],
data[mask_HL],
error[mask_HL],
crosstalks[mask_HL],
templates[mask_HL],
weight[mask_HL])
templates[mask_HL])

log_lh = (log_pdf_faint + log_pdf_bright) / np.sum(weight)
log_lh = (log_pdf_faint + log_pdf_bright) / (data.shape[0] * data.shape[1])

Check warning on line 394 in lstchain/reco/reconstructorCC.py

View check run for this annotation

Codecov / codecov/patch

lstchain/reco/reconstructorCC.py#L394

Added line #L394 was not covered by tests

return log_lh
Loading
Loading