Implement Pyrrha functionalities in Irene#953
Conversation
Done for backwards compatibility, this function now mirrors that of `calibrate_sipms()` before thresholding was removed.
This is the main controlling function, that decides which type of cut will be applied.
This included the adjustment of the cuts, applying `select_wfs_above_time_integrated_thr()` and the zeroing integration cut before the waveform rebinning.
This is to avoid possible situations where `calibrate_sipms()` is used outwith irene. `thr` is now a keyword argument to ensure that if a threshold is applied it is done so with intent.
Initially this was done over the entire waveforms due to the reordering of the cuts when applied in `build_sipm_responses()`. As suggested by @Ian0sborne, it would be wise to move these cuts out of `compute_and_write_pmaps()`
…ection`) into `pyrrha_sipm_selection`
…iPMs and their waveforms
…SiPMSelectionMethod class
…for incorrect input
fb435dd to
b7f45d4
Compare
| if thr is None: | ||
| return cwfs | ||
| else: | ||
| # if you apply a threshold here, apply as usual | ||
| thr = to_col_vector(np.full(sipm_wfs.shape[0], thr)) | ||
| return np.where(cwfs > thr, cwfs, 0) |
There was a problem hiding this comment.
Even though this is not used in the code, this is needed for 4 tests in calib_sensor_functions_test.py.
There was a problem hiding this comment.
Add another function that does the thresholds suppression, redo those tests doing the threshold suppression outside calibrate_sipms, and finally remove the else branch here
There was a problem hiding this comment.
The function charge_threshold_method() in reco/wfm_functions.py does the zero suppression step. Should I make a function that does just that? If I do this, do I remove the test that rely on zero suppression from calib_sensor_functions_test.py and implement them specifically for this new function?
…eshold outside of `charge_threshold_method()`
…_wfs_below_threshold()` for zeroing waveform entries
gonzaponte
left a comment
There was a problem hiding this comment.
I will take a deeper look, but here is a first go.
|
|
||
|
|
||
| def waveform_integrator(limits): | ||
| def integrate_wfs(wfs): | ||
| return cf.spaced_integrals(wfs, limits)[:, ::2] | ||
| return integrate_wfs | ||
|
|
||
|
|
||
| # Compound components | ||
| def compute_and_write_pmaps(detector_db, run_number, pmt_samp_wid, sipm_samp_wid, | ||
| s1_lmax, s1_lmin, s1_rebin_stride, s1_stride, s1_tmax, s1_tmin, | ||
| s2_lmax, s2_lmin, s2_rebin_stride, s2_stride, s2_tmax, s2_tmin, thr_sipm_s2, | ||
| h5out, sipm_rwf_to_cal=None): | ||
| h5out, apply_cut, sipm_rwf_to_cal=None): |
There was a problem hiding this comment.
I think we need a better name for apply_cut. Think of something along the lines of "sipm selection algorithm"
There was a problem hiding this comment.
I agree, I'll do sipm_selection_algo, but I will change it last so there's no back and forth
| if apply_cut is not None: | ||
| # apply cut before slicing and rebinning | ||
| (sipm_ids, | ||
| sipm_wfs) = apply_cut(sipm_wfs) | ||
| else: | ||
| # give all sipm ids as index if no cut is applied | ||
| sipm_ids = np.arange(sipm_wfs.shape[0]) | ||
|
|
There was a problem hiding this comment.
What does apply_cut = None represent? I thought there were only 2 options: threshold or pyrrha
There was a problem hiding this comment.
I think this is a leftover from when we were first making the implementation, it doesn't make sense anymore I agree
There was a problem hiding this comment.
However in the functions prior to this one, apply_cut is set as None in the parameters, see:
b268b32#diff-da42ca1aac462421c8d637d77af246ebd19d1c5c9d1764c251b771b420a1cacdR103
b268b32#diff-da42ca1aac462421c8d637d77af246ebd19d1c5c9d1764c251b771b420a1cacdR130
b268b32#diff-da42ca1aac462421c8d637d77af246ebd19d1c5c9d1764c251b771b420a1cacdR154
Should we leave this as is?
There was a problem hiding this comment.
Okay I removed the else clause and some tests for the peak_functions.py use to the None case for the apply_cut parameter, so now without the else fallback they fail
FAILED invisible_cities/reco/peak_functions_test.py::test_build_peak_development[S2-40-True] - TypeError: 'NoneType' object is not callable
FAILED invisible_cities/reco/peak_functions_test.py::test_find_peaks_s2_style - TypeError: 'NoneType' object is not callable
FAILED invisible_cities/reco/peak_functions_test.py::test_get_pmap - TypeError: 'NoneType' object is not callable
Should we redo the functions that are using apply_cut = None so that there is some parameter passes through?
There was a problem hiding this comment.
I think there are only two or three options, but correct me if I'm wrong:
Nonemeant "no selection", so everything is kept. You would need to add this option toCutAlgoNonemeant "threshold selection". In this case you need to adapt the tests by explicitly sending the original default threshold parameters.Nonemeant "threshold selection but with some (non-defaul) specific value of the parameters (e.g. thr=0). You would need to tweak the tests to specify these values as well.
There was a problem hiding this comment.
None in this case means no cut is applied at all, I think the best option would be to implement this explicitly as an option.
I noticed that in the old version of the tests that failed above they set thr_sipm_s2 = -1, this is essentially equivalent to setting no threshold the way I see it.
If you agree I'll make an option for no threshold and that should fix things.
There was a problem hiding this comment.
Done. What do you think?
Also should I make these functions have CutAlgo.no_cut as the default option?
b268b32#diff-da42ca1aac462421c8d637d77af246ebd19d1c5c9d1764c251b771b420a1cacdR103
b268b32#diff-da42ca1aac462421c8d637d77af246ebd19d1c5c9d1764c251b771b420a1cacdR130
b268b32#diff-da42ca1aac462421c8d637d77af246ebd19d1c5c9d1764c251b771b420a1cacdR154
gonzaponte
left a comment
There was a problem hiding this comment.
A few more comments. I still need to check a few things, but at least you can make some progress while I do that.
| def apply_cutting_function(wfs): | ||
| return func(wfs) | ||
|
|
||
| return apply_cutting_function |
There was a problem hiding this comment.
this is the same as return func. you can also use the return statement directly in the if/elif branches.
| # assume that if the detector_db is None, you return sipm threshold as the number provided | ||
| if detector_db is None: | ||
| sipm_thr = thr_sipm | ||
| else: | ||
| # extract sipm threshold | ||
| sipm_thr = get_actual_sipm_thr(thr_sipm_type, thr_sipm, detector_db, run_number) |
There was a problem hiding this comment.
If you add comments, make them say something that is not obvious from the code. In this case, since it is more a description of the function overall (rather than an implementation detail or trick), I would use the docstring to explain what the function does.
| ------- | ||
| 2D array of shape (n_sipms, n_time_bins) containing the input waveforms with entries below threshold set to zero. | ||
| """ | ||
| thr = to_col_vector(np.full(wfs.shape[0], zeroing_thr)) |
There was a problem hiding this comment.
| thr = to_col_vector(np.full(wfs.shape[0], zeroing_thr)) | |
| thr = np.full((wfs.shape[0], 1), zeroing_thr) |
| # zero entries below threshold | ||
| zwfs = zero_wfs_below_threshold(wfs, zeroing_thr) | ||
|
|
||
| # returns selected ids and waveforms above integral | ||
| return select_wfs_above_time_integrated_thr(zwfs, integration_thr) |
| def apply_circular_padding(selected_ids_no_isolated : np.ndarray, | ||
| sipm_x : np.ndarray, | ||
| sipm_y : np.ndarray, | ||
| padding_radius : float) -> np.ndarray: | ||
| """ | ||
| Receives a list of SiPM IDs corresponding to the most energetic SiPMs clustered | ||
| near the event. For these SiPMs, creates circular padding of radius padding_radius, | ||
| selecting all SiPMs within that radius. Stores the union of all selected SiPMs. | ||
| Outputs the SiPM IDs which are relevant for a given event. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| selected_ids_no_isolated : Boolean array of shape (n_sipms,) corresponding to the "relevant" SiPMs. | ||
| sipm_x : 1D array of shape (n_sipms,) containing the x positions of the SiPMs. | ||
| sipm_y : 1D array of shape (n_sipms,) containing the y positions of the SiPMs. | ||
| padding_radius : Distance threshold in mm used to create circular padding around selected SiPMs. | ||
|
|
||
| Returns | ||
| ------- | ||
| sipm_ids_with_signal : Boolean array of shape (n_sipms,) where True indicates that the SiPM is selected. | ||
| """ |
There was a problem hiding this comment.
Whenever possible, try to frame the function in a more general way. Here, you use variable names and an overall function explanation that is perhaps easy to follow if you read the whole Pyrrha implementation, but not on their own. It should be possible to read and interpret this function without understanding Pyrrha as a whole.
This PR addresses the data processing issues cause by the SiPM thresholds in Irene. It retains standard Irene functionalities but also implements the new Pyrrha selection methods for SiPMs, which relies on a spatial selection rather than a charge threshold.
The user is free to decide how they would rather process the raw waveforms (using the old Irene or Pyrrha), as the config file allows for either method to be selected.