class muVETOHitlets(nVETOHitlets): __doc__ = MV_PREAMBLE + nVETOHitlets.__doc__ __version__ = '0.0.2' depends_on = 'records_mv' provides = 'hitlets_mv' data_kind = 'hitlets_mv' child_plugin = True dtype = strax.hitlet_dtype() def setup(self): self.channel_range = self.config['channel_map']['mv'] self.n_channel = (self.channel_range[1] - self.channel_range[0]) + 1 to_pe = straxen.get_to_pe(self.run_id, self.config['gain_model_mv'], self.n_channel) # Create to_pe array of size max channel: self.to_pe = np.zeros(self.channel_range[1] + 1, dtype=np.float32) self.to_pe[self.channel_range[0]:] = to_pe[:] def compute(self, records_mv, start, end): return super().compute(records_mv, start, end)
def compute(self, records_nv, start, end): hits = strax.find_hits(records_nv, min_amplitude=self.config['hit_min_amplitude_nv']) hits = remove_switched_off_channels(hits, self.to_pe) temp_hitlets = strax.create_hitlets_from_hits(hits, self.config['save_outside_hits_nv'], self.channel_range, chunk_start=start, chunk_end=end) del hits # Get hitlet data and split hitlets: temp_hitlets = strax.get_hitlets_data(temp_hitlets, records_nv, to_pe=self.to_pe) temp_hitlets = strax.split_peaks(temp_hitlets, records_nv, self.to_pe, data_type='hitlets', algorithm='local_minimum', min_height=self.config['min_split_nv'], min_ratio=self.config['min_split_ratio_nv'] ) # Compute other hitlet properties: # We have to loop here 3 times over all hitlets... strax.hitlet_properties(temp_hitlets) entropy = strax.conditional_entropy(temp_hitlets, template='flat', square_data=False) temp_hitlets['entropy'][:] = entropy # Remove data field: hitlets = np.zeros(len(temp_hitlets), dtype=strax.hitlet_dtype()) strax.copy_to_buffer(temp_hitlets, hitlets, '_copy_hitlets') return hitlets
def create_hitlets_from_hits( hits, save_outside_hits, channel_range, chunk_start=0, chunk_end=np.inf, ): """ Function which creates hitlets from a bunch of hits. :param hits: Hits found in records. :param save_outside_hits: Tuple with left and right hit extension. :param channel_range: Detectors change range from channel map. :param chunk_start: (optional) start time of a chunk. Ensures that no hitlet is earlier than this timestamp. :param chunk_end: (optional) end time of a chunk. Ensures that no hitlet ends later than this timestamp. :return: Hitlets with temporary fields (data, max_goodness_of_split...) """ # Merge concatenate overlapping within a channel. This is important # in case hits were split by record boundaries. In case we # accidentally concatenate two PMT signals we split them later again. hits = strax.concat_overlapping_hits( hits, save_outside_hits, channel_range, chunk_start, chunk_end, ) hits = strax.sort_by_time(hits) hitlets = np.zeros(len(hits), strax.hitlet_dtype()) strax.copy_to_buffer(hits, hitlets, '_refresh_hit_to_hitlets') return hitlets
def test_get_hitlets_data_without_data_field(self): hitlets_empty = np.zeros(len(self.hitlets), strax.hitlet_dtype()) strax.copy_to_buffer(self.hitlets, hitlets_empty, '_copy_hitlets_to_hitlets_without_data') hitlets = strax.get_hitlets_data(hitlets_empty, self.records, np.ones(3000)) self._test_data_is_identical(hitlets, [self.test_data_truth])
def compute(self, records_nv, start, end): # Search again for hits in records: hits = strax.find_hits( records_nv, min_amplitude=self.config['hit_min_amplitude_nv']) # Merge concatenate overlapping within a channel. This is important # in case hits were split by record boundaries. In case we # accidentally concatenate two PMT signals we split them later again. hits = strax.concat_overlapping_hits( hits, self.config['save_outside_hits_nv'], self.config['channel_map']['nveto'], start, end) hits = strax.sort_by_time(hits) # Now convert hits into temp_hitlets including the data field: if len(hits): nsamples = hits['length'].max() else: nsamples = 0 temp_hitlets = np.zeros( len(hits), strax.hitlet_with_data_dtype(n_samples=nsamples)) # Generating hitlets and copying relevant information from hits to hitlets. # These hitlets are not stored in the end since this array also contains a data # field which we will drop later. strax.refresh_hit_to_hitlets(hits, temp_hitlets) del hits # Get hitlet data and split hitlets: strax.get_hitlets_data(temp_hitlets, records_nv, to_pe=self.to_pe) temp_hitlets = strax.split_peaks( temp_hitlets, records_nv, self.to_pe, data_type='hitlets', algorithm='local_minimum', min_height=self.config['min_split_nv'], min_ratio=self.config['min_split_ratio_nv']) # Compute other hitlet properties: # We have to loop here 3 times over all hitlets... strax.hitlet_properties(temp_hitlets) entropy = strax.conditional_entropy(temp_hitlets, template='flat', square_data=False) temp_hitlets['entropy'][:] = entropy strax.compute_widths(temp_hitlets) # Remove data field: hitlets = np.zeros(len(temp_hitlets), dtype=strax.hitlet_dtype()) drop_data_field(temp_hitlets, hitlets) return hitlets
def plot_nveto( self, hitlets, pmt_size=8, pmt_distance=0.5, _hitlet_points=None, ): """ Plots the nveto pmt pattern map for the specified hitlets. Expects hitlets to be sorted in time. :param hitlets: Hitlets to be plotted if called directly. :param pmt_size: Base size of a PMT for 1 pe. :param pmt_distance: Scaling parameter for the z -> xy projection. :param _hitlet_points: holoviews.Points created by the event display. Only internal use. :returns: stacked hv.Points plot. """ if not _hitlet_points: _hitlet_points = self.hitlets_to_hv_points(hitlets, ) pmts = self._plot_nveto(_hitlet_points.data, pmt_size=pmt_size, pmt_distance=pmt_distance) # Plot channels which have not seen anything: pmts_data = pmts.data m = pmts_data.loc[:, 'area'].values == 0 blanko_pmts = np.zeros(np.sum(m), dtype=strax.hitlet_dtype()) blanko_pmts['area'] = 1 blanko_pmts['channel'] = pmts_data.loc[m, 'channel'] blanko = self._plot_nveto(blanko_pmts, pmt_size=pmt_size, pmt_distance=pmt_distance) blanko = blanko.opts(fill_color='white', tools=[HoverTool(tooltips=[('PMT', '@channel')])], color=None) detector_layout = (plot_tpc_circle(straxen.cryostat_outer_radius) * plot_diffuser_balls_nv() * plot_nveto_reflector()) return detector_layout * blanko * pmts
def test_ext_timings_nv(records): """ Little test for nVetoExtTimings. """ if not straxen.utilix_is_configured(): return # several fake records do not have any pulse length # and channel start at 0, convert to nv: records['pulse_length'] = records['length'] records['channel'] += 2000 st = straxen.contexts.xenonnt_led() plugin = st.get_single_plugin('1', 'ext_timings_nv') hits = strax.find_hits(records, min_amplitude=1) hitlets = np.zeros(len(hits), strax.hitlet_dtype()) strax.copy_to_buffer(hits, hitlets, '_refresh_hit_to_hitlets') result = plugin.compute(hitlets, records) assert len(result) == len(hits) assert np.all(result['time'] == hits['time']) assert np.all(result['pulse_i'] == hits['record_i']) true_dt = hits['time'] - records[hits['record_i']]['time'] assert np.all(result['delta_time'] == true_dt)
class muVETOHitlets(nVETOHitlets): __doc__ = MV_PREAMBLE + nVETOHitlets.__doc__ __version__ = '0.0.2' depends_on = 'records_mv' provides = 'hitlets_mv' data_kind = 'hitlets_mv' child_plugin = True dtype = strax.hitlet_dtype() def setup(self): self.channel_range = self.config['channel_map']['mv'] self.n_channel = (self.channel_range[1] - self.channel_range[0]) + 1 to_pe = straxen.get_correction_from_cmt( self.run_id, self.config['gain_model_mv']) # Create to_pe array of size max channel: self.to_pe = np.zeros(self.channel_range[1] + 1, dtype=np.float32) self.to_pe[self.channel_range[0]:] = to_pe[:] # Check config of `hit_min_amplitude_mv` and define hit thresholds # if cmt config if is_cmt_option(self.config['hit_min_amplitude_mv']): self.hit_thresholds = straxen.get_correction_from_cmt(self.run_id, self.config[ 'hit_min_amplitude_mv']) # if hitfinder_thresholds config elif isinstance(self.config['hit_min_amplitude_mv'], str): self.hit_thresholds = straxen.hit_min_amplitude( self.config['hit_min_amplitude_mv']) else: # int or array self.hit_thresholds = self.config['hit_min_amplitude_mv'] def compute(self, records_mv, start, end): return super().compute(records_mv, start, end)
class nVETOHitlets(strax.Plugin): """ Plugin which computes the nveto hitlets and their parameters. Hitlets are an extension of regular hits. They include the left and right extension. The plugin does the following: 1. Generate hitlets which includes these sub-steps: * Apply left and right hit extension and concatenate overlapping hits. * Generate temp. hitelts and look for their waveforms in their corresponding records. * Split hitlets if they satisfy the set criteria. 2. Compute the properties of the hitlets. Note: Hitlets are getting chopped if extended in not recorded regions. """ __version__ = '0.1.1' parallel = 'process' rechunk_on_save = True compressor = 'zstd' depends_on = 'records_nv' provides = 'hitlets_nv' data_kind = 'hitlets_nv' dtype = strax.hitlet_dtype() def setup(self): self.channel_range = self.config['channel_map']['nveto'] self.n_channel = (self.channel_range[1] - self.channel_range[0]) + 1 to_pe = straxen.get_correction_from_cmt(self.run_id, self.config['gain_model_nv']) # Create to_pe array of size max channel: self.to_pe = np.zeros(self.channel_range[1] + 1, dtype=np.float32) self.to_pe[self.channel_range[0]:] = to_pe[:] # Check config of `hit_min_amplitude_nv` and define hit thresholds # if cmt config if is_cmt_option(self.config['hit_min_amplitude_nv']): self.hit_thresholds = straxen.get_correction_from_cmt( self.run_id, self.config['hit_min_amplitude_nv']) # if hitfinder_thresholds config elif isinstance(self.config['hit_min_amplitude_nv'], str): self.hit_thresholds = straxen.hit_min_amplitude( self.config['hit_min_amplitude_nv']) else: # int or array self.hit_thresholds = self.config['hit_min_amplitude_nv'] def compute(self, records_nv, start, end): hits = strax.find_hits(records_nv, min_amplitude=self.hit_thresholds) hits = remove_switched_off_channels(hits, self.to_pe) temp_hitlets = strax.create_hitlets_from_hits( hits, self.config['save_outside_hits_nv'], self.channel_range, chunk_start=start, chunk_end=end) del hits # Get hitlet data and split hitlets: temp_hitlets = strax.get_hitlets_data(temp_hitlets, records_nv, to_pe=self.to_pe, min_hitlet_sample=600) temp_hitlets = strax.split_peaks( temp_hitlets, None, # Only needed for peak splitting records_nv, None, # Only needed for peak splitting self.to_pe, data_type='hitlets', algorithm='local_minimum', min_height=self.config['min_split_nv'], min_ratio=self.config['min_split_ratio_nv']) # Compute other hitlet properties: # We have to loop here 3 times over all hitlets... strax.hitlet_properties(temp_hitlets) entropy = strax.conditional_entropy(temp_hitlets, template='flat', square_data=False) temp_hitlets['entropy'][:] = entropy # Remove data field: hitlets = np.zeros(len(temp_hitlets), dtype=strax.hitlet_dtype()) strax.copy_to_buffer(temp_hitlets, hitlets, '_copy_hitlets') return hitlets
class nVETOHitlets(strax.Plugin): """ Plugin which computes the nveto hitlets and their parameters. Hitlets are an extension of regular hits. They include the left and right extension. The plugin does the following: 1. Generate hitlets which includes these sub-steps: * Apply left and right hit extension and concatenate overlapping hits. * Generate temp. hitelts and look for their waveforms in their corresponding records. * Split hitlets if they satisfy the set criteria. 2. Compute the properties of the hitlets. Note: Hitlets are getting chopped if extended in not recorded regions. """ __version__ = '0.0.7' parallel = 'process' rechunk_on_save = True compressor = 'zstd' depends_on = 'records_nv' provides = 'hitlets_nv' data_kind = 'hitlets_nv' ends_with = '_nv' dtype = strax.hitlet_dtype() def setup(self): self.channel_range = self.config['channel_map']['nveto'] self.n_channel = (self.channel_range[1] - self.channel_range[0]) + 1 to_pe = straxen.get_to_pe(self.run_id, self.config['gain_model_nv'], self.n_channel) # Create to_pe array of size max channel: self.to_pe = np.zeros(self.channel_range[1] + 1, dtype=np.float32) self.to_pe[self.channel_range[0]:] = to_pe[:] def compute(self, records_nv, start, end): # Search again for hits in records: hits = strax.find_hits(records_nv, min_amplitude=self.config['hit_min_amplitude_nv']) # Merge concatenate overlapping within a channel. This is important # in case hits were split by record boundaries. In case we # accidentally concatenate two PMT signals we split them later again. hits = strax.concat_overlapping_hits(hits, self.config['save_outside_hits_nv'], self.channel_range, start, end) hits = strax.sort_by_time(hits) # Now convert hits into temp_hitlets including the data field: nsamples = 200 if len(hits): nsamples = max(hits['length'].max(), nsamples) temp_hitlets = np.zeros(len(hits), strax.hitlet_with_data_dtype(n_samples=nsamples)) # Generating hitlets and copying relevant information from hits to hitlets. # These hitlets are not stored in the end since this array also contains a data # field which we will drop later. strax.refresh_hit_to_hitlets(hits, temp_hitlets) del hits # Get hitlet data and split hitlets: strax.get_hitlets_data(temp_hitlets, records_nv, to_pe=self.to_pe) temp_hitlets = strax.split_peaks(temp_hitlets, records_nv, self.to_pe, data_type='hitlets', algorithm='local_minimum', min_height=self.config['min_split_nv'], min_ratio=self.config['min_split_ratio_nv'] ) # Compute other hitlet properties: # We have to loop here 3 times over all hitlets... strax.hitlet_properties(temp_hitlets) entropy = strax.conditional_entropy(temp_hitlets, template='flat', square_data=False) temp_hitlets['entropy'][:] = entropy # Remove data field: hitlets = np.zeros(len(temp_hitlets), dtype=strax.hitlet_dtype()) strax.copy_to_buffer(temp_hitlets, hitlets, '_copy_hitlets') return hitlets
edt = time + length * dt m |= (int_ch['time'] <= edt) & (edt < endtime) if np.any(m): # Found an overlap: return False else: return True @settings(suppress_health_check=[ hypothesis.HealthCheck.large_base_example, hypothesis.HealthCheck.too_slow ], deadline=None) @given( create_disjoint_intervals( strax.hitlet_dtype(), n_intervals=50, dt=1, time_range=(0, 1000), channel_range=(2000, 2119), length_range=(20, 80), ), hst.integers(1, 3), ) def test_nveto_event_building(hitlets, coincidence): """ In this test we test the code of straxen.plugins.veto_evnets.find_veto_events """ hitlets = strax.sort_by_time(hitlets)
class PeakSplitter: """Split peaks into more peaks based on arbitrary algorithm. :param peaks: Original peaks. Sum waveform must have been built and properties must have been computed (if you use them). :param records: Records from which peaks were built. :param to_pe: ADC to PE conversion factor array (of n_channels). :param data_type: 'peaks' or 'hitlets'. Specifies whether to use sum_waveform or get_hitlets_data to compute the waveform of the new split peaks/hitlets. :param next_ri: Index of next record for current record record_i. None if not needed. :param do_iterations: maximum number of times peaks are recursively split. :param min_area: Minimum area to do split. Smaller peaks are not split. The function find_split_points(), implemented in each subclass defines the algorithm, which takes in a peak's waveform and returns the index to split the peak at, if a split point is found. Otherwise NO_MORE_SPLITS is returned and the peak is left as is. """ find_split_args_defaults: tuple def __call__(self, peaks, records, to_pe, data_type, next_ri=None, do_iterations=1, min_area=0, **kwargs): if not len(records) or not len(peaks) or not do_iterations: return peaks # Build the *args tuple for self.find_split_points from kwargs # since numba doesn't support **kwargs args_options = [] for i, (k, value) in enumerate(self.find_split_args_defaults): if k in kwargs: value = kwargs[k] if k == 'threshold': # The 'threshold' option is a user-specified function value = value(peaks) args_options.append(value) args_options = tuple(args_options) # Check for spurious options argnames = [k for k, _ in self.find_split_args_defaults] for k in kwargs: if k not in argnames: raise TypeError(f"Unknown argument {k} for {self.__class__}") is_split = np.zeros(len(peaks), dtype=np.bool_) split_function = {'peaks': self._split_peaks, 'hitlets': self._split_hitlets} if data_type not in split_function: raise ValueError(f'Data_type "{data_type}" is not supported.') new_peaks = split_function[data_type]( # Numba doesn't like self as argument, but it's ok with functions... split_finder=self.find_split_points, peaks=peaks, is_split=is_split, orig_dt=records[0]['dt'], min_area=min_area, args_options=tuple(args_options), result_dtype=peaks.dtype) if is_split.sum() != 0: # Found new peaks: compute basic properties if data_type == 'peaks': strax.sum_waveform(new_peaks, records, to_pe) elif data_type == 'hitlets': # Add record fields here strax.update_new_hitlets(new_peaks, records, next_ri, to_pe) strax.compute_widths(new_peaks) # ... and recurse (if needed) new_peaks = self(new_peaks, records, to_pe, data_type, next_ri, do_iterations=do_iterations - 1, min_area=min_area, **kwargs) peaks = strax.sort_by_time(np.concatenate([peaks[~is_split], new_peaks])) return peaks @staticmethod @strax.growing_result(dtype=strax.peak_dtype(), chunk_size=int(1e4)) @numba.jit(nopython=True, nogil=True) def _split_peaks(split_finder, peaks, orig_dt, is_split, min_area, args_options, _result_buffer=None, result_dtype=None): """Loop over peaks, pass waveforms to algorithm, construct new peaks if and where a split occurs. """ # NB: code very similar to _split_hitlets see # github.com/AxFoundation/strax/pull/309 for more info. Keep in mind # that changing one function should also be reflected in the other. new_peaks = _result_buffer offset = 0 for p_i, p in enumerate(peaks): if p['area'] < min_area: continue prev_split_i = 0 w = p['data'][:p['length']] for split_i, bonus_output in split_finder( w, p['dt'], p_i, *args_options): if split_i == NO_MORE_SPLITS: p['max_goodness_of_split'] = bonus_output # although the iteration will end anyway afterwards: continue is_split[p_i] = True r = new_peaks[offset] r['time'] = p['time'] + prev_split_i * p['dt'] r['channel'] = p['channel'] # Set the dt to the original (lowest) dt first; # this may change when the sum waveform of the new peak # is computed r['dt'] = orig_dt r['length'] = (split_i - prev_split_i) * p['dt'] / orig_dt r['max_gap'] = -1 # Too lazy to compute this if r['length'] <= 0: print(p['data']) print(prev_split_i, split_i) raise ValueError("Attempt to create invalid peak!") offset += 1 if offset == len(new_peaks): yield offset offset = 0 prev_split_i = split_i yield offset @staticmethod @strax.growing_result(dtype=strax.hitlet_dtype(), chunk_size=int(1e4)) @numba.jit(nopython=True, nogil=True) def _split_hitlets(split_finder, peaks, orig_dt, is_split, min_area, args_options, _result_buffer=None, result_dtype=None): """Loop over hits, pass waveforms to algorithm, construct new hits if and where a split occurs. """ # TODO NEEDS TESTS! # NB: code very similar to _split_peaks see # github.com/AxFoundation/strax/pull/309 for more info. Keep in mind # that changing one function should also be reflected in the other. new_hits = _result_buffer offset = 0 for h_i, h in enumerate(peaks): if h['area'] < min_area: continue prev_split_i = 0 w = h['data'][:h['length']] for split_i, bonus_output in split_finder( w, h['dt'], h_i, *args_options): if split_i == NO_MORE_SPLITS: continue is_split[h_i] = True r = new_hits[offset] r['time'] = h['time'] + prev_split_i * h['dt'] r['channel'] = h['channel'] # Hitlet specific r['record_i'] = h['record_i'] # Set the dt to the original (lowest) dt first; # this may change when the sum waveform of the new peak # is computed r['dt'] = orig_dt r['length'] = (split_i - prev_split_i) * h['dt'] / orig_dt if r['length'] <= 0: print(h['data']) print(prev_split_i, split_i) raise ValueError("Attempt to create invalid hitlet!") offset += 1 if offset == len(new_hits): yield offset offset = 0 prev_split_i = split_i yield offset @staticmethod def find_split_points(w, dt, peak_i, *args_options): """This function is overwritten by LocalMinimumSplitter or LocalMinimumSplitter bare PeakSplitter class is not implemented""" raise NotImplementedError
endtime = strax.endtime(int_ch) m = (int_ch['time'] <= time) & (time < endtime) edt = time + length * dt m |= (int_ch['time'] <= edt) & (edt < endtime) if np.any(m): # Found an overlap: return False else: return True @settings(suppress_health_check=[hypothesis.HealthCheck.large_base_example, hypothesis.HealthCheck.too_slow], deadline=None) @given(create_disjoint_intervals(strax.hitlet_dtype(), n_intervals=7, dt=1, time_range=(0, 15), channel_range=(2000, 2010), length_range=(1, 4), ), hst.integers(1, 3), ) def test_nveto_event_building(hitlets, coincidence): """ In this test we test the code of straxen.plugins.veto_evnets.find_veto_events """ hitlets = strax.sort_by_time(hitlets)