def compute(self, raw_records_aqmon): rec = strax.raw_to_records(raw_records_aqmon) strax.sort_by_time(rec) strax.zero_out_of_bounds(rec) strax.baseline(rec, baseline_samples=self.config['baseline_samples_aqmon'], flip=True) aqmon_hits = strax.find_hits( rec, min_amplitude=self.config['hit_min_amplitude_aqmon']) return aqmon_hits
def compute(self, raw_records_coin_nv): # Do not trust in DAQ + strax.baseline to leave the # out-of-bounds samples to zero. r = strax.raw_to_records(raw_records_coin_nv) del raw_records_coin_nv r = strax.sort_by_time(r) strax.zero_out_of_bounds(r) strax.baseline(r, baseline_samples=self.baseline_samples, flip=True) if self.config['min_samples_alt_baseline_nv']: m = r['pulse_length'] > self.config['min_samples_alt_baseline_nv'] if np.any(m): # Correcting baseline after PMT saturated signals r[m] = median_baseline(r[m]) strax.integrate(r) strax.zero_out_of_bounds(r) hits = strax.find_hits(r, min_amplitude=self.hit_thresholds) le, re = self.config['save_outside_hits_nv'] r = strax.cut_outside_hits(r, hits, left_extension=le, right_extension=re) strax.zero_out_of_bounds(r) return r
def plot_peaks(peaks, seconds_range, t_reference, show_largest=100, single_figure=True, figsize=(10, 4), xaxis=True): if single_figure: plt.figure(figsize=figsize) plt.axhline(0, c='k', alpha=0.2) peaks = peaks[np.argsort(-peaks['area'])[:show_largest]] peaks = strax.sort_by_time(peaks) for p in peaks: plot_peak(p, t0=t_reference, color={ 0: 'gray', 1: 'b', 2: 'g' }[p['type']]) if xaxis == 'since_start': seconds_range_xaxis(seconds_range, t0=seconds_range[0]) elif xaxis: seconds_range_xaxis(seconds_range) plt.xlim(*seconds_range) else: plt.xticks([]) plt.xlim(*seconds_range) plt.ylabel("Intensity [PE/ns]") if single_figure: plt.tight_layout()
def compute(self, records): r = records hits = strax.find_hits(r, threshold=self.config['hit_threshold']) hits = strax.sort_by_time(hits) peaks = strax.find_peaks( hits, self.config['to_pe'], result_dtype=self.dtype, gap_threshold=self.config['peak_gap_threshold'], left_extension=self.config['peak_left_extension'], right_extension=self.config['peak_right_extension'], min_channels=self.config['peak_min_chan'], min_area=self.config['peak_min_area'], max_duration=self.config['peak_max_duration']) strax.sum_waveform(peaks, r, adc_to_pe=self.config['to_pe']) peaks = peaks[peaks['dt'] > 0] # removes strange edge case peaks = strax.split_peaks(peaks, r, self.config['to_pe'], min_height=self.config['split_min_height'], min_ratio=self.config['split_min_ratio']) strax.compute_widths(peaks) return peaks
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) event_intervals = straxen.plugins.nveto_recorder.find_coincidence(hitlets, coincidence, 300) mes = 'Found overlapping events returned by "coincidence".' assert np.all(event_intervals['endtime'][:-1] - event_intervals['time'][1:] < 0), mes # Get hits which touch the event window, this can lead to ambiguities # which we will solve subsequently. hitlets_ids_in_event = strax.touching_windows(hitlets, event_intervals) # First check for empty events, since ambiguity check will merge intervals: mes = f'Found an empty event without any hitlets: {hitlets_ids_in_event}.' assert np.all(np.diff(hitlets_ids_in_event) != 0), mes # Solve ambiguities (merge overlapping intervals) interval_truth = _test_ambiguity(hitlets_ids_in_event) hitlets_ids_in_event = straxen.plugins.veto_events._solve_ambiguity(hitlets_ids_in_event) mes = f'Found ambigious event for {hitlets_ids_in_event} with turth {interval_truth}' assert np.all(hitlets_ids_in_event == interval_truth), mes # Check if events satisfy the coincidence requirement: mes = f'Found an event with less than 3 hitelts. {hitlets_ids_in_event}' assert np.all(np.diff(hitlets_ids_in_event) >= coincidence), mes
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 compute(self, raw_records_coin_nv): # Do not trust in DAQ + strax.baseline to leave the # out-of-bounds samples to zero. r = strax.raw_to_records(raw_records_coin_nv) del raw_records_coin_nv r = strax.sort_by_time(r) strax.zero_out_of_bounds(r) strax.baseline(r, baseline_samples=self.config['baseline_samples_nv'], flip=True) strax.integrate(r) strax.zero_out_of_bounds(r) hits = strax.find_hits( r, min_amplitude=self.config['hit_min_amplitude_nv']) le, re = self.config['save_outside_hits_nv'] r = strax.cut_outside_hits(r, hits, left_extension=le, right_extension=re) strax.zero_out_of_bounds(r) rlinks = strax.record_links(r) r = clean_up_empty_records(r, rlinks, only_last=True) return r
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
def final_results(self): records = self.record_buffer[:self. blevel] # No copying the records from buffer maska = records['time'] <= self.last_digitized_right * self.config[ 'sample_duration'] records = records[maska] records = strax.sort_by_time(records) # Do NOT remove this line # strax.baseline(records) Will be done w/ pulse processing strax.integrate(records) # Yield an appropriate amount of stuff from the truth buffer # and mark it as available for writing again maskb = ( self.truth_buffer['fill'] & # This condition will always be false if self.truth_buffer['t_first_photon'] == np.nan ((self.truth_buffer['t_first_photon'] <= self.last_digitized_right * self.config['sample_duration']) | # Hence, we need to use this trick to also save these cases (this # is what we set the end time to for np.nans) (np.isnan(self.truth_buffer['t_first_photon']) & (self.truth_buffer['time'] <= self.last_digitized_right * self.config['sample_duration'])))) truth = self.truth_buffer[maskb] # This is a copy, not a view! # Careful here: [maskb]['fill'] = ... does not work # numpy creates a copy of the array on the first index. # The assignment then goes to the (unused) copy. # ['fill'][maskb] leads to a view first, then the advanced # assignment works into the original array as expected. self.truth_buffer['fill'][maskb] = False truth.sort(order='time') # Return truth without 'fill' field _truth = np.zeros(len(truth), dtype=instruction_dtype + truth_extra_dtype) for name in _truth.dtype.names: _truth[name] = truth[name] _truth['time'][~np.isnan(_truth['t_first_photon'])] = \ _truth['t_first_photon'][~np.isnan(_truth['t_first_photon'])].astype(int) _truth.sort(order='time') if self.config['detector'] == 'XENON1T': yield dict(raw_records=records, truth=_truth) else: yield dict( raw_records=records[records['channel'] < self. config['channels_top_high_energy'][0]], raw_records_he=records[ (records['channel'] >= self.config['channels_top_high_energy'][0]) & (records['channel'] <= self.config['channels_top_high_energy'][-1])], raw_records_aqmon=records[records['channel'] == 800], truth=_truth) self.record_buffer[:np.sum(~maska)] = self.record_buffer[:self.blevel][ ~maska] self.blevel = np.sum(~maska)
def finish_results(): nonlocal results records = np.concatenate(results) # In strax data, records are always stored # sorted, baselined and integrated records = strax.sort_by_time(records) print("Returning %d records" % len(records)) results = [] return records
def test_concat_overlapping_hits(hits0, hits1, le, re): # combining fake hits of the two channels: hits1['channel'] = 1 hits = np.concatenate([hits0, hits1]) if not len(hits): # In case there are no hitlets there is not much to do: concat_hits = strax.concat_overlapping_hits(hits, (le, re), (0, 1), 0, float('inf')) assert not len( concat_hits), 'Concatenated hits not empty although hits are empty' else: hits = strax.sort_by_time(hits) # Additional offset to time since le > hits['time'].min() does not # make sense: hits['time'] += 100 # Now we are ready for the tests: # Creating for each channel a dummy array. tmax = strax.endtime( hits).max() # Since dt is one this is the last sample tmax += re dummy_array = np.zeros((2, tmax), np.int64) for h in hits: # Filling samples with 1 if inside a hit: st = h['time'] - le et = strax.endtime(h) + re dummy_array[h['channel'], st:et] = 1 # Now we concatenate the hits and check whether their length matches # with the total sum of our dummy arrays. concat_hits = strax.concat_overlapping_hits(hits, (le, re), (0, 1), 0, float('inf')) assert len(concat_hits) <= len( hits), 'Somehow we have more hits than before ?!?' for ch in [0, 1]: dummy_sum = np.sum(dummy_array[ch]) # Computing total length of concatenated hits: diff = strax.endtime(concat_hits) - concat_hits['time'] m = concat_hits['channel'] == ch concat_sum = np.sum(diff[m]) assert concat_sum == dummy_sum, f'Total length of concatenated hits deviates from hits for channel {ch}' if len(concat_hits[m]) > 1: # Checking if new hits do not overlapp or touch anymore: mask = strax.endtime( concat_hits[m])[:-1] - concat_hits[m]['time'][1:] assert np.all( mask < 0 ), f'Found two hits within {ch} which are touching or overlapping'
def compute(self, raw_records_nv, start, end): strax.zero_out_of_bounds(raw_records_nv) # First we have to split rr into records and lone records: # Please note that we consider everything as a lone record which # does not satisfy the coincidence requirement intervals = coincidence(raw_records_nv, self.config['coincidence_level_recorder_nv'], self.config['resolving_time_recorder_nv']) # Always save the first and last resolving_time nanoseconds (e.g. 600 ns) since we cannot guarantee the gap # size to be larger. (We cannot use an OverlapingWindow plugin either since it requires disjoint objects.) if len(intervals): intervals_with_bounds = np.zeros((len(intervals) + 2, 2), dtype=np.int64) intervals_with_bounds[1:-1, :] = intervals intervals_with_bounds[0, :] = start, min( start + self.config['resolving_time_recorder_nv'], intervals[0, 0]) intervals_with_bounds[-1, :] = max( end - self.config['resolving_time_recorder_nv'], intervals[-1, 1]), end del intervals else: intervals_with_bounds = np.zeros((0, 2), dtype=np.int64) neighbors = strax.record_links(raw_records_nv) mask = pulse_in_interval(raw_records_nv, neighbors, *np.transpose(intervals_with_bounds)) rr, lone_records = straxen.mask_and_not(raw_records_nv, mask) # Compute some properties of the lone_records: # We compute only for lone_records baseline etc. since # raw_records_nv will be deleted, otherwise we could not change # the settings and reprocess the data in case of raw_records_nv lr = strax.raw_to_records(lone_records) del lone_records lr = strax.sort_by_time(lr) strax.zero_out_of_bounds(lr) strax.baseline( lr, baseline_samples=self.config['nbaseline_samples_lone_records_nv'], flip=True) strax.integrate(lr) lrs, lr = compute_lone_records(lr, self.config['channel_map']['nveto'], self.config['n_lone_records_nv']) lrs['time'] = start lrs['endtime'] = end return { 'raw_records_coin_nv': rr, 'lone_raw_records_nv': lr, 'lone_raw_record_statistics_nv': lrs }
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 compute(self, even_recs, odd_recs): n_even = len(even_recs) n_odd = len(odd_recs) res = np.zeros(n_even + n_odd, self.dtype) res['dt'][:n_even] = even_recs['dt'] res['time'][:n_even] = even_recs['time'] res['length'][:n_even] = even_recs['length'] res['channel'][:n_even] = even_recs['channel'] res['dt'][n_even:] = odd_recs['dt'] res['time'][n_even:] = odd_recs['time'] res['length'][n_even:] = odd_recs['length'] res['channel'][n_even:] = odd_recs['channel'] return strax.sort_by_time(res)
def final_results(self, record_j): records = self.record_buffer[:record_j] # Copy the records from buffer records = strax.sort_by_time( records) # Must keep this for sorted output strax.baseline(records) strax.integrate(records) _truth = self.truth_buffer[self.truth_buffer['fill']] # Return truth without 'fill' field truth = np.zeros(len(_truth), dtype=instruction_dtype + truth_extra_dtype) for name in truth.dtype.names: truth[name] = _truth[name] return dict(raw_records=records, truth=truth)
def compute(self, peaklets, lone_hits): if self.config['merge_without_s1']: peaklets = peaklets[peaklets['type'] != 1] if len(peaklets) <= 1: return np.zeros(0, dtype=peaklets.dtype) gap_thresholds = self.config['s2_merge_gap_thresholds'] max_gap = gap_thresholds[0][1] max_area = 10**gap_thresholds[-1][0] if max_gap < 0: # Do not merge at all return np.zeros(0, dtype=peaklets.dtype) else: # Max gap and area should be set by the gap thresholds # to avoid contradictions start_merge_at, end_merge_at = self.get_merge_instructions( peaklets['time'], strax.endtime(peaklets), areas=peaklets['area'], types=peaklets['type'], gap_thresholds=gap_thresholds, max_duration=self.config['s2_merge_max_duration'], max_gap=max_gap, max_area=max_area, ) merged_s2s = strax.merge_peaks( peaklets, start_merge_at, end_merge_at, max_buffer=int(self.config['s2_merge_max_duration'] // np.gcd.reduce(peaklets['dt'])), ) merged_s2s['type'] = 2 # Updated time and length of lone_hits and sort again: lh = np.copy(lone_hits) del lone_hits lh_time_shift = (lh['left'] - lh['left_integration']) * lh['dt'] lh['time'] = lh['time'] - lh_time_shift lh['length'] = (lh['right_integration'] - lh['left_integration']) lh = strax.sort_by_time(lh) strax.add_lone_hits(merged_s2s, lh, self.to_pe) strax.compute_widths(merged_s2s) return merged_s2s
def compute(self, records, peaks): r = records p = peaks hits = strax.find_hits(r) hits = strax.sort_by_time(hits) hit_mean_times = hits['time'] + (hits['length'] / 2.0 ) # hit "mean" time peak_mean_times = p['time'] + (p['length'] / 2.0) # peak "mean" time tight_coin = self.get_tight_coin( hit_mean_times, peak_mean_times, self.config['tight_coincidence_window_left'], self.config['tight_coincidence_window_right']) return dict(tight_coincidence=tight_coin)
def _load_chunk(self, path, kind='central'): records = [ strax.load_file(fn, compressor='blosc', dtype=strax.record_dtype()) for fn in glob.glob(f'{path}/reader_*') ] records = np.concatenate(records) records = strax.sort_by_time(records) if kind == 'central': return records result = strax.from_break( records, safe_break=int(1e3), # TODO config? left=kind == 'post', tolerant=True) if self.config['erase']: shutil.rmtree(path) return result
def _load_chunk(self, path, kind='central'): records = [ strax.load_file(fn, compressor='blosc', dtype=strax.record_dtype()) for fn in sorted(glob.glob(f'{path}/*')) ] records = np.concatenate(records) records = strax.sort_by_time(records) if kind == 'central': result = records else: result = strax.from_break( records, safe_break=self.config['safe_break_in_pulses'], left=kind == 'post', tolerant=True) if self.config['erase']: shutil.rmtree(path) return result
def split_peaks(peaks, records, to_pe, min_height=25, min_ratio=4): """Return peaks after splitting at prominent sum waveform minima 'Prominent' means: on either side of a split point, local maxima are: - larger than minimum + min_height - larger than minimum * min_ratio (this is related to topographical prominence for mountains) Min_height is in pe/ns (NOT pe/bin!) """ is_split = np.zeros(len(peaks), dtype=np.bool_) new_peaks = _split_peaks(peaks, min_height=min_height, min_ratio=min_ratio, orig_dt=records[0]['dt'], is_split=is_split, result_dtype=peaks.dtype) strax.sum_waveform(new_peaks, records, to_pe) return strax.sort_by_time(np.concatenate([peaks[~is_split], new_peaks]))
def compute(self, records): r = records hits = strax.find_hits(r) # TODO: Duplicate work hits = strax.sort_by_time(hits) peaks = strax.find_peaks(hits, to_pe, result_dtype=self.dtype) strax.sum_waveform(peaks, r, to_pe) peaks = strax.split_peaks(peaks, r, to_pe) strax.compute_widths(peaks) if self.config['diagnose_sorting']: assert np.diff(r['time']).min() >= 0, "Records not sorted" assert np.diff(hits['time']).min() >= 0, "Hits not sorted" assert np.all(peaks['time'][1:] >= strax.endtime(peaks)[:-1] ), "Peaks not disjoint" return peaks
def test_sum_waveform(records): # Make a single big peak to contain all the records n_ch = 100 rlinks = strax.record_links(records) hits = strax.find_hits(records, np.ones(n_ch)) hits['left_integration'] = hits['left'] hits['right_integration'] = hits['right'] hits = strax.sort_by_time(hits) peaks = strax.find_peaks(hits, np.ones(n_ch), gap_threshold=6, left_extension=2, right_extension=3, min_area=0, min_channels=1, max_duration=10_000_000) strax.sum_waveform(peaks, hits, records, rlinks, np.ones(n_ch)) for p in peaks: # Area measures must be consistent area = p['area'] assert area >= 0 assert p['data'].sum() == area assert p['area_per_channel'].sum() == area sum_wv = np.zeros(p['length'], dtype=np.float32) for r in records: (rs, re), (ps, pe) = strax.overlap_indices(r['time'], r['length'], p['time'], p['length']) sum_wv[ps:pe] += r['data'][rs:re] assert np.all(p['data'][:p['length']] == sum_wv) # Finally check that we also can use a selection of peaks to sum strax.sum_waveform(peaks, hits, records, rlinks, np.ones(n_ch), select_peaks_indices=np.array([0]))
def load_chunk(self, folder, kind='central'): records = np.concatenate([ strax.load_file(os.path.join(folder, f), compressor='blosc', dtype=strax.record_dtype()) for f in os.listdir(folder) ]) records = strax.sort_by_time(records) if kind == 'central': result = records else: if self.config['do_breaks']: result = strax.from_break(records, safe_break=self.config['safe_break'], left=kind == 'post', tolerant=True) else: result = records result['time'] += self.config['run_start'] return result
def compute(self, raw_records_aqmon): not_allowed_channels = (set(np.unique(raw_records_aqmon['channel'])) - set(self.aqmon_channels)) if not_allowed_channels: raise ValueError( f'Unknown channel {not_allowed_channels}. Only know {self.aqmon_channels}' ) if self.check_raw_record_aqmon_overlaps: straxen.check_overlaps(raw_records_aqmon, n_channels=max(AqmonChannels).value + 1) records = strax.raw_to_records(raw_records_aqmon) strax.zero_out_of_bounds(records) strax.baseline(records, baseline_samples=self.baseline_samples_aqmon, flip=True) aqmon_hits = self.find_aqmon_hits_per_channel(records) aqmon_hits = strax.sort_by_time(aqmon_hits) return aqmon_hits
def compute(self, peaklets, merged_s2s): # Remove fake merged S2s from dirty hack, see above merged_s2s = merged_s2s[merged_s2s['type'] != FAKE_MERGED_S2_TYPE] if self.config['merge_without_s1']: is_s1 = peaklets['type'] == 1 peaks = strax.replace_merged(peaklets[~is_s1], merged_s2s) peaks = strax.sort_by_time(np.concatenate([peaklets[is_s1], peaks])) else: peaks = strax.replace_merged(peaklets, merged_s2s) if self.config['diagnose_sorting']: assert np.all(np.diff(peaks['time']) >= 0), "Peaks not sorted" if self.config['merge_without_s1']: to_check = peaks['type'] != 1 else: to_check = peaks['type'] != FAKE_MERGED_S2_TYPE assert np.all(peaks['time'][to_check][1:] >= strax.endtime(peaks) [to_check][:-1]), "Peaks not disjoint" return peaks
def compute(self, records): r = records hits = strax.find_hits(r) # Remove hits in zero-gain channels # they should not affect the clustering! hits = hits[self.to_pe[hits['channel']] != 0] hits = strax.sort_by_time(hits) peaks = strax.find_peaks( hits, self.to_pe, gap_threshold=self.config['peak_gap_threshold'], left_extension=self.config['peak_left_extension'], right_extension=self.config['peak_right_extension'], min_channels=self.config['peak_min_pmts'], result_dtype=self.dtype) strax.sum_waveform(peaks, r, self.to_pe) peaks = strax.split_peaks( peaks, r, self.to_pe, min_height=self.config['peak_split_min_height'], min_ratio=self.config['peak_split_min_ratio']) strax.compute_widths(peaks) if self.config['diagnose_sorting']: assert np.diff(r['time']).min() >= 0, "Records not sorted" assert np.diff(hits['time']).min() >= 0, "Hits not sorted" assert np.all(peaks['time'][1:] >= strax.endtime(peaks)[:-1] ), "Peaks not disjoint" return peaks
def compute(self, raw_records_coin_nv): # Do not trust in DAQ + strax.baseline to leave the # out-of-bounds samples to zero. r = strax.raw_to_records(raw_records_coin_nv) del raw_records_coin_nv r = strax.sort_by_time(r) strax.zero_out_of_bounds(r) strax.baseline(r, baseline_samples=self.baseline_samples, flip=True) strax.integrate(r) strax.zero_out_of_bounds(r) hits = strax.find_hits(r, min_amplitude=self.hit_thresholds) le, re = self.config['save_outside_hits_nv'] r = strax.cut_outside_hits(r, hits, left_extension=le, right_extension=re) strax.zero_out_of_bounds(r) return r
def compute(self, records, start, end): r = records hits = strax.find_hits(r, min_amplitude=self.hit_thresholds) # Remove hits in zero-gain channels # they should not affect the clustering! hits = hits[self.to_pe[hits['channel']] != 0] hits = strax.sort_by_time(hits) # Use peaklet gap threshold for initial clustering # based on gaps between hits peaklets = strax.find_peaks( hits, self.to_pe, gap_threshold=self.config['peaklet_gap_threshold'], left_extension=self.config['peak_left_extension'], right_extension=self.config['peak_right_extension'], min_channels=self.config['peak_min_pmts'], result_dtype=self.dtype_for('peaklets'), max_duration=self.config['peaklet_max_duration'], ) # Make sure peaklets don't extend out of the chunk boundary # This should be very rare in normal data due to the ADC pretrigger # window. self.clip_peaklet_times(peaklets, start, end) # Get hits outside peaklets, and store them separately. # fully_contained is OK provided gap_threshold > extension, # which is asserted inside strax.find_peaks. is_lone_hit = strax.fully_contained_in(hits, peaklets) == -1 lone_hits = hits[is_lone_hit] strax.integrate_lone_hits( lone_hits, records, peaklets, save_outside_hits=(self.config['peak_left_extension'], self.config['peak_right_extension']), n_channels=len(self.to_pe)) # Compute basic peak properties -- needed before natural breaks hits = hits[~is_lone_hit] # Define regions outside of peaks such that _find_hit_integration_bounds # is not extended beyond a peak. outside_peaks = self.create_outside_peaks_region(peaklets, start, end) strax.find_hit_integration_bounds( hits, outside_peaks, records, save_outside_hits=(self.config['peak_left_extension'], self.config['peak_right_extension']), n_channels=len(self.to_pe), allow_bounds_beyond_records=True, ) # Transform hits to hitlets for naming conventions. A hit refers # to the central part above threshold a hitlet to the entire signal # including the left and right extension. # (We are not going to use the actual hitlet data_type here.) hitlets = hits del hits hitlet_time_shift = (hitlets['left'] - hitlets['left_integration']) * hitlets['dt'] hitlets['time'] = hitlets['time'] - hitlet_time_shift hitlets['length'] = (hitlets['right_integration'] - hitlets['left_integration']) hitlets = strax.sort_by_time(hitlets) rlinks = strax.record_links(records) strax.sum_waveform(peaklets, hitlets, r, rlinks, self.to_pe) strax.compute_widths(peaklets) # Split peaks using low-split natural breaks; # see https://github.com/XENONnT/straxen/pull/45 # and https://github.com/AxFoundation/strax/pull/225 peaklets = strax.split_peaks( peaklets, hitlets, r, rlinks, self.to_pe, algorithm='natural_breaks', threshold=self.natural_breaks_threshold, split_low=True, filter_wing_width=self.config['peak_split_filter_wing_width'], min_area=self.config['peak_split_min_area'], do_iterations=self.config['peak_split_iterations']) # Saturation correction using non-saturated channels # similar method used in pax # see https://github.com/XENON1T/pax/pull/712 # Cases when records is not writeable for unclear reason # only see this when loading 1T test data # more details on https://numpy.org/doc/stable/reference/generated/numpy.ndarray.flags.html if not r['data'].flags.writeable: r = r.copy() if self.config['saturation_correction_on']: peak_list = peak_saturation_correction( r, rlinks, peaklets, hitlets, self.to_pe, reference_length=self.config['saturation_reference_length'], min_reference_length=self. config['saturation_min_reference_length']) # Compute the width again for corrected peaks strax.compute_widths(peaklets, select_peaks_indices=peak_list) # Compute tight coincidence level. # Making this a separate plugin would # (a) doing hitfinding yet again (or storing hits) # (b) increase strax memory usage / max_messages, # possibly due to its currently primitive scheduling. hit_max_times = np.sort( hitlets['time'] + hitlets['dt'] * hit_max_sample(records, hitlets) + hitlet_time_shift # add time shift again to get correct maximum ) peaklet_max_times = ( peaklets['time'] + np.argmax(peaklets['data'], axis=1) * peaklets['dt']) tight_coincidence_channel = get_tight_coin( hit_max_times, hitlets['channel'], peaklet_max_times, self.config['tight_coincidence_window_left'], self.config['tight_coincidence_window_right'], self.channel_range) peaklets['tight_coincidence'] = tight_coincidence_channel if self.config['diagnose_sorting'] and len(r): assert np.diff(r['time']).min(initial=1) >= 0, "Records not sorted" assert np.diff( hitlets['time']).min(initial=1) >= 0, "Hits/Hitlets not sorted" assert np.all(peaklets['time'][1:] >= strax.endtime(peaklets)[:-1] ), "Peaks not disjoint" # Update nhits of peaklets: counts = strax.touching_windows(hitlets, peaklets) counts = np.diff(counts, axis=1).flatten() peaklets['n_hits'] = counts return dict(peaklets=peaklets, lone_hits=lone_hits)
def compute(self, records, start, end): r = records hits = strax.find_hits(r, min_amplitude=straxen.hit_min_amplitude( self.config['hit_min_amplitude'])) # Remove hits in zero-gain channels # they should not affect the clustering! hits = hits[self.to_pe[hits['channel']] != 0] hits = strax.sort_by_time(hits) # Use peaklet gap threshold for initial clustering # based on gaps between hits peaklets = strax.find_peaks( hits, self.to_pe, gap_threshold=self.config['peaklet_gap_threshold'], left_extension=self.config['peak_left_extension'], right_extension=self.config['peak_right_extension'], min_channels=self.config['peak_min_pmts'], result_dtype=self.dtype_for('peaklets')) # Make sure peaklets don't extend out of the chunk boundary # This should be very rare in normal data due to the ADC pretrigger # window. self.clip_peaklet_times(peaklets, start, end) # Get hits outside peaklets, and store them separately. # fully_contained is OK provided gap_threshold > extension, # which is asserted inside strax.find_peaks. lone_hits = hits[strax.fully_contained_in(hits, peaklets) == -1] strax.integrate_lone_hits( lone_hits, records, peaklets, save_outside_hits=(self.config['peak_left_extension'], self.config['peak_right_extension']), n_channels=len(self.to_pe)) # Compute basic peak properties -- needed before natural breaks strax.sum_waveform(peaklets, r, self.to_pe) strax.compute_widths(peaklets) # Split peaks using low-split natural breaks; # see https://github.com/XENONnT/straxen/pull/45 # and https://github.com/AxFoundation/strax/pull/225 peaklets = strax.split_peaks( peaklets, r, self.to_pe, algorithm='natural_breaks', threshold=self.natural_breaks_threshold, split_low=True, filter_wing_width=self.config['peak_split_filter_wing_width'], min_area=self.config['peak_split_min_area'], do_iterations=self.config['peak_split_iterations']) # Saturation correction using non-saturated channels # similar method used in pax # see https://github.com/XENON1T/pax/pull/712 if self.config['saturation_correction_on']: peak_saturation_correction( r, peaklets, self.to_pe, reference_length=self.config['saturation_reference_length'], min_reference_length=self. config['saturation_min_reference_length']) # Compute tight coincidence level. # Making this a separate plugin would # (a) doing hitfinding yet again (or storing hits) # (b) increase strax memory usage / max_messages, # possibly due to its currently primitive scheduling. hit_max_times = np.sort(hits['time'] + hits['dt'] * hit_max_sample(records, hits)) peaklet_max_times = ( peaklets['time'] + np.argmax(peaklets['data'], axis=1) * peaklets['dt']) peaklets['tight_coincidence'] = get_tight_coin( hit_max_times, peaklet_max_times, self.config['tight_coincidence_window_left'], self.config['tight_coincidence_window_right']) if self.config['diagnose_sorting'] and len(r): assert np.diff(r['time']).min(initial=1) >= 0, "Records not sorted" assert np.diff(hits['time']).min(initial=1) >= 0, "Hits not sorted" assert np.all(peaklets['time'][1:] >= strax.endtime(peaklets)[:-1] ), "Peaks not disjoint" # Update nhits of peaklets: counts = strax.touching_windows(hits, peaklets) counts = np.diff(counts, axis=1).flatten() counts += 1 peaklets['n_hits'] = counts return dict(peaklets=peaklets, lone_hits=lone_hits)
def compute(self, raw_records_nv, start, end): if self.config['check_raw_record_overlaps_nv']: straxen.check_overlaps(raw_records_nv, n_channels=3000) # Cover the case if we do not want to have any coincidence: if self.config['coincidence_level_recorder_nv'] <= 1: rr = raw_records_nv lr = np.zeros(0, dtype=self.dtype['lone_raw_records_nv']) lrs = np.zeros(0, dtype=self.dtype['lone_raw_record_statistics_nv']) return { 'raw_records_coin_nv': rr, 'lone_raw_records_nv': lr, 'lone_raw_record_statistics_nv': lrs } # Search for hits to define coincidence intervals: temp_records = strax.raw_to_records(raw_records_nv) temp_records = strax.sort_by_time(temp_records) strax.zero_out_of_bounds(temp_records) strax.baseline(temp_records, baseline_samples=self.baseline_samples, flip=True) hits = strax.find_hits(temp_records, min_amplitude=self.hit_thresholds) del temp_records # First we have to split rr into records and lone records: # Please note that we consider everything as a lone record which # does not satisfy the coincidence requirement intervals = find_coincidence( hits, self.config['coincidence_level_recorder_nv'], self.config['resolving_time_recorder_nv'], self.config['pre_trigger_time_nv']) del hits # Always save the first and last resolving_time nanoseconds (e.g. 600 ns) since we cannot guarantee the gap # size to be larger. (We cannot use an OverlapingWindow plugin either since it requires disjoint objects.) if len(intervals): intervals_with_bounds = np.zeros(len(intervals) + 2, dtype=strax.time_fields) intervals_with_bounds['time'][1:-1] = intervals['time'] intervals_with_bounds['endtime'][1:-1] = intervals['endtime'] intervals_with_bounds['time'][0] = start intervals_with_bounds['endtime'][0] = min( start + self.config['resolving_time_recorder_nv'], intervals['time'][0]) intervals_with_bounds['time'][-1] = max( end - self.config['resolving_time_recorder_nv'], intervals['endtime'][-1]) intervals_with_bounds['endtime'][-1] = end del intervals else: intervals_with_bounds = np.zeros((0, 2), dtype=strax.time_fields) neighbors = strax.record_links(raw_records_nv) mask = pulse_in_interval( raw_records_nv, neighbors, intervals_with_bounds['time'], intervals_with_bounds['endtime'], ) rr, lone_records = straxen.mask_and_not(raw_records_nv, mask) # Compute some properties of the lone_records: # We compute only for lone_records baseline etc. since # raw_records_nv will be deleted, otherwise we could not change # the settings and reprocess the data in case of raw_records_nv lr = strax.raw_to_records(lone_records) del lone_records lr = strax.sort_by_time(lr) strax.zero_out_of_bounds(lr) strax.baseline(lr, baseline_samples=self.baseline_samples, flip=True) strax.integrate(lr) lrs, lr = compute_lone_records(lr, self.config['channel_map']['nveto'], self.config['n_lone_records_nv']) lrs['time'] = start lrs['endtime'] = end return { 'raw_records_coin_nv': rr, 'lone_raw_records_nv': lr, 'lone_raw_record_statistics_nv': lrs }