Coverage for tests/io/test_reader_modes.py: 100%
96 statements
« prev ^ index » next coverage.py v7.15.1, created at 2026-07-15 09:33 +0000
« prev ^ index » next coverage.py v7.15.1, created at 2026-07-15 09:33 +0000
1"""Windowing-mode tests for EventReader: ``mixed`` and ``all``.
3``delta_t`` / ``n_events`` modes are exercised throughout the per-format
4tests; the combined (``mixed``) and whole-file (``all``) modes had no
5dedicated coverage. Window semantics under test:
7* n_events cutoff: exactly ``n_events`` events per chunk while available.
8* delta_t cutoff: half-open window ``[start, start + delta_t)``.
9* mixed: whichever cutoff is hit first wins, per chunk.
10* all: one read returns everything; the next read is empty.
11"""
12import numpy as np
13import pytest
15from typing import Any
18def _uniform_events(n: int = 1000, dt: int = 1000) -> Any:
19 """n events at exactly one event per ``dt`` microseconds."""
20 ev = np.zeros(n, dtype=np.dtype([('t', np.int64), ('x', np.uint16), ('y', np.uint16), ('p', np.uint8)]))
21 ev['t'] = np.arange(n, dtype=np.int64) * dt
22 ev['x'] = np.arange(n) % 1280
23 ev['y'] = np.arange(n) % 720
24 ev['p'] = np.arange(n) % 2
25 return ev
28@pytest.fixture
29def uniform_file(tmp_path: Any) -> Any:
30 from evutils.io import EventWriter
31 ev = _uniform_events()
32 p = tmp_path / "uniform.raw"
33 with EventWriter(p, format="evt2") as w:
34 w.write(ev)
35 return p, ev
38####################################
39# mixed mode
40####################################
42def test_mixed_mode_n_events_cuts_first(uniform_file: Any) -> None:
43 """delta_t generous (100 events worth), n_events tight (20): every chunk
44 must be cut by the event count."""
45 from evutils.io import EventReader
46 p, ev = uniform_file
47 with EventReader(p, mode="mixed", delta_t=100_000, n_events=20) as r:
48 chunks = list(r)
49 assert all(len(c) == 20 for c in chunks)
50 assert sum(len(c) for c in chunks) == len(ev)
51 out = np.concatenate([np.asarray(c) for c in chunks])
52 assert np.array_equal(out['t'], ev['t'])
55def test_mixed_mode_delta_t_cuts_first(uniform_file: Any) -> None:
56 """n_events generous (500), delta_t tight (5 events worth): every chunk
57 must span < delta_t and hold 5 events."""
58 from evutils.io import EventReader
59 p, ev = uniform_file
60 with EventReader(p, mode="mixed", delta_t=5_000, n_events=500) as r:
61 chunks = [c for c in r if len(c) > 0]
62 assert all(len(c) == 5 for c in chunks)
63 for c in chunks:
64 assert c['t'][-1] - c['t'][0] < 5_000 # half-open window
65 out = np.concatenate([np.asarray(c) for c in chunks])
66 assert np.array_equal(out['t'], ev['t'])
69def test_mixed_is_default_mode(uniform_file: Any) -> None:
70 """auto mode with both parameters set resolves to mixed."""
71 from evutils.io import EventReader
72 p, _ = uniform_file
73 with EventReader(p, delta_t=100_000, n_events=20) as r:
74 first = r.read()
75 assert len(first) == 20
78def test_mixed_mode_alternating_cutoffs(tmp_path: Any) -> None:
79 """A burst then a gap: the burst chunk is cut by n_events, the sparse
80 region by delta_t."""
81 from evutils.io import EventReader, EventWriter
82 # 50 events in the first 50 us (burst), then 50 events at 1 ms apart.
83 t = np.concatenate([np.arange(50), 1_000_000 + np.arange(50) * 1000])
84 ev = _uniform_events(100)
85 ev['t'] = t
86 p = tmp_path / "burst.raw"
87 with EventWriter(p, format="evt2") as w:
88 w.write(ev)
90 with EventReader(p, mode="mixed", delta_t=10_000, n_events=30) as r:
91 first = r.read()
92 # burst: 30 events arrive well inside 10 ms -> n_events cut
93 assert len(first) == 30
94 total = len(first)
95 for c in r:
96 # sparse tail: 10 ms holds at most 10 events -> delta_t cut
97 assert len(c) <= 30
98 total += len(c)
99 assert total == 100
102####################################
103# all mode
104####################################
106def test_all_mode_single_read(uniform_file: Any) -> None:
107 from evutils.io import EventReader
108 p, ev = uniform_file
109 with EventReader(p, mode="all") as r:
110 out = r.read()
111 assert np.array_equal(np.asarray(out), ev)
112 # stream exhausted: subsequent reads are empty
113 assert len(r.read()) == 0
116def test_all_mode_iteration_yields_once(uniform_file: Any) -> None:
117 from evutils.io import EventReader
118 p, ev = uniform_file
119 with EventReader(p, mode="all") as r:
120 chunks = [c for c in r if len(c) > 0]
121 assert len(chunks) == 1
122 assert np.array_equal(np.asarray(chunks[0]), ev)
125def test_all_mode_matches_read_all(uniform_file: Any) -> None:
126 from evutils.io import EventReader
127 p, _ = uniform_file
128 with EventReader(p, mode="all") as r1, EventReader(p) as r2:
129 assert np.array_equal(np.asarray(r1.read()), np.asarray(r2.read_all()))
132####################################
133# parameter validation
134####################################
136def test_explicit_mode_requires_parameter(uniform_file: Any) -> None:
137 from evutils.io import EventReader
138 p, _ = uniform_file
139 with pytest.raises(ValueError):
140 EventReader(p, mode="delta_t") # delta_t missing
141 with pytest.raises(ValueError):
142 EventReader(p, mode="n_events") # n_events missing
143 with pytest.raises(ValueError):
144 EventReader(p, mode="bogus")
147def test_nonpositive_windows_rejected(uniform_file: Any) -> None:
148 from evutils.io import EventReader
149 p, _ = uniform_file
150 with pytest.raises(ValueError):
151 EventReader(p, n_events=0)
152 with pytest.raises(ValueError):
153 EventReader(p, delta_t=-1)
154 with pytest.raises(TypeError):
155 EventReader(p, delta_t=1.5) # type: ignore[arg-type]