Example #1
0
class DummyEventBasics(strax.Plugin):
    """Get evenly spaced random duration events that don't overlap"""
    n_chunks = strax.Config(default=3)
    event_time_range = strax.Config(
        default=(0, 3e6), help='Where to span the durations of chunks over')
    events_per_chunk = strax.Config(default=20)
    event_durations = strax.Config(default=(1000, 20_000),
                                   help='event durations (min, max) ns')
    depends_on = ()
    save_when = strax.SaveWhen.ALWAYS
    provides = 'event_basics'
    data_kind = 'events'
    dtype = strax.time_fields
    _events = None

    def source_finished(self):
        return True

    def is_ready(self, chunk_i):
        return chunk_i < self.n_chunks

    def get_events_this_chunk(self, chunk_i):
        if self._events is None:
            res = np.zeros(int(self.events_per_chunk * self.n_chunks),
                           dtype=self.dtype)
            times = np.linspace(*self.event_time_range, len(res))
            res['time'] = times

            endtimes = res['time'] + np.random.randint(*self.event_durations,
                                                       size=len(res))
            # Don't allow overlapping events
            endtimes[:-1] = np.clip(endtimes[:-1], 0, res['time'][1:] - 1)
            res['endtime'] = endtimes
            self._events = np.split(res, self.n_chunks)
        return self._events[chunk_i]

    def chunk_start(self, chunk_i) -> int:
        # Get the start time of the requested chunk
        assert self._events is not None, "run get_events_this_chunk first!"
        if chunk_i == 0:
            return self._events[chunk_i]['time'][0]
        return self._events[chunk_i - 1]['endtime'][-1]

    def get_chunk_end(self, chunk_i):
        assert self._events is not None, "run get_events_this_chunk first!"
        if chunk_i == self.n_chunks:
            return self.event_time_range[1]
        return self._events[chunk_i]['endtime'][-1]

    def compute(self, chunk_i):
        events = self.get_events_this_chunk(chunk_i)
        events_within = events['endtime'] < self.event_time_range[1]
        events = events[events_within]

        return self.chunk(start=self.chunk_start(chunk_i),
                          end=self.get_chunk_end(chunk_i),
                          data=events)
Example #2
0
class DummyPlugin(strax.Plugin):
    depends_on = ()
    provides = ('dummy_data', )
    dtype = strax.dtypes.time_fields + [
        (('Some data description', 'some_data_name'), np.int32),
    ]

    int_config = strax.Config(type=int, default=42)
    str_config = strax.Config(type=str, default='forty_two')
Example #3
0
class DummyDAQReader(DAQReader):
    """Dummy version of DAQReader with different provides and different lineage"""
    provides = [
        'raw_records',
        'raw_records_nv',
        'raw_records_aqmon',
    ]
    dummy_version = strax.Config(
        default=None,
        track=True,
        help=
        "Extra option to make sure that we are getting a different lineage if we want to",
    )
    data_kind = dict(zip(provides, provides))
    rechunk_on_save = False

    def _path(self, chunk_i):
        path = super()._path(chunk_i)
        path_exists = os.path.exists(path)
        print(f'looked for {chunk_i} on {path}. Is found = {path_exists}')
        return path
