diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5ad10a5279ca690b288907947902dc9c2e8c7616..c881c2e3f2c92fc55249f7b4749d4f0c00b5b313 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,18 @@ Unreleased changes Version 0 --------- +0.8.0 / 2020-01-23 +~~~~~~~~~~~~~~~~~~ +* Offline file headers are now accessible + +0.7.0 / 2020-01-23 +~~~~~~~~~~~~~~~~~~ +* Reading of summary slice status information is now supported + +0.6.3 / 2020-01-09 +~~~~~~~~~~~~~~~~~~ +* Bugfixes + 0.6.2 / 2019-12-22 ~~~~~~~~~~~~~~~~~~ * Fixes slicing of ``OfflineTracks`` diff --git a/README.rst b/README.rst index 8b0d452e99f9a1634e6f2c96c00efa4a25b0e8ab..5c74862bc8900a86c58872552b9fa37693a5a5c2 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ The km3io Python package ======================== -.. image:: https://git.km3net.de/km3py/km3io/badges/master/build.svg +.. image:: https://git.km3net.de/km3py/km3io/badges/master/pipeline.svg :target: https://git.km3net.de/km3py/km3io/pipelines .. image:: https://git.km3net.de/km3py/km3io/badges/master/coverage.svg @@ -140,12 +140,59 @@ First, let's read our file: .. code-block:: python3 >>> import km3io as ki - >>> file = 'datav6.0test.jchain.aanet.00005971.root' + >>> file = 'my_file.root' >>> r = ki.OfflineReader(file) - <km3io.aanet.OfflineReader at 0x7f24cc2bd550> + <km3io.offline.OfflineReader at 0x7f24cc2bd550> and that's it! Note that `file` can be either an str of your file path, or a path-like object. +To read the file header: + +.. code-block:: python3 + + >>> r.header + DAQ 394 + PDF 4 58 + XSecFile + can 0 1027 888.4 + can_user 0.00 1027.00 888.40 + coord_origin 0 0 0 + cut_in 0 0 0 0 + cut_nu 100 1e+08 -1 1 + cut_primary 0 0 0 0 + cut_seamuon 0 0 0 0 + decay doesnt happen + detector NOT + drawing Volume + end_event + genhencut 2000 0 + genvol 0 1027 888.4 2.649e+09 100000 + kcut 2 + livetime 0 0 + model 1 2 0 1 12 + muon_desc_file + ngen 0.1000E+06 + norma 0 0 + nuflux 0 3 0 0.500E+00 0.000E+00 0.100E+01 0.300E+01 + physics GENHEN 7.2-220514 181116 1138 + seed GENHEN 3 305765867 0 0 + simul JSirene 11012 11/17/18 07 + sourcemode diffuse + spectrum -1.4 + start_run 1 + target isoscalar + usedetfile false + xlat_user 0.63297 + xparam OFF + zed_user 0.00 3450.00 + +**Note:** not all file header types are supported, so don't be surprised when you get the following warning + +.. code-block:: python3 + + /home/zineb/km3net/km3net/km3io/km3io/offline.py:341: UserWarning: Your file header has an unsupported format + warnings.warn("Your file header has an unsupported format") + To explore all the available branches in our offline file: .. code-block:: python3 diff --git a/doc/conf.py b/doc/conf.py index 0a5580b13ee01e884a48e73495676abe41d48aaa..944303b9799f01688505bc4ec35766251302de95 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -26,7 +26,7 @@ release = get_distribution('km3io').version version = '.'.join(release.split('.')[:2]) project = 'km3io {}'.format(km3io.__version__) copyright = '{0}, Zineb Aly and Tamas Gal'.format(date.today().year) -author = 'Zineb Aly, Tamas Gal' +author = 'Zineb Aly, Tamas Gal, Johannes Schumann' # -- General configuration --------------------------------------------------- diff --git a/km3io/daq.py b/km3io/daq.py index f7bb48b16b20e5246e0d3d91fef03e39fb481f43..eb12052b6070852852c6d8bf7dc61714ecc26b7d 100644 --- a/km3io/daq.py +++ b/km3io/daq.py @@ -2,12 +2,19 @@ import uproot import numpy as np import numba as nb -TIMESLICE_FRAME_BASKET_CACHE_SIZE = 23 * 1024**2 # [byte] +TIMESLICE_FRAME_BASKET_CACHE_SIZE = 523 * 1024**2 # [byte] SUMMARYSLICE_FRAME_BASKET_CACHE_SIZE = 523 * 1024**2 # [byte] + +# Parameters for PMT rate conversions, since the rates in summary slices are +# stored as a single byte to save space. The values from 0-255 can be decoded +# using the `get_rate(value)` function, which will yield the actual rate +# in Hz. MINIMAL_RATE_HZ = 2.0e3 MAXIMAL_RATE_HZ = 2.0e6 RATE_FACTOR = np.log(MAXIMAL_RATE_HZ / MINIMAL_RATE_HZ) / 255 +CHANNEL_BITS_TEMPLATE = np.zeros(31, dtype=bool) + @nb.vectorize([ nb.int32(nb.int8), @@ -23,10 +30,82 @@ def get_rate(value): return MINIMAL_RATE_HZ * np.exp(value * RATE_FACTOR) +@nb.guvectorize( + "void(i8, b1[:], b1[:])", "(), (n) -> (n)", target="parallel", nopython=True + ) +def unpack_bits(value, bits_template, out): + """Return a boolean array for a value's bit representation. + + This function also accepts arrays as input, the output shape will be + NxM where N is the number of input values and M the length of the + ``bits_template`` array, which is just a dummy array, due to the weird + signature system of numba. + + Parameters + ---------- + value: int or np.array(int) with shape (N,) + The binary value of containing the bit information + bits_template: np.array() with shape (M,) + The template for the output array, the only important is its shape + + Returns + ------- + np.array(bool) either with shape (M,) or (N, M) + """ + for i in range(bits_template.shape[0]): + out[30 - i] = value & (1 << i) > 0 + + +def get_channel_flags(value): + """Returns the hrv/fifo flags for the PMT channels (hrv/fifo) + + Parameters + ---------- + value : int32 + The integer value to be parsed. + """ + channel_bits = np.bitwise_and(value, 0x3FFFFFFF) + flags = unpack_bits(channel_bits, CHANNEL_BITS_TEMPLATE) + return np.flip(flags, axis=-1) + + +def get_number_udp_packets(value): + """Returns the number of received UDP packets (dq_status) + + Parameters + ---------- + value : int32 + The integer value to be parsed. + """ + return np.bitwise_and(value, 0x7FFF) + + +def get_udp_max_sequence_number(value): + """Returns the maximum sequence number of the received UDP packets (dq_status) + + Parameters + ---------- + value : int32 + The integer value to be parsed. + """ + return np.right_shift(value, 16) + + +def has_udp_trailer(value): + """Returns the UDP Trailer flag (fifo) + + Parameters + ---------- + value : int32 + The integer value to be parsed. + """ + return np.any(np.bitwise_and(value, np.left_shift(1, 31))) + + class DAQReader: """Reader for DAQ ROOT files""" def __init__(self, filename): - self.fobj = uproot.open(filename) + self._fobj = uproot.open(filename) self._events = None self._timeslices = None self._summaryslices = None @@ -34,7 +113,7 @@ class DAQReader: @property def events(self): if self._events is None: - tree = self.fobj["KM3NET_EVENT"] + tree = self._fobj["KM3NET_EVENT"] headers = tree["KM3NETDAQ::JDAQEventHeader"].array( uproot.interpret(tree["KM3NETDAQ::JDAQEventHeader"], @@ -57,20 +136,20 @@ class DAQReader: @property def timeslices(self): if self._timeslices is None: - self._timeslices = DAQTimeslices(self.fobj) + self._timeslices = DAQTimeslices(self._fobj) return self._timeslices @property def summaryslices(self): if self._summaryslices is None: - self._summaryslices = SummmarySlices(self.fobj) + self._summaryslices = SummmarySlices(self._fobj) return self._summaryslices class SummmarySlices: """A wrapper for summary slices""" def __init__(self, fobj): - self.fobj = fobj + self._fobj = fobj self._slices = None self._headers = None self._rates = None @@ -96,7 +175,7 @@ class SummmarySlices: def _read_summaryslices(self): """Reads a lazyarray of summary slices""" - tree = self.fobj[b'KM3NET_SUMMARYSLICE'][b'KM3NET_SUMMARYSLICE'] + tree = self._fobj[b'KM3NET_SUMMARYSLICE'][b'KM3NET_SUMMARYSLICE'] return tree[b'vector<KM3NETDAQ::JDAQSummaryFrame>'].lazyarray( uproot.asjagged(uproot.astable( uproot.asdtype([("dom_id", "i4"), ("dq_status", "u4"), @@ -109,7 +188,7 @@ class SummmarySlices: def _read_headers(self): """Reads a lazyarray of summary slice headers""" - tree = self.fobj[b'KM3NET_SUMMARYSLICE'][b'KM3NET_SUMMARYSLICE'] + tree = self._fobj[b'KM3NET_SUMMARYSLICE'][b'KM3NET_SUMMARYSLICE'] return tree[b'KM3NETDAQ::JDAQSummarysliceHeader'].lazyarray( uproot.interpret(tree[b'KM3NETDAQ::JDAQSummarysliceHeader'], cntvers=True)) @@ -118,42 +197,35 @@ class SummmarySlices: class DAQTimeslices: """A simple wrapper for DAQ timeslices""" def __init__(self, fobj): - self.fobj = fobj + self._fobj = fobj self._timeslices = {} - self._read_default_stream() self._read_streams() - def _read_default_stream(self): - """Read the default KM3NET_TIMESLICE stream""" - tree = self.fobj[b'KM3NET_TIMESLICE'][b'KM3NET_TIMESLICE'] - headers = tree[b'KM3NETDAQ::JDAQTimesliceHeader'] - superframes = tree[b'vector<KM3NETDAQ::JDAQSuperFrame>'] - self._timeslices['default'] = (headers, superframes) - def _read_streams(self): """Read the L0, L1, L2 and SN streams if available""" - streams = [ + streams = set( s.split(b"KM3NET_TIMESLICE_")[1].split(b';')[0] - for s in self.fobj.keys() if b"KM3NET_TIMESLICE_" in s - ] + for s in self._fobj.keys() if b"KM3NET_TIMESLICE_" in s) for stream in streams: - tree = self.fobj[b'KM3NET_TIMESLICE_' + - stream][b'KM3NETDAQ::JDAQTimeslice'] + tree = self._fobj[b'KM3NET_TIMESLICE_' + + stream][b'KM3NETDAQ::JDAQTimeslice'] headers = tree[b'KM3NETDAQ::JDAQTimesliceHeader'][ b'KM3NETDAQ::JDAQHeader'][b'KM3NETDAQ::JDAQChronometer'] if len(headers) == 0: continue superframes = tree[b'vector<KM3NETDAQ::JDAQSuperFrame>'] + hits_dtype = np.dtype([("pmt", "u1"), ("tdc", "<u4"), + ("tot", "u1")]) hits_buffer = superframes[ b'vector<KM3NETDAQ::JDAQSuperFrame>.buffer'].lazyarray( - uproot.asjagged(uproot.astable( - uproot.asdtype([("pmt", "u1"), ("tdc", "u4"), - ("tot", "u1")])), + uproot.asjagged(uproot.astable(uproot.asdtype(hits_dtype)), skipbytes=6), basketcache=uproot.cache.ThreadSafeArrayCache( TIMESLICE_FRAME_BASKET_CACHE_SIZE)) self._timeslices[stream.decode("ascii")] = (headers, superframes, hits_buffer) + setattr(self, stream.decode("ascii"), + DAQTimesliceStream(headers, superframes, hits_buffer)) def stream(self, stream, idx): ts = self._timeslices[stream] @@ -167,6 +239,21 @@ class DAQTimeslices: return str(self) +class DAQTimesliceStream: + def __init__(self, headers, superframes, hits_buffer): + # self.headers = headers.lazyarray( + # uproot.asjagged(uproot.astable( + # uproot.asdtype( + # np.dtype([('a', 'i4'), ('b', 'i4'), ('c', 'i4'), + # ('d', 'i4'), ('e', 'i4')]))), + # skipbytes=6), + # basketcache=uproot.cache.ThreadSafeArrayCache( + # TIMESLICE_FRAME_BASKET_CACHE_SIZE)) + self.headers = headers + self.superframes = superframes + self._hits_buffer = hits_buffer + + class DAQTimeslice: """A wrapper for a DAQ timeslice""" def __init__(self, header, superframe, hits_buffer, idx, stream): diff --git a/km3io/definitions/fitparameters.py b/km3io/definitions/fitparameters.py new file mode 100644 index 0000000000000000000000000000000000000000..3b9a03ee4fb0e77f751a016388af7da001d20198 --- /dev/null +++ b/km3io/definitions/fitparameters.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +""" +KM3NeT Data Definitions v1.1.2 +https://git.km3net.de/common/km3net-dataformat +""" + +# fitparameters +data = { + "JGANDALF_BETA0_RAD": 0, + "JGANDALF_BETA1_RAD": 1, + "JGANDALF_CHI2": 2, + "JGANDALF_NUMBER_OF_HITS": 3, + "JENERGY_ENERGY": 4, + "JENERGY_CHI2": 5, + "JGANDALF_LAMBDA": 6, + "JGANDALF_NUMBER_OF_ITERATIONS": 7, + "JSTART_NPE_MIP": 8, + "JSTART_NPE_MIP_TOTAL": 9, + "JSTART_LENGTH_METRES": 10, + "JVETO_NPE": 11, + "JVETO_NUMBER_OF_HITS": 12, + "JENERGY_MUON_RANGE_METRES": 13, + "JENERGY_NOISE_LIKELIHOOD": 14, + "JENERGY_NDF": 15, + "JENERGY_NUMBER_OF_HITS": 16, + "JCOPY_Z_M": 17, +} diff --git a/km3io/definitions/reconstruction.py b/km3io/definitions/reconstruction.py new file mode 100644 index 0000000000000000000000000000000000000000..43e009afc6044c192615ee73cbab480e10837368 --- /dev/null +++ b/km3io/definitions/reconstruction.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +""" +KM3NeT Data Definitions v1.1.2 +https://git.km3net.de/common/km3net-dataformat +""" + +# reconstruction +data = { + "JPP_RECONSTRUCTION_TYPE": 4000, + "JMUONFIT": 0, + "JMUONBEGIN": 0, + "JMUONPREFIT": 1, + "JMUONSIMPLEX": 2, + "JMUONGANDALF": 3, + "JMUONENERGY": 4, + "JMUONSTART": 5, + "JLINEFIT": 6, + "JMUONEND": 99, + "JSHOWERFIT": 100, + "JSHOWERBEGIN": 100, + "JSHOWERPREFIT": 101, + "JSHOWERPOSITIONFIT": 102, + "JSHOWERCOMPLETEFIT": 103, + "JSHOWER_BJORKEN_Y": 104, + "JSHOWEREND": 199, + "DUSJSHOWERFIT": 200, + "DUSJBEGIN": 200, + "DUSJPREFIT": 201, + "DUSJPOSITIONFIT": 202, + "JDUSJCOMPLETEFIT": 203, + "DUSJEND": 299, + "AASHOWERFIT": 300, + "AASHOWERBEGIN": 300, + "AASHOWERCOMPLETEFIT": 301, + "AASHOWEREND": 399, + "JUSERBEGIN": 1000, + "JMUONVETO": 1001, + "JMUONPATH": 1003, + "JMCEVT": 1004, + "JUSEREND": 1099, + "RECTYPE_UNKNOWN": -1, + "RECSTAGE_UNKNOWN": -1, +} diff --git a/km3io/definitions/trigger.py b/km3io/definitions/trigger.py new file mode 100644 index 0000000000000000000000000000000000000000..79826052d9b3b2372c1736c48c625dbbff63e55b --- /dev/null +++ b/km3io/definitions/trigger.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +""" +KM3NeT Data Definitions v1.1.2 +https://git.km3net.de/common/km3net-dataformat +""" + +# trigger +data = { + "JTRIGGER3DSHOWER": 1, + "JTRIGGERMXSHOWER": 2, + "JTRIGGER3DMUON": 4, + "JTRIGGERNB": 5, +} diff --git a/km3io/fitparameters.py b/km3io/fitparameters.py deleted file mode 100644 index 72447652965f230f621253a67eef52652ba9747c..0000000000000000000000000000000000000000 --- a/km3io/fitparameters.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -""" -KM3NeT Data Definitions v1.0.1 -https://git.km3net.de/common/data-format -""" - -# fitparameters -JGANDALF_BETA0_RAD = 0 -JGANDALF_BETA1_RAD = 1 -JGANDALF_CHI2 = 2 -JGANDALF_NUMBER_OF_HITS = 3 -JENERGY_ENERGY = 4 -JENERGY_CHI2 = 5 -JGANDALF_LAMBDA = 6 -JGANDALF_NUMBER_OF_ITERATIONS = 7 -JSTART_NPE_MIP = 8 -JSTART_NPE_MIP_TOTAL = 9 -JSTART_LENGTH_METRES = 10 -JVETO_NPE = 11 -JVETO_NUMBER_OF_HITS = 12 -JENERGY_MUON_RANGE_METRES = 13 -JENERGY_NOISE_LIKELIHOOD = 14 -JENERGY_NDF = 15 -JENERGY_NUMBER_OF_HITS = 16 -JCOPY_Z_M = 17 diff --git a/km3io/offline.py b/km3io/offline.py index bc6f379f1321798c3cdbd11b62f3763c03835010..335342a51efff565099863c7243724b317465356 100644 --- a/km3io/offline.py +++ b/km3io/offline.py @@ -1,5 +1,6 @@ import uproot import numpy as np +import warnings # 110 MB based on the size of the largest basket found so far in km3net BASKET_CACHE_SIZE = 110 * 1024**2 @@ -320,6 +321,7 @@ class OfflineReader: self._mc_tracks = None self._keys = None self._best_reco = None + self._header = None def __getitem__(self, item): return OfflineReader(file_path=self._file_path, data=self._data[item]) @@ -327,6 +329,20 @@ class OfflineReader: def __len__(self): return len(self._data) + @property + def header(self): + if self._header is None: + fobj = uproot.open(self._file_path) + if b'Head;1' in fobj.keys(): + self._header = {} + for n, x in fobj['Head']._map_3c_string_2c_string_3e_.items(): + print("{:15s} {}".format(n.decode("utf-8"), x.decode("utf-8"))) + self._header[n.decode("utf-8")] = x.decode("utf-8") + if b'Header;1' in fobj.keys(): + warnings.warn("Your file header has an unsupported format") + return self._header + + @property def keys(self): """wrapper for all keys in an offline file. diff --git a/km3io/reconstruction.py b/km3io/reconstruction.py deleted file mode 100644 index e981c9b792f5dcff6a59e72c246bddf3110a4612..0000000000000000000000000000000000000000 --- a/km3io/reconstruction.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 -*- -""" -KM3NeT Data Definitions v1.0.1 -https://git.km3net.de/common/data-format -""" - -# reconstruction -JMUONFIT = 0 -JMUONBEGIN = 0 -JMUONPREFIT = 1 -JMUONSIMPLEX = 2 -JMUONGANDALF = 3 -JMUONENERGY = 4 -JMUONSTART = 5 -JLINEFIT = 6 -JMUONEND = 99 -JSHOWERFIT = 100 -JSHOWERBEGIN = 100 -JSHOWERPREFIT = 101 -JSHOWERPOSITIONFIT = 102 -JSHOWERCOMPLETEFIT = 103 -JSHOWER_BJORKEN_Y = 104 -JSHOWEREND = 199 -DUSJSHOWERFIT = 200 -DUSJBEGIN = 200 -DUSJPREFIT = 201 -DUSJPOSITIONFIT = 202 -JDUSJCOMPLETEFIT = 203 -DUSJEND = 299 -AASHOWERFIT = 300 -AASHOWERBEGIN = 300 -AASHOWERCOMPLETEFIT = 301 -AASHOWEREND = 399 -JUSERBEGIN = 1000 -JMUONVETO = 1001 -JMUONPATH = 1003 -JMCEVT = 1004 -JUSEREND = 1099 -RECTYPE_UNKNOWN = -1 -RECSTAGE_UNKNOWN = -1 diff --git a/km3io/trigger.py b/km3io/trigger.py deleted file mode 100644 index 6198b4262362c94e48d193f9708396092d888088..0000000000000000000000000000000000000000 --- a/km3io/trigger.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -""" -KM3NeT Data Definitions v1.0.1 -https://git.km3net.de/common/data-format -""" - -# trigger -JTRIGGER3DSHOWER = 1 -JTRIGGERMXSHOWER = 2 -JTRIGGER3DMUON = 4 -JTRIGGERNB = 5 diff --git a/requirements.txt b/requirements.txt index 81bca4449ad63617ed6725bc577a445cc3f1cdcf..96ec8f79ccd2c06a830005d59baaf84d601f67f1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ docopt numba -uproot>=3.10.12 +uproot>=3.11.1 setuptools_scm diff --git a/setup.py b/setup.py index 0dfb14e3ca7d33a6a9674d7f755992250b39edd1..7f9d245dcbc52f66057d6f9e96a99ea1ebfced63 100644 --- a/setup.py +++ b/setup.py @@ -20,8 +20,8 @@ setup( url='http://git.km3net.de/km3py/km3io', description='KM3NeT I/O without ROOT', long_description=long_description, - author='Zineb Aly, Tamas Gal', - author_email='zaly@km3net.de, tgal@km3net.de', + author='Zineb Aly, Tamas Gal, Johannes Schumann', + author_email='zaly@km3net.de, tgal@km3net.de, johannes.schumann@fau.de', packages=['km3io'], include_package_data=True, platforms='any', @@ -33,9 +33,7 @@ setup( install_requires=requirements, python_requires='>=3.5', entry_points={ - 'console_scripts': [ - 'KPrintTree=km3io.utils.kprinttree:main' - ] + 'console_scripts': ['KPrintTree=km3io.utils.kprinttree:main'] }, classifiers=[ 'Intended Audience :: Developers', @@ -44,4 +42,4 @@ setup( ], ) -__author__ = 'Zineb Aly and Tamas Gal' +__author__ = 'Zineb Aly, Tamas Gal and Johannes Schumann' diff --git a/tests/test_daq.py b/tests/test_daq.py index 8ee4429921dd8cf8c5c75dc1143bd91612c5dc87..090fe14a767559bf8f59867172f7256760152255 100644 --- a/tests/test_daq.py +++ b/tests/test_daq.py @@ -2,7 +2,7 @@ import os import re import unittest -from km3io.daq import DAQReader, get_rate +from km3io.daq import DAQReader, get_rate, has_udp_trailer, get_udp_max_sequence_number, get_channel_flags, get_number_udp_packets SAMPLES_DIR = os.path.join(os.path.dirname(__file__), "samples") @@ -122,7 +122,6 @@ class TestDAQTimeslices(unittest.TestCase): "daq_v1.0.0.root")).timeslices def test_data_lengths(self): - assert 3 == len(self.ts._timeslices["default"][0]) assert 3 == len(self.ts._timeslices["L1"][0]) assert 3 == len(self.ts._timeslices["SN"][0]) with self.assertRaises(KeyError): @@ -130,12 +129,15 @@ class TestDAQTimeslices(unittest.TestCase): with self.assertRaises(KeyError): assert 0 == len(self.ts._timeslices["L0"][0]) + def test_streams(self): + self.ts.stream("L1", 0) + self.ts.stream("SN", 0) + def test_reading_frames(self): assert 8 == len(self.ts.stream("SN", 1).frames[808447186]) def test_str(self): s = str(self.ts) - assert "default" in s assert "L1" in s assert "SN" in s @@ -173,8 +175,227 @@ class TestSummaryslices(unittest.TestCase): def test_rates(self): assert 3 == len(self.ss.rates) - -class TestGetReate(unittest.TestCase): + def test_fifo(self): + s = self.ss.slices[0] + dct_fifo_stat = { + 808981510: True, + 808981523: False, + 808981672: False, + 808974773: False + } + for dom_id, fifo_status in dct_fifo_stat.items(): + frame = s[s.dom_id == dom_id] + assert any(get_channel_flags(frame.fifo[0])) == fifo_status + + def test_has_udp_trailer(self): + s = self.ss.slices[0] + dct_udp_trailer = { + 806451572: True, + 806455814: True, + 806465101: True, + 806483369: True, + 806487219: True, + 806487226: True, + 806487231: True, + 808432835: True, + 808435278: True, + 808447180: True, + 808447186: True + } + for dom_id, udp_trailer in dct_udp_trailer.items(): + frame = s[s.dom_id == dom_id] + assert has_udp_trailer(frame.fifo[0]) == udp_trailer + + def test_high_rate_veto(self): + s = self.ss.slices[0] + dct_high_rate_veto = { + 808489014: True, + 808489117: False, + 808493910: True, + 808946818: True, + 808951460: True, + 808956908: True, + 808959411: True, + 808961448: True, + 808961480: True, + 808961504: True, + 808961655: False, + 808964815: False, + 808964852: True, + 808969848: False, + 808969857: True, + 808972593: True, + 808972598: True, + 808972698: False, + 808974758: False, + 808974773: True, + 808974811: True, + 808974972: True, + 808976377: True, + 808979567: False, + 808979721: False, + 808979729: False, + 808981510: True, + 808981523: True, + 808981672: False, + 808981812: True, + 808981864: False, + 808982018: False + } + for dom_id, high_rate_veto in dct_high_rate_veto.items(): + frame = s[s.dom_id == dom_id] + assert any(get_channel_flags(frame.hrv[0])) == high_rate_veto + + def test_max_sequence_number(self): + s = self.ss.slices[0] + dct_seq_numbers = { + 808974758: 18, + 808974773: 26, + 808974811: 25, + 808974972: 41, + 808976377: 35, + 808979567: 20, + 808979721: 17, + 808979729: 25, + 808981510: 35, + 808981523: 27, + 808981672: 17, + 808981812: 34, + 808981864: 18, + 808982018: 21, + 808982041: 27, + 808982077: 32, + 808982547: 20, + 808984711: 26, + 808996773: 31, + 808997793: 21, + 809006037: 26, + 809007627: 18, + 809503416: 28, + 809521500: 31, + 809524432: 21, + 809526097: 23, + 809544058: 21, + 809544061: 23 + } + for dom_id, max_sequence_number in dct_seq_numbers.items(): + frame = s[s.dom_id == dom_id] + assert get_udp_max_sequence_number( + frame.dq_status[0]) == max_sequence_number + + def test_number_udp_packets(self): + s = self.ss.slices[0] + dct_n_packets = { + 808451904: 27, + 808451907: 22, + 808469129: 20, + 808472260: 21, + 808472265: 22, + 808488895: 20, + 808488990: 20, + 808489014: 28, + 808489117: 22, + 808493910: 26, + 808946818: 23, + 808951460: 37, + 808956908: 33, + 808959411: 36, + 808961448: 28, + 808961480: 24, + 808961504: 28, + 808961655: 20, + 808964815: 20, + 808964852: 28, + 808969848: 21 + } + for dom_id, n_udp_packets in dct_n_packets.items(): + frame = s[s.dom_id == dom_id] + assert get_number_udp_packets(frame.dq_status[0]) == n_udp_packets + + def test_hrv_flags(self): + s = self.ss.slices[0] + dct_hrv_flags = { + 809524432: [ + False, False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, False, + False, False, False, False + ], + 809526097: [ + False, False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, False, + True, False, False, False, False, False, False, False, True, + False, False, False, False + ], + 809544058: [ + False, False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, False, + False, False, False, False + ], + 809544061: [ + False, True, False, False, False, True, False, False, False, + False, False, False, False, False, False, True, False, False, + False, False, False, True, False, False, False, False, False, + False, False, False, False + ] + } + for dom_id, hrv_flags in dct_hrv_flags.items(): + frame = s[s.dom_id == dom_id] + assert any([ + a == b + for a, b in zip(get_channel_flags(frame.hrv[0]), hrv_flags) + ]) + + def test_fifo_flags(self): + s = self.ss.slices[0] + dct_fifo_flags = { + 808982547: [ + False, False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, False, + False, False, False, False + ], + 808984711: [ + False, False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, False, + False, False, False, False + ], + 808996773: [ + False, False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, False, + False, False, False, False + ], + 808997793: [ + False, False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, False, + False, False, False, False + ], + 809006037: [ + False, False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, False, + False, False, False, False + ], + 808981510: [ + False, False, False, False, False, False, False, False, False, + False, False, False, False, False, True, True, False, False, + False, True, False, True, True, True, True, True, True, False, + False, True, False + ] + } + for dom_id, fifo_flags in dct_fifo_flags.items(): + frame = s[s.dom_id == dom_id] + assert any([ + a == b for a, b in zip( + get_channel_flags(frame.fifo[0]), fifo_flags) + ]) + + +class TestGetRate(unittest.TestCase): def test_zero(self): assert 0 == get_rate(0) @@ -185,4 +406,4 @@ class TestGetReate(unittest.TestCase): def test_vectorized_input(self): self.assertListEqual([2054], list(get_rate([1]))) - self.assertListEqual([2054, 2111, 2169], list(get_rate([1,2,3]))) + self.assertListEqual([2054, 2111, 2169], list(get_rate([1, 2, 3]))) diff --git a/tests/test_offline.py b/tests/test_offline.py index b4a6ee8b3d9c4ba45a9a063f6a21ac81a9c86543..fa708e28015004bf9f9720a4560baae9574fa39f 100644 --- a/tests/test_offline.py +++ b/tests/test_offline.py @@ -184,6 +184,16 @@ class TestOfflineReader(unittest.TestCase): self.assertEqual(best.size, 9) self.assertEqual(best['JGANDALF_BETA1_RAD'][:4].tolist(), JGANDALF_BETA1_RAD) + def test_reading_header(self): + # head is the supported format + head = OfflineReader(OFFLINE_NUMUCC).header + + self.assertEqual(float(head['DAQ']), 394) + self.assertEqual(float(head['kcut']), 2) + + # test the warning for unsupported fheader format + self.assertWarns(UserWarning, self.r.header, + "Your file header has an unsupported format") class TestOfflineEvents(unittest.TestCase):