natus-erd-reader 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. natus_erd_reader-0.2.1/CHANGELOG.md +58 -0
  2. natus_erd_reader-0.2.1/CONTRIBUTING.md +59 -0
  3. natus_erd_reader-0.2.1/LICENSE +21 -0
  4. natus_erd_reader-0.2.1/MANIFEST.in +11 -0
  5. natus_erd_reader-0.2.1/PKG-INFO +289 -0
  6. natus_erd_reader-0.2.1/README.md +255 -0
  7. natus_erd_reader-0.2.1/SECURITY.md +41 -0
  8. natus_erd_reader-0.2.1/THIRD_PARTY_NOTICES.md +42 -0
  9. natus_erd_reader-0.2.1/examples/read_window.py +33 -0
  10. natus_erd_reader-0.2.1/examples/validate_recording.py +580 -0
  11. natus_erd_reader-0.2.1/pyproject.toml +47 -0
  12. natus_erd_reader-0.2.1/setup.cfg +4 -0
  13. natus_erd_reader-0.2.1/src/natus_erd/__init__.py +26 -0
  14. natus_erd_reader-0.2.1/src/natus_erd/_paths.py +92 -0
  15. natus_erd_reader-0.2.1/src/natus_erd/binary.py +292 -0
  16. natus_erd_reader-0.2.1/src/natus_erd/decoder.py +207 -0
  17. natus_erd_reader-0.2.1/src/natus_erd/ent.py +474 -0
  18. natus_erd_reader-0.2.1/src/natus_erd/errors.py +21 -0
  19. natus_erd_reader-0.2.1/src/natus_erd/limits.py +60 -0
  20. natus_erd_reader-0.2.1/src/natus_erd/models.py +65 -0
  21. natus_erd_reader-0.2.1/src/natus_erd/py.typed +0 -0
  22. natus_erd_reader-0.2.1/src/natus_erd/reader.py +567 -0
  23. natus_erd_reader-0.2.1/src/natus_erd_reader.egg-info/PKG-INFO +289 -0
  24. natus_erd_reader-0.2.1/src/natus_erd_reader.egg-info/SOURCES.txt +41 -0
  25. natus_erd_reader-0.2.1/src/natus_erd_reader.egg-info/dependency_links.txt +1 -0
  26. natus_erd_reader-0.2.1/src/natus_erd_reader.egg-info/requires.txt +6 -0
  27. natus_erd_reader-0.2.1/src/natus_erd_reader.egg-info/top_level.txt +1 -0
  28. natus_erd_reader-0.2.1/tests/__init__.py +1 -0
  29. natus_erd_reader-0.2.1/tests/_fixture.py +204 -0
  30. natus_erd_reader-0.2.1/tests/test_channel_compat.py +143 -0
  31. natus_erd_reader-0.2.1/tests/test_decoder_limits.py +173 -0
  32. natus_erd_reader-0.2.1/tests/test_distribution_audit.py +131 -0
  33. natus_erd_reader-0.2.1/tests/test_ent_decoder.py +66 -0
  34. natus_erd_reader-0.2.1/tests/test_ent_safety.py +206 -0
  35. natus_erd_reader-0.2.1/tests/test_gap_compat.py +236 -0
  36. natus_erd_reader-0.2.1/tests/test_package.py +58 -0
  37. natus_erd_reader-0.2.1/tests/test_path_compat.py +168 -0
  38. natus_erd_reader-0.2.1/tests/test_read_limits.py +154 -0
  39. natus_erd_reader-0.2.1/tests/test_reader.py +121 -0
  40. natus_erd_reader-0.2.1/tests/test_sampling_compat.py +158 -0
  41. natus_erd_reader-0.2.1/tests/test_security_paths.py +138 -0
  42. natus_erd_reader-0.2.1/tests/test_validation_example.py +255 -0
  43. natus_erd_reader-0.2.1/tools/check_dist.py +132 -0
@@ -0,0 +1,58 @@
1
+ # Changelog
2
+
3
+ ## 0.2.1 — 2026-09-04
4
+
5
+ Compatibility fixes within the existing schema-9 Quantum layout; no new
6
+ application entry points, hardware calibration or resampling behavior.
7
+
8
+ - Read the finite positive sample rate from ERD headers instead of requiring
9
+ 2048 Hz. Reject non-finite recording duration and inconsistent segment rates.
10
+ - Interpret STC stored counts separately from stamp spans, verify ETC counts,
11
+ and preserve leading, internal, trailing and fully empty gaps as NaN.
12
+ - Resolve complete recording filenames case-insensitively with ambiguity and
13
+ directory-boundary checks. Explicit STC or matching EEG can select among
14
+ multiple recordings; an unrelated ordinary `.stc` directory is ignored.
15
+ - Keep positional channel-label placeholders, avoid fallback-name collisions
16
+ and require index selection for genuinely duplicated vendor labels.
17
+ - Add bounded public-API validation examples without a fixed channel label;
18
+ adapt sample windows to the actual rate and redact unknown error details.
19
+ - Extend synthetic compatibility coverage, add Python 3.13 CI, and test the
20
+ minimum NumPy 1.24.x dependency on Python 3.10 for Windows and Linux.
21
+ - Preserve all existing read/parser budgets and Python-only runtime surface.
22
+
23
+ ## 0.2.0 — source-only update
24
+
25
+ Python-library-only release with explicit resource limits. This is a breaking
26
+ change from 0.1.0, and remains experimental research software.
27
+
28
+ - Remove both installed command-line programs and `python -m natus_erd`.
29
+ - Remove EDF APIs, comparison tools, plotting extras and the local web viewer.
30
+ - Keep native ERD window reads, structural metadata, ENT events and validation.
31
+ - Add `ReadLimits` and `ResourceLimitError`; default output budget is 64 MiB
32
+ per read, with at most 131,072 samples per call. Reject oversized requests
33
+ before allocation and NumPy import.
34
+ - Add `iter_samples()` for chunked processing without retaining a full record.
35
+ - Bound metadata reads and ENT parsing; validate compressed-packet lengths and
36
+ decode through a bounded buffer instead of loading a whole packet.
37
+ - Parse ENT using an independent restricted grammar; never execute input.
38
+ - Load NumPy only when an accepted sample request needs an array. Do not
39
+ modify global numerical-backend thread settings.
40
+ - Require an explicit recording directory or entry-point file rather than
41
+ recursively searching broad parent directories.
42
+ - Extend synthetic resource-boundary tests and distribution checks. No
43
+ operating-system crash-prevention or clinical-safety claim is made.
44
+
45
+ ### Migration from 0.1.0
46
+
47
+ Use `NatusERDReader` directly from Python; there is no replacement executable.
48
+ `EDFInfo`, `EDFReader` and `EDFSignal` are no longer exported or included.
49
+ Remove optional `plot`/`validation` extras from dependency declarations.
50
+ Split large `read_samples` calls with `iter_samples`, or choose an explicit
51
+ `ReadLimits` policy appropriate to the application. Raising the limits is an
52
+ application decision and requires its own memory planning.
53
+
54
+ ## 0.1.0
55
+
56
+ Initial research release of the schema-9 Quantum reader, before the bounded
57
+ Python-only API introduced in 0.2.0. The legacy release did not sufficiently
58
+ constrain output allocation, metadata reads or compressed-packet reads.
@@ -0,0 +1,59 @@
1
+ # Contributing
2
+
3
+ Use Python 3.10 or newer. A clean development environment is recommended:
4
+
5
+ ```shell
6
+ python -m venv .venv
7
+ # Activate the environment using the command appropriate for your shell.
8
+ python -m pip install -e ".[dev]"
9
+ python -m unittest discover -v -s tests -t .
10
+ python -m build
11
+ python -m twine check dist/*
12
+ python tools/check_dist.py dist
13
+ ```
14
+
15
+ All automated tests generate synthetic recordings at runtime. Do not add
16
+ clinical recordings, patient names, event text, exported waveforms, local
17
+ credentials or derived plots to issues, pull requests, commits or artifacts.
18
+ When reporting a format problem, supply sanitized structural metadata and a
19
+ synthetic reproducer where possible.
20
+
21
+ Keep unsupported schemas and headboxes explicit. Do not silently reuse the
22
+ Quantum calibration for unverified hardware. Changes to packet decoding need
23
+ tests for absolute values, delta sentinels, shorted channels and packet edges.
24
+ Resource-boundary tests must intercept oversized requests or use synthetic
25
+ bounded input; do not actually allocate gigabytes to test rejection paths.
26
+ Keep metadata-only imports free of NumPy and never alter the user's global
27
+ numerical-backend settings from package code.
28
+
29
+ CI sets `OPENBLAS_NUM_THREADS=1` and `OMP_NUM_THREADS=1` for the test runner
30
+ to keep backend initialization predictable. These are application-side test
31
+ settings, not package behavior or a substitute for an operating-system memory
32
+ limit. Backend-specific failure diagnosis should record the actual environment
33
+ and avoid unsafe reproduction on a user's machine.
34
+
35
+ The installed distribution exposes Python APIs only. Do not add executables,
36
+ web assets, EDF readers, optional plotting dependencies or real data to it.
37
+ `tools/check_dist.py` is a development audit script, not an installed command.
38
+
39
+ ## Release checklist
40
+
41
+ 1. Update the versions in `pyproject.toml` and `src/natus_erd/__init__.py`.
42
+ 2. Update `CHANGELOG.md`; run the unit suite and distribution audit from a
43
+ clean staging tree so removed files cannot survive in build caches.
44
+ 3. Confirm `git ls-files` contains no recordings, reports or credentials.
45
+ 4. Merge to `main` and wait for CI to succeed.
46
+ 5. Push an annotated `vX.Y.Z` tag. The release workflow builds and attaches a
47
+ wheel and source archive to the GitHub release.
48
+
49
+ 6. For an explicitly authorized PyPI publication, configure a PyPI Trusted
50
+ Publisher for owner `ms903`, repository `natus-erd-reader`, workflow
51
+ `publish-pypi.yml`, and environment `pypi`. A new PyPI project requires a
52
+ pending publisher under the account's Publishing settings. See the
53
+ [official PyPI setup guide](https://docs.pypi.org/trusted-publishers/creating-a-project-through-oidc/).
54
+ 7. Manually run `publish-pypi.yml` with the released tag. It audits the existing
55
+ GitHub Release archives and uploads those same files using short-lived OIDC
56
+ credentials. It does not rebuild them or require a stored PyPI API token.
57
+
58
+ PyPI publication is a separate, explicitly authorized step, not triggered by
59
+ ordinary pushes or by the GitHub Release workflow. Never commit credentials.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ms903 and natus-erd-reader contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,11 @@
1
+ include README.md LICENSE THIRD_PARTY_NOTICES.md CHANGELOG.md CONTRIBUTING.md SECURITY.md
2
+ include pyproject.toml MANIFEST.in
3
+ recursive-include src/natus_erd *.py py.typed
4
+ recursive-include tests *.py
5
+ recursive-include tools *.py
6
+ recursive-include examples *.py
7
+ prune data
8
+ prune figures
9
+ prune reports
10
+ prune .github
11
+ global-exclude __pycache__ *.py[cod] *.erd *.etc *.stc *.eeg *.ent *.edf *.bdf *.npy *.npz
@@ -0,0 +1,289 @@
1
+ Metadata-Version: 2.4
2
+ Name: natus-erd-reader
3
+ Version: 0.2.1
4
+ Summary: Resource-bounded Python reader for Natus NeuroWorks schema-9 ERD recordings
5
+ Author: ms903 and natus-erd-reader contributors
6
+ License-Expression: MIT AND BSD-3-Clause
7
+ Project-URL: Homepage, https://github.com/ms903/natus-erd-reader
8
+ Project-URL: Repository, https://github.com/ms903/natus-erd-reader
9
+ Project-URL: Issues, https://github.com/ms903/natus-erd-reader/issues
10
+ Project-URL: Changelog, https://github.com/ms903/natus-erd-reader/blob/main/CHANGELOG.md
11
+ Keywords: eeg,seeg,natus,neuroworks,erd
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ License-File: THIRD_PARTY_NOTICES.md
28
+ Requires-Dist: numpy>=1.24
29
+ Provides-Extra: dev
30
+ Requires-Dist: build>=1.2; extra == "dev"
31
+ Requires-Dist: twine>=5; extra == "dev"
32
+ Requires-Dist: mypy>=1.8; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ # natus-erd-reader
36
+
37
+ [![Tests and package build](https://github.com/ms903/natus-erd-reader/actions/workflows/ci.yml/badge.svg)](https://github.com/ms903/natus-erd-reader/actions/workflows/ci.yml)
38
+
39
+ A small Python library for native Natus NeuroWorks ERD recordings. It reads
40
+ sample windows through the STC/ETC indexes, decodes schema-9 packets and
41
+ extracts ENT annotations. It has **no command-line application, web server,
42
+ EDF reader, plotting tool or vendor SDK dependency**.
43
+
44
+ Version 0.2.1 adds header-driven sample rates and compatibility fixes while
45
+ preserving bounded file reads and allocations. Python 3.10+ is
46
+ required; NumPy is the only runtime dependency and is imported only when a
47
+ valid sample-read request needs an array. Opening a recording and inspecting
48
+ metadata or events do not initialize NumPy.
49
+
50
+ This is **experimental research software**, not a certified diagnostic device.
51
+ It is not affiliated with or endorsed by Natus. The supported hardware/layout
52
+ is deliberately narrow, and no guarantee is made against operating-system,
53
+ driver, hardware or numerical-library failures.
54
+
55
+ ## Installation
56
+
57
+ With Python 3.10 or newer, install **0.2.1** directly from GitHub Releases
58
+ (no Git installation or manual download required):
59
+
60
+ ```shell
61
+ python -m pip install --upgrade "https://github.com/ms903/natus-erd-reader/releases/download/v0.2.1/natus_erd_reader-0.2.1-py3-none-any.whl"
62
+ ```
63
+
64
+ Run this command in the Python environment where you want to use the reader.
65
+ NumPy is installed automatically if needed. To preserve an existing,
66
+ compatible NumPy installation, add `--no-deps`.
67
+
68
+ Once the version is available on [PyPI](https://pypi.org/project/natus-erd-reader/),
69
+ it can also be installed by name:
70
+
71
+ ```shell
72
+ python -m pip install --upgrade "natus-erd-reader==0.2.1"
73
+ ```
74
+
75
+ The GitHub Release command above does not depend on PyPI availability.
76
+
77
+ Check the installed version without loading NumPy or opening a recording:
78
+
79
+ ```shell
80
+ python -c "from importlib.metadata import version; print(version('natus-erd-reader'))"
81
+ ```
82
+
83
+ For development source from the current `main` branch, or from a local checkout:
84
+
85
+ ```shell
86
+ python -m pip install --upgrade "https://github.com/ms903/natus-erd-reader/archive/refs/heads/main.zip"
87
+ python -m pip install .
88
+ ```
89
+
90
+ Tagged releases are listed in
91
+ [GitHub Releases](https://github.com/ms903/natus-erd-reader/releases).
92
+ The distribution name is
93
+ `natus-erd-reader`; the import name is `natus_erd`.
94
+
95
+ ## Read a small window
96
+
97
+ ```python
98
+ from math import ceil
99
+
100
+ from natus_erd import NatusERDReader
101
+
102
+ # Pass the recording directory itself, or its .stc, .eeg or .erd file.
103
+ reader = NatusERDReader.open(r"D:\path\to\recording")
104
+ print(reader.info) # Structural counts, not patient header fields.
105
+
106
+ # Start is inclusive; stop is exclusive. Both are sample numbers.
107
+ # Target one second, using the actual ERD header rate and recording length.
108
+ stop = min(reader.info.n_samples, ceil(reader.info.sample_rate))
109
+ data = reader.read_samples(0, stop, channels=[0, 1])
110
+ print(data.shape, data.dtype) # (2, stop), float64
111
+
112
+ digital = reader.read_samples(0, stop, channels=[0, 1], units="digital")
113
+ events = reader.read_events()
114
+ ```
115
+
116
+ Arrays have shape `(channels, samples)`, always with `float64` dtype. The
117
+ default unit is microvolts (`units="uV"`); `"digital"` returns decoded native
118
+ counts without clipping to int16. Channel selection accepts zero-based
119
+ indices or names and preserves the requested order. Omitting `channels`
120
+ selects the first 256 signal channels. Shorted channels and known gaps are
121
+ returned as `NaN`, not zero.
122
+ STC stored-sample counts can be smaller than the stamp-defined time span;
123
+ the ETC packet stamps determine where samples are present. Leading, internal
124
+ and trailing gaps remain on the original time axis: they are not removed,
125
+ interpolated or compressed. Reads crossing a gap retain it as `NaN`.
126
+
127
+ Sample rates are read from the ERD header, not assumed to be 2048 Hz. For
128
+ example, 2048 samples cover four seconds at 512 Hz and one second at 2048 Hz.
129
+ The reader does not resample. A one-second request at an unusually high rate
130
+ can exceed the unchanged resource limits; use a shorter window or chunks.
131
+
132
+ Sample zero corresponds to the first STC stamp. `sample_to_stamp()` and
133
+ `stamp_to_sample()` convert between relative sample positions and native
134
+ stamps. `reader.info`, `reader.channels`, `reader.read_events()` and
135
+ `reader.validate()` expose structural metadata, channel descriptions, events
136
+ and a structural validation report, respectively. Validation is not a
137
+ complete decoding or clinical validation of every sample.
138
+
139
+ The reader does not recursively search arbitrary parent directories. Pass
140
+ the actual recording directory and keep its index and segment files together.
141
+ Filenames are matched case-insensitively, including their extensions, but
142
+ ambiguous case-folded duplicates are rejected. In a directory containing
143
+ multiple recordings, pass an explicit `.stc` or its matching `.eeg`; a
144
+ directory or `.erd` alone cannot select a unique recording. An ENT file is
145
+ optional; `.ent.old` is used only if `.ent` is absent, never to conceal a
146
+ corrupt `.ent`. Missing channel labels receive positional fallback names;
147
+ ambiguous vendor labels must be selected by index.
148
+ Only completed, static recordings are supported: do not modify the files or
149
+ continue acquisition into them while a reader is open. Size, timestamp and
150
+ file-identity checks detect some concurrent changes, but detection is
151
+ best-effort and does not provide a consistent snapshot of changing files.
152
+
153
+ ## Keep larger reads bounded
154
+
155
+ The default output budget is **64 MiB per `read_samples` call**. A request
156
+ that would exceed it raises `ResourceLimitError` before allocating the output
157
+ or importing NumPy. An output needs `selected_channels × samples × 8` bytes.
158
+ Thus a one-second, two-channel read at 2048 Hz needs 32 KiB for its output;
159
+ reading an entire long recording at once is intentionally rejected.
160
+ There is also a default limit of **131,072 samples per call** (64 seconds at
161
+ 2048 Hz), even when only one channel is selected, to bound decoding work.
162
+
163
+ Process long recordings incrementally:
164
+
165
+ ```python
166
+ for chunk in reader.iter_samples(
167
+ start=0,
168
+ stop=reader.info.n_samples,
169
+ chunk_samples=20480,
170
+ channels=[0, 1],
171
+ units="uV",
172
+ ):
173
+ # Analyze or write this chunk, then let it go before requesting the next.
174
+ print(chunk.shape)
175
+ ```
176
+
177
+ Each chunk is checked against the same read budget. **Do not collect all
178
+ chunks into a list** unless you deliberately want to retain that memory.
179
+ The caller owns arrays returned by the reader; repeated reads retained by
180
+ the caller can still consume arbitrary memory.
181
+
182
+ For a stricter output policy:
183
+
184
+ ```python
185
+ from natus_erd import NatusERDReader, ReadLimits
186
+
187
+ reader = NatusERDReader.open(
188
+ r"D:\path\to\recording",
189
+ limits=ReadLimits(max_read_bytes=16 * 1024 * 1024),
190
+ )
191
+ ```
192
+
193
+ Metadata lengths, parser complexity and packet sizes are also checked before
194
+ large reads. Packet decoding uses a bounded input buffer rather than reading
195
+ the entire compressed packet into memory. Invalid offsets, truncated data,
196
+ overlap and unsupported layouts raise explicit errors. These checks limit
197
+ the reader's own resource requests; **they are not a process-wide memory
198
+ limit or a sandbox**.
199
+
200
+ Other defaults include 8 MiB per metadata file or compressed packet, 256 KiB
201
+ per ENT record, 4,096 ENT records, parser depth 32 and a cache of four segment
202
+ indexes. `ReadLimits` exposes these policies explicitly; increasing a limit
203
+ should follow an assessment of the input and available resources.
204
+
205
+ ### NumPy backend resource use
206
+
207
+ Some NumPy builds initialize a multi-threaded numerical backend with a large
208
+ memory footprint, even though this reader does not need matrix multiplication.
209
+ The package does not modify process-wide thread settings or environment
210
+ variables. On a memory-constrained system, an application may explicitly set
211
+ its own backend policy **before importing NumPy or any library that imports
212
+ it**, for example at the very start of the application:
213
+
214
+ ```python
215
+ import os
216
+
217
+ os.environ["OPENBLAS_NUM_THREADS"] = "1" # For an OpenBLAS-backed NumPy build.
218
+
219
+ from natus_erd import NatusERDReader
220
+ ```
221
+
222
+ This setting is backend-specific and is not a remedy or guarantee for kernel
223
+ crashes. If a machine has frozen or restarted, do not retry an unbounded
224
+ workload; investigate the operating-system failure separately.
225
+
226
+ ## Supported format and errors
227
+
228
+ The supported layout is ERD schema 9, base schema 1, Quantum headbox type 20:
229
+ 276 recorded channels, 8-bit delta base and 6 discarded bits. Any finite
230
+ positive header sample rate is accepted if its recording duration is finite;
231
+ there is no sample-rate whitelist. All segments must have the same rate.
232
+ This does not extend support to other hardware or per-channel sampling rates.
233
+ STC schema 1, ETC schema 3 and ENT schema 3 are supported, each with base
234
+ schema 1. The ERD layout must contain a single headbox, an identity physical
235
+ channel mapping and a frequency-factor value of 32767 for every channel.
236
+ The first 256 channels use the Quantum AC calibration:
237
+
238
+ ```text
239
+ uV = digital × (-8711 / (2**21 - 0.5)) × 2**discard_bits
240
+ ```
241
+
242
+ The last 20 auxiliary channels remain in `reader.channels` and can be read
243
+ as digital counts, but have no verified physical-unit conversion. Native
244
+ UTF-8 segment and montage names are supported. ENT is parsed as data with a
245
+ restricted, bounded grammar; it is never evaluated as Python code.
246
+ Notes with unsupported or malformed text syntax may be skipped; a restricted
247
+ fallback extracts channel names from recognized nonstandard montage fields.
248
+ Consequently, `read_events()` is not guaranteed to include every annotation
249
+ stored by the vendor. Invalid binary lengths, schemas and exceeded parser
250
+ budgets still raise errors rather than being silently skipped.
251
+
252
+ `.eeg` is an accepted entry point, not the sample-data source. Samples come
253
+ from `.stc`/`.etc` indexes and `.erd` payloads. Other schemas/headboxes,
254
+ `Decimated`, video, EDF and additional auxiliary calibration are out of scope.
255
+
256
+ - `UnsupportedFormatError`: unsupported format or hardware layout.
257
+ - `DataIntegrityError`: malformed, truncated or inconsistent input.
258
+ - `ResourceLimitError`: a configured resource limit would be exceeded.
259
+ - `NatusERDError`: common base class for these reader exceptions.
260
+
261
+ Ordinary invalid API arguments may raise `TypeError`, `ValueError` or
262
+ `IndexError`. Filesystem failures may propagate the corresponding `OSError`.
263
+
264
+ ## Privacy, testing and maintenance
265
+
266
+ The library does not upload recordings or contact network services. Channel
267
+ labels and ENT event text can contain sensitive information; avoid printing
268
+ or sharing them without reviewing them. Only generic source and synthetic
269
+ tests belong in this repository. Real recordings, event text and derived
270
+ patient outputs must not be distributed.
271
+
272
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for the test/build workflow and
273
+ [SECURITY.md](SECURITY.md) for resource-limit boundaries. Public API changes
274
+ from 0.1.0 are listed in [CHANGELOG.md](CHANGELOG.md).
275
+
276
+ ## 中文速览
277
+
278
+ 这是只提供 Python API 的 Natus ERD 读取包,不再包含命令行、网页、EDF 或绘图功能。
279
+ 支持的范围限定为 schema 9 / Quantum headbox 20。默认单次输出上限为 64 MiB;
280
+ 采样率从 ERD 文件头读取,不再固定为 2048 Hz;采样空档保留原时间轴并返回 NaN。
281
+ 长记录请使用 `iter_samples()` 分块处理,不要把所有块保留在内存中。
282
+ 读取器会检查文件长度、索引和解析资源上限,但不能保证避免操作系统或硬件故障。
283
+ 仅查看记录信息和事件不会初始化 NumPy;真正读取数组时才加载它。
284
+
285
+ ## License
286
+
287
+ See [LICENSE](LICENSE) and [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)
288
+ for applicable terms and retained attribution. Both are included in source
289
+ and wheel distributions.