Example #4
0
class DummyAqmonHits(strax.Plugin):
    """
    Dummy plugin to make some aqmon hits that may span ON and OFF
    signals over chunks

    There are two channels, that signify ON or OFF of a logic signal

    We are interested in the deadtime, which is the time difference
    between ON and OFF singals. This plugin computes that deadtime, even
    if we have missed the first ON (so we start with OFF) or missed the
    last OFF (such that the last signal is an ON). If we miss one ON at
    the start or one OFF at the end, we should just consider all time
    before the first OFF or all the time after the last ON as deadtime.
    """
    vetos_per_chunk = strax.Config(
        default=list(range(1, 10)),
        help='The number of ON/OFF signals per chunk, preferably a '
        'combination of odd and even such that we have both '
        'unmatched ON/OFF singals')
    start_with_channel_on = strax.Config(
        default=True,
        help='If True, start with an ON signal, otherwise start if OFF')
    channel_on = strax.Config(
        default=straxen.acqmon_processing.AqmonChannels.BUSY_START,
        help='ON channel. Just some channel known to the VetoIntervals plugin.',
        type=int)
    channel_off = strax.Config(
        default=straxen.acqmon_processing.AqmonChannels.BUSY_STOP,
        help='OFF channel. Just some channel known to the VetoIntervals plugin',
        type=int)
    veto_duration_max = strax.Config(default=300e9,
                                     help='Max duration of one veto [ns].')
    # Start from scratch
    depends_on = ()
    parallel = False
    provides = straxen.AqmonHits.provides
    dtype = straxen.AqmonHits.dtype
    save_when = strax.SaveWhen.NEVER

    # Keep track for we need this from plugin to plugin
    _last_channel_was_off = True
    _last_endtime = 0

    # Will overwrite this with a mutable default in the test
    TOTAL_DEADTIME = None
    TOTAL_SIGNALS = None

    # This value should be larger than the duration of a single hit
    gap_ns_between_chunks = 10

    def source_finished(self):
        return True

    def is_ready(self, chunk_i):
        return chunk_i < len(self.vetos_per_chunk)

    def compute(self, chunk_i):
        if chunk_i == 0:
            self._last_channel_was_off = self.start_with_channel_on

        n_vetos = self.vetos_per_chunk[chunk_i]
        res = np.zeros(n_vetos, self.dtype)

        # Add some randomly increasing times (larger than previous chunk
        res['time'] = self._last_endtime + 1 + np.cumsum(
            np.random.randint(low=2, high=self.veto_duration_max,
                              size=n_vetos))
        res['dt'] = 1
        res['length'] = 1

        if self._last_channel_was_off:
            # Previous chunk, we ended with an off, so now we start with an ON
            res['channel'][::2] = self.channel_on
            res['channel'][1::2] = self.channel_off
            starts = res[::2]['time']
            stops = res[1::2]['time']

            # Sum the time differences between starts and stops to get the deadtime
            self.TOTAL_DEADTIME += [np.sum(stops - starts[:len(stops)])]

        else:
            # Previous chunk, we ended with an ON, so now we start with an OFF
            res['channel'][1::2] = self.channel_on
            res['channel'][::2] = self.channel_off
            starts = res[1::2]['time']
            stops = res[::2]['time']

            # Ignore the first stop (stops last from previous chunk)
            self.TOTAL_DEADTIME += [
                np.sum(stops[1:] - starts[:len(stops) - 1])
            ]

            # Additionally, add the deadtime that spans the chunks (i.e. the first stop)
            # The gap_ns_between_chunks may look a bit magic, but it's correct
            if chunk_i != 0:
                self.TOTAL_DEADTIME += [self.gap_ns_between_chunks]
            self.TOTAL_DEADTIME += [stops[0] - self._last_endtime]

        # Track the total number of signals both ON and OFF
        self.TOTAL_SIGNALS += [n_vetos]

        previous_end = self._last_endtime
        self._last_endtime = res['time'][-1] + self.gap_ns_between_chunks
        self._last_channel_was_off = res['channel'][-1] == self.channel_off
        if len(res) > 1:
            assert np.sum(res['channel'])

        if chunk_i == len(
                self.vetos_per_chunk) - 1 and not self._last_channel_was_off:
            # There is one ON without an OFF at the end of the run in
            # the last chunk. This should fill all the way to the end as
            # deadtime:
            dt = self._last_endtime - res['time'][-1]
            self.TOTAL_DEADTIME += [dt]
        return self.chunk(start=previous_end, end=self._last_endtime, data=res)
Example #5
0
import strax
import numpy as np
import tempfile
import unittest
import hypothesis
from hypothesis import given


@strax.takes_config(
    strax.Option(name='int_option', type=int, default=42),
    strax.Option(name='str_option', type=str, default='forty_two'),
    strax.Config(name='mixed', type=int, default=42),
)
class DummyPlugin(strax.Plugin):
    depends_on = ()
    provides = ('dummy_data', )
    dtype = strax.dtypes.time_fields + [
        (('Some data description', 'some_data_name'), np.int32),
    ]

    int_config = strax.Config(type=int, default=42)
    str_config = strax.Config(type=str, default='forty_two')


class TestPluginConfig(unittest.TestCase):
    @staticmethod
    def get_plugin(config):
        with tempfile.TemporaryDirectory() as temp_dir:
            context = strax.Context(
                storage=strax.DataDirectory(temp_dir, deep_scan=True),
                config=config,
Example #6
0
class DetectorSynchronization(strax.Plugin):
    """
    Plugin which computes the synchronization delay between TPC and
    vetos.

    Reference:
        * xenon:xenonnt:dsg:mveto:sync_monitor
    """
    __version__ = '0.0.3'
    depends_on = ('raw_records_aqmon', 'raw_records_aqmon_nv',
                  'raw_records_aux_mv')
    provides = 'detector_time_offsets'
    data_kind = 'detector_time_offsets'

    tpc_internal_delay = straxen.URLConfig(
        default={
            '0': 4917,
            '020380': 10137
        },
        type=dict,
        track=True,
        help='Internal delay between aqmon and regular TPC channels ins [ns]')
    adc_threshold_nim_signal = straxen.URLConfig(
        default=500,
        type=int,
        track=True,
        help='Threshold in [adc] to search for the NIM signal')
    # This value is only valid for SR0:
    epsilon_offset = straxen.URLConfig(
        default=76,
        type=int,
        track=True,
        help='Measured missing offset for nveto in [ns]')
    sync_max_delay = strax.Config(
        default=11e3, help='max delay DetectorSynchronization [ns]')
    sync_expected_min_clock_distance = straxen.URLConfig(
        default=9.9e9, help='min clock distance DetectorSynchronization [ns]')
    sync_expected_max_clock_distance = straxen.URLConfig(
        default=10.1e9, help='max clock distance DetectorSynchronization [ns]')

    def infer_dtype(self):
        dtype = []
        dtype += strax.time_fields
        dtype += [(('Time offset for nV to synchronize with TPC in [ns]',
                    'time_offset_nv'), np.int64),
                  (('Time offset for mV to synchronize with TPC in [ns]',
                    'time_offset_mv'), np.int64)]
        return dtype

    def compute(self, raw_records_aqmon, raw_records_aqmon_nv,
                raw_records_aux_mv, start, end):
        rr_tpc = raw_records_aqmon
        rr_nv = raw_records_aqmon_nv
        rr_mv = raw_records_aux_mv

        extra_offset = 0
        _mask_tpc = (rr_tpc['channel'] == AqmonChannels.GPS_SYNC)
        if not np.any(_mask_tpc):
            # For some runs in the beginning no signal has been acquired here.
            # In that case we have to add the internal DAQ delay as an extra offset later.
            _mask_tpc = (rr_tpc['channel'] == AqmonChannels.GPS_SYNC_AM)
            extra_offset = self.get_delay()

        hits_tpc = self.get_nim_edge(rr_tpc[_mask_tpc],
                                     self.config['adc_threshold_nim_signal'])
        hits_tpc['time'] += extra_offset

        _mask_mveto = (rr_mv['channel'] == AqmonChannels.GPS_SYNC_MV)
        hits_mv = self.get_nim_edge(rr_mv[_mask_mveto],
                                    self.config['adc_threshold_nim_signal'])

        _mask_nveto = rr_nv['channel'] == AqmonChannels.GPS_SYNC_NV
        hits_nv = self.get_nim_edge(rr_nv[_mask_nveto],
                                    self.config['adc_threshold_nim_signal'])
        nveto_extra_offset = 0
        if not len(hits_nv):
            # During SR0 sync signal was not recorded properly for the
            # neutron-veto, hence take waveform itself as "hits".
            _mask_nveto &= rr_nv['record_i'] == 0
            nveto_extra_offset = self.config['epsilon_offset']
            hits_nv = rr_nv[_mask_nveto]
        hits_nv['time'] += nveto_extra_offset

        offsets_mv = self.estimate_delay(hits_tpc, hits_mv)
        offsets_nv = self.estimate_delay(hits_tpc, hits_nv)
        assert len(offsets_mv) == len(
            offsets_nv), 'Unequal number of sync signals!'

        result = np.zeros(len(offsets_mv), dtype=self.dtype)
        result['time'] = hits_tpc['time']
        result['endtime'] = strax.endtime(hits_tpc)
        result['time_offset_nv'] = offsets_nv
        result['time_offset_mv'] = offsets_mv

        return result

    def get_delay(self):
        delay = 0
        for run_id, _delay in self.config['tpc_internal_delay'].items():
            if int(self.run_id) >= int(run_id):
                delay = _delay
        return delay

    @staticmethod
    def get_nim_edge(raw_records, threshold=500):
        records = strax.raw_to_records(raw_records)
        strax.baseline(records)
        hits = strax.find_hits(records, min_amplitude=threshold)
        return hits

    def estimate_delay(self, hits_det0, hits_det1):
        """
        Function to estimate the average offset between two hits.
        """
        err_value = -10000000000

        offsets = []
        prev_time = 0
        for ind in range(len(hits_det0)):
            offset = self.find_offset_nearest(hits_det1['time'],
                                              hits_det0['time'][ind])
            if ind:
                # Cannot compute time to prev for first event
                time_to_prev = hits_det0['time'][ind] - prev_time
            else:
                time_to_prev = 10e9

            # Additional check to avoid spurious signals
            _correct_distance_to_prev_lock = time_to_prev >= self.sync_expected_min_clock_distance
            _correct_distance_to_prev_lock = time_to_prev < self.sync_expected_max_clock_distance
            if (abs(offset) <
                    self.sync_max_delay) & _correct_distance_to_prev_lock:
                offsets.append(offset)
                prev_time = hits_det0['time'][ind]
            else:
                # Add err_value in case offset is not valid
                offsets.append(err_value)
                prev_time = hits_det0['time'][ind]

        return np.array(offsets)

    def find_offset_nearest(self, array, value):
        if not len(array):
            return -self.sync_max_delay
        array = np.asarray(array)
        idx = (np.abs(array - value)).argmin()
        return value - array[idx]