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

fwhm, rise/fall time #2151

Open
wants to merge 29 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
6553dc2
added fwhm and time parameters. Need to solve an error.
clara-escanuela Nov 24, 2022
b87ed27
fwhm definition changed
clara-escanuela Dec 2, 2022
5bc56b5
fwhm definition
clara-escanuela Dec 4, 2022
e0ad2bf
Added fall and rise time
clara-escanuela Dec 5, 2022
6d954a6
Adding new container and filling simteleventsource
clara-escanuela Sep 30, 2022
dced424
adding new simulation container and filling simteleventsource
clara-escanuela Sep 30, 2022
10fd48e
Fixed some issues
clara-escanuela Dec 5, 2022
15dbaa1
Fixed some issues
clara-escanuela Dec 5, 2022
412c866
suggested changes
clara-escanuela Dec 6, 2022
fbd875c
Added FWHM unit test
clara-escanuela Dec 7, 2022
492c6ee
improved peak amplitude definition
clara-escanuela Dec 10, 2022
e0db767
included upsampling
clara-escanuela Dec 11, 2022
fd8dc17
added baseline limits for calculating the mean of the waveform
clara-escanuela Dec 17, 2022
6296a74
improved variable names and unit test
clara-escanuela Jan 14, 2023
47f68ca
added saturation time as an additional time parameter
clara-escanuela Jan 17, 2023
e0a932b
added time over threshold
clara-escanuela Jan 20, 2023
347d6ce
added more tests
clara-escanuela Apr 3, 2023
24b3fad
solved style issues and test failure
clara-escanuela Apr 3, 2023
2e06677
style changes
clara-escanuela Apr 3, 2023
4aa35ba
added doc
clara-escanuela Apr 3, 2023
5897140
modified time parameters to work better
clara-escanuela May 19, 2023
5887196
added test for tot
clara-escanuela May 19, 2023
d9bbc7e
code style
clara-escanuela May 19, 2023
fe29167
added guvectorize
clara-escanuela Oct 26, 2023
f63f5fa
Re-ordered guvectorize and updated unit test
clara-escanuela Oct 27, 2023
680985b
add peak_index as argument
clara-escanuela Nov 2, 2023
40adf94
removed unnecessary argument
clara-escanuela Jan 15, 2024
3c29eac
optimization of tot
clara-escanuela Jan 18, 2024
766d061
explained tot
clara-escanuela Jan 18, 2024
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
112 changes: 112 additions & 0 deletions ctapipe/image/extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,118 @@
return correction


@guvectorize(
[
(float32[:], float32[:], float32[:], float32[:]),
(float64[:], float32[:], float32[:], float32[:]),
],
"(s)->(),(),()",
nopython=True,
cache=True,
)
def time_parameters(waveform, fwhm_arr, rise_time_arr, fall_time_arr):
Copy link
Member

Choose a reason for hiding this comment

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

I think this new method is missing in __all__ at the top.

"""
Calculates the full width at half maximum (FWHM) of R1 already calibrated waveforms.
The rise and fall time of these waveforms is also computed at 90% and 10% of the peak.

Parameters
----------
waveforms : ndarray
r1 waveforms stored in a numpy array.
Shape: (n_pix, n_samples)

Returns
-------
fwhm_arr : list of floats
Full width half maximum of pulse in units of time
Shape : (n_pix)
rise_time_arr : list of floats
Rise time of the pulse in units of time
Shape : (n_pix)
fall_time_arr : list of floats
Fall time of the pulse in units of time
Shape : (n_pix)

"""
peak_index = np.argmax(waveform)
amplitude = np.max(waveform)
Copy link
Member

Choose a reason for hiding this comment

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

This is wasteful, you don't need to find the maximum again, just do amplitude = waveform[peak_index]

n_samples = np.shape(waveform)[-1]

Check warning on line 389 in ctapipe/image/extractor.py

View check run for this annotation

Codecov / codecov/patch

ctapipe/image/extractor.py#L387-L389

Added lines #L387 - L389 were not covered by tests

# Initialize in case of dividing by zero
rt_90 = np.nan
rt_10 = np.nan
ft_90 = np.nan
ft_10 = np.nan
fwhm_left = np.nan
fwhm_right = np.nan

Check warning on line 397 in ctapipe/image/extractor.py

View check run for this annotation

Codecov / codecov/patch

ctapipe/image/extractor.py#L392-L397

Added lines #L392 - L397 were not covered by tests

half_amplitude = amplitude / 2
ampl_10percent = 0.1 * amplitude
ampl_90percent = 0.9 * amplitude

Check warning on line 401 in ctapipe/image/extractor.py

View check run for this annotation

Codecov / codecov/patch

ctapipe/image/extractor.py#L399-L401

Added lines #L399 - L401 were not covered by tests

for ti in range(peak_index, n_samples - 1):
tj = ti + 1
yi = waveform[ti]
yj = waveform[tj]
if (yj - yi) != 0:
if yi >= half_amplitude >= yj:
fwhm_right = ti + (half_amplitude - yi) / (yj - yi)
if yi >= ampl_90percent >= yj:
ft_90 = ti + (ampl_90percent - yi) / (yj - yi)
if yi >= ampl_10percent >= yj:
ft_10 = ti + (ampl_10percent - yi) / (yj - yi)

Check warning on line 413 in ctapipe/image/extractor.py

View check run for this annotation

Codecov / codecov/patch

ctapipe/image/extractor.py#L403-L413

Added lines #L403 - L413 were not covered by tests

for ti in range(peak_index, 0, -1):
tj = ti - 1
yi = waveform[ti]
yj = waveform[tj]
if (yj - yi) != 0:
if yi >= half_amplitude >= yj:
fwhm_left = ti - (half_amplitude - yi) / (yj - yi)
if yi >= ampl_90percent >= yj:
rt_90 = ti - (ampl_90percent - yi) / (yj - yi)
if yi >= ampl_10percent >= yj:
rt_10 = ti - (ampl_10percent - yi) / (yj - yi)

Check warning on line 425 in ctapipe/image/extractor.py

View check run for this annotation

Codecov / codecov/patch

ctapipe/image/extractor.py#L415-L425

Added lines #L415 - L425 were not covered by tests

fwhm = 0.0
if None not in (fwhm_right, fwhm_left):
Copy link
Member

Choose a reason for hiding this comment

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

You initialize these two with np.nan above, so None will never be in (fwhm_right, fwhm_left).

I think you can just subtract the two anyways, and the result will be nan if either is nan. Check if there is a warning and maybe silence it in case we don't want to have a warning like "invalid valid encountered in subtract".

fwhm = fwhm_right - fwhm_left

Check warning on line 429 in ctapipe/image/extractor.py

View check run for this annotation

Codecov / codecov/patch

ctapipe/image/extractor.py#L427-L429

Added lines #L427 - L429 were not covered by tests

rise_time = 0.0
if None not in (rt_90, rt_10):
Copy link
Member

Choose a reason for hiding this comment

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

Same as above with the other if

rise_time = rt_90 - rt_10

Check warning on line 433 in ctapipe/image/extractor.py

View check run for this annotation

Codecov / codecov/patch

ctapipe/image/extractor.py#L431-L433

Added lines #L431 - L433 were not covered by tests

fall_time = 0.0
if None not in (ft_90, ft_10):
Copy link
Member

Choose a reason for hiding this comment

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

Same as above with the other if

fall_time = ft_10 - ft_90

Check warning on line 437 in ctapipe/image/extractor.py

View check run for this annotation

Codecov / codecov/patch

ctapipe/image/extractor.py#L435-L437

Added lines #L435 - L437 were not covered by tests

fwhm_arr[0] = fwhm
rise_time_arr[0] = rise_time
fall_time_arr[0] = fall_time

Check warning on line 441 in ctapipe/image/extractor.py

View check run for this annotation

Codecov / codecov/patch

ctapipe/image/extractor.py#L439-L441

Added lines #L439 - L441 were not covered by tests


def time_over_threshold(waveforms, thr):
Copy link
Member

Choose a reason for hiding this comment

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

Technically, this is "samples above threshold", for "time over threshold you would have to divide by the sampling frequency.

"""
Calculates the time over threshold (TOT) of waveforms. This is the width of the pulse above
a threshold above the baseline of the waveform.

Parameters
----------
waveforms : ndarray
r1 waveforms stored in a numpy array.
thr: float
Threshold above baseline

Returns
-------
time_over_thr : list of int
Number of samples with amplitude > thr
Shape : (n_pix)

"""
return np.count_nonzero(waveforms > thr, axis=-1)


class ImageExtractor(TelescopeComponent):
def __init__(self, subarray, config=None, parent=None, **kwargs):
"""
Expand Down
42 changes: 41 additions & 1 deletion ctapipe/image/tests/test_extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import numpy as np
import pytest
from numpy.testing import assert_allclose, assert_equal
from scipy.signal import filtfilt
from scipy.signal import filtfilt, peak_widths
from scipy.stats import norm
from traitlets.config.loader import Config
from traitlets.traitlets import TraitError
Expand All @@ -27,6 +27,8 @@
integration_correction,
neighbor_average_maximum,
subtract_baseline,
time_over_threshold,
time_parameters,
)
from ctapipe.image.toymodel import SkewedGaussian, WaveformModel, obtain_time_image
from ctapipe.instrument import SubarrayDescription
Expand Down Expand Up @@ -156,6 +158,44 @@ def toymodel_mst_fc(subarray_mst_fc: object) -> object:
return get_test_toymodel(subarray_mst_fc)


def test_fwhm(toymodel):
waveforms, _, _, _, true_charge, _ = toymodel
waveforms = waveforms[true_charge > 5]
fwhm_scp = np.array([])

for i in range(0, len(waveforms)):
peak_index = np.argmax(waveforms[i])
widths = peak_widths(
waveforms[i],
peaks=[
peak_index,
],
rel_height=0.5,
)

fwhm_scp = np.append(fwhm_scp, widths[0])

fwhm, rise, fall = time_parameters(waveforms)

assert_allclose(
np.array(fwhm),
fwhm_scp,
atol=1e-3,
rtol=1e-3,
)


def test_tot(toymodel):
waveforms, _, _, _, true_charge, _ = toymodel

tot_scp = time_over_threshold(waveforms, thr=200)
assert np.array(tot_scp).all() == 0

waveforms = np.atleast_2d(np.heaviside([-4, -3, -2, -1, 0, 1, 2, 3, 4, 5], 1))
tot_scp = time_over_threshold(waveforms, thr=0.5)
assert np.array(tot_scp)[0] == 6


def test_extract_around_peak(toymodel):
waveforms, _, _, _, _, _ = toymodel
n_pixels, n_samples = waveforms.shape
Expand Down
1 change: 1 addition & 0 deletions docs/changes/2151.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add time parameters (FWHM, rise and fall time, and time over threshold)
Loading