edfcore 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +319 -0
- package/dist/bytes/latin1.d.ts +43 -0
- package/dist/bytes/latin1.d.ts.map +1 -0
- package/dist/bytes/latin1.js +84 -0
- package/dist/bytes/latin1.js.map +1 -0
- package/dist/bytes/numbers.d.ts +45 -0
- package/dist/bytes/numbers.d.ts.map +1 -0
- package/dist/bytes/numbers.js +87 -0
- package/dist/bytes/numbers.js.map +1 -0
- package/dist/bytes/view.d.ts +27 -0
- package/dist/bytes/view.d.ts.map +1 -0
- package/dist/bytes/view.js +53 -0
- package/dist/bytes/view.js.map +1 -0
- package/dist/constants.d.ts +113 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +83 -0
- package/dist/constants.js.map +1 -0
- package/dist/decode/digital.d.ts +49 -0
- package/dist/decode/digital.d.ts.map +1 -0
- package/dist/decode/digital.js +180 -0
- package/dist/decode/digital.js.map +1 -0
- package/dist/decode/physical.d.ts +37 -0
- package/dist/decode/physical.d.ts.map +1 -0
- package/dist/decode/physical.js +178 -0
- package/dist/decode/physical.js.map +1 -0
- package/dist/diagnostics/codes.d.ts +38 -0
- package/dist/diagnostics/codes.d.ts.map +1 -0
- package/dist/diagnostics/codes.js +134 -0
- package/dist/diagnostics/codes.js.map +1 -0
- package/dist/diagnostics/collector.d.ts +76 -0
- package/dist/diagnostics/collector.d.ts.map +1 -0
- package/dist/diagnostics/collector.js +95 -0
- package/dist/diagnostics/collector.js.map +1 -0
- package/dist/diagnostics/format.d.ts +21 -0
- package/dist/diagnostics/format.d.ts.map +1 -0
- package/dist/diagnostics/format.js +125 -0
- package/dist/diagnostics/format.js.map +1 -0
- package/dist/errors.d.ts +143 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +135 -0
- package/dist/errors.js.map +1 -0
- package/dist/header/dates.d.ts +101 -0
- package/dist/header/dates.d.ts.map +1 -0
- package/dist/header/dates.js +378 -0
- package/dist/header/dates.js.map +1 -0
- package/dist/header/fields.d.ts +105 -0
- package/dist/header/fields.d.ts.map +1 -0
- package/dist/header/fields.js +363 -0
- package/dist/header/fields.js.map +1 -0
- package/dist/header/identification.d.ts +47 -0
- package/dist/header/identification.d.ts.map +1 -0
- package/dist/header/identification.js +156 -0
- package/dist/header/identification.js.map +1 -0
- package/dist/header/lookup.d.ts +33 -0
- package/dist/header/lookup.d.ts.map +1 -0
- package/dist/header/lookup.js +67 -0
- package/dist/header/lookup.js.map +1 -0
- package/dist/header/parse.d.ts +32 -0
- package/dist/header/parse.d.ts.map +1 -0
- package/dist/header/parse.js +319 -0
- package/dist/header/parse.js.map +1 -0
- package/dist/header/scale.d.ts +68 -0
- package/dist/header/scale.d.ts.map +1 -0
- package/dist/header/scale.js +154 -0
- package/dist/header/scale.js.map +1 -0
- package/dist/header/signals.d.ts +64 -0
- package/dist/header/signals.d.ts.map +1 -0
- package/dist/header/signals.js +374 -0
- package/dist/header/signals.js.map +1 -0
- package/dist/header/variant.d.ts +67 -0
- package/dist/header/variant.d.ts.map +1 -0
- package/dist/header/variant.js +180 -0
- package/dist/header/variant.js.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +59 -0
- package/dist/index.js.map +1 -0
- package/dist/inspect.d.ts +31 -0
- package/dist/inspect.d.ts.map +1 -0
- package/dist/inspect.js +165 -0
- package/dist/inspect.js.map +1 -0
- package/dist/io/blob.d.ts +14 -0
- package/dist/io/blob.d.ts.map +1 -0
- package/dist/io/blob.js +29 -0
- package/dist/io/blob.js.map +1 -0
- package/dist/io/bytes.d.ts +11 -0
- package/dist/io/bytes.d.ts.map +1 -0
- package/dist/io/bytes.js +26 -0
- package/dist/io/bytes.js.map +1 -0
- package/dist/io/cached.d.ts +19 -0
- package/dist/io/cached.d.ts.map +1 -0
- package/dist/io/cached.js +129 -0
- package/dist/io/cached.js.map +1 -0
- package/dist/io/http.d.ts +20 -0
- package/dist/io/http.d.ts.map +1 -0
- package/dist/io/http.js +225 -0
- package/dist/io/http.js.map +1 -0
- package/dist/io/read.d.ts +42 -0
- package/dist/io/read.d.ts.map +1 -0
- package/dist/io/read.js +119 -0
- package/dist/io/read.js.map +1 -0
- package/dist/io/source.d.ts +33 -0
- package/dist/io/source.d.ts.map +1 -0
- package/dist/io/source.js +72 -0
- package/dist/io/source.js.map +1 -0
- package/dist/node.d.ts +70 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +109 -0
- package/dist/node.js.map +1 -0
- package/dist/record-index.d.ts +58 -0
- package/dist/record-index.d.ts.map +1 -0
- package/dist/record-index.js +290 -0
- package/dist/record-index.js.map +1 -0
- package/dist/recording.d.ts +57 -0
- package/dist/recording.d.ts.map +1 -0
- package/dist/recording.js +178 -0
- package/dist/recording.js.map +1 -0
- package/dist/tal/annotations.d.ts +36 -0
- package/dist/tal/annotations.d.ts.map +1 -0
- package/dist/tal/annotations.js +437 -0
- package/dist/tal/annotations.js.map +1 -0
- package/dist/tal/grammar.d.ts +111 -0
- package/dist/tal/grammar.d.ts.map +1 -0
- package/dist/tal/grammar.js +324 -0
- package/dist/tal/grammar.js.map +1 -0
- package/dist/tal/ticks.d.ts +69 -0
- package/dist/tal/ticks.d.ts.map +1 -0
- package/dist/tal/ticks.js +137 -0
- package/dist/tal/ticks.js.map +1 -0
- package/dist/time/segments.d.ts +33 -0
- package/dist/time/segments.d.ts.map +1 -0
- package/dist/time/segments.js +87 -0
- package/dist/time/segments.js.map +1 -0
- package/dist/time/timeline.d.ts +62 -0
- package/dist/time/timeline.d.ts.map +1 -0
- package/dist/time/timeline.js +227 -0
- package/dist/time/timeline.js.map +1 -0
- package/dist/time/window.d.ts +54 -0
- package/dist/time/window.d.ts.map +1 -0
- package/dist/time/window.js +232 -0
- package/dist/time/window.js.map +1 -0
- package/dist/types.d.ts +409 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -0
- package/dist/validate.d.ts +53 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +483 -0
- package/dist/validate.js.map +1 -0
- package/package.json +77 -0
- package/src/bytes/latin1.ts +87 -0
- package/src/bytes/numbers.ts +115 -0
- package/src/bytes/view.ts +59 -0
- package/src/constants.ts +96 -0
- package/src/decode/digital.ts +258 -0
- package/src/decode/physical.ts +235 -0
- package/src/diagnostics/codes.ts +209 -0
- package/src/diagnostics/collector.ts +129 -0
- package/src/diagnostics/format.ts +143 -0
- package/src/errors.ts +208 -0
- package/src/header/dates.ts +453 -0
- package/src/header/fields.ts +455 -0
- package/src/header/identification.ts +196 -0
- package/src/header/lookup.ts +81 -0
- package/src/header/parse.ts +398 -0
- package/src/header/scale.ts +268 -0
- package/src/header/signals.ts +550 -0
- package/src/header/variant.ts +239 -0
- package/src/index.ts +151 -0
- package/src/inspect.ts +181 -0
- package/src/io/blob.ts +31 -0
- package/src/io/bytes.ts +29 -0
- package/src/io/cached.ts +136 -0
- package/src/io/http.ts +299 -0
- package/src/io/read.ts +152 -0
- package/src/io/source.ts +87 -0
- package/src/node.ts +138 -0
- package/src/record-index.ts +389 -0
- package/src/recording.ts +234 -0
- package/src/tal/annotations.ts +574 -0
- package/src/tal/grammar.ts +540 -0
- package/src/tal/ticks.ts +172 -0
- package/src/time/segments.ts +109 -0
- package/src/time/timeline.ts +290 -0
- package/src/time/window.ts +304 -0
- package/src/types.ts +490 -0
- package/src/validate.ts +610 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every public data shape in edfcore.
|
|
3
|
+
*
|
|
4
|
+
* Layer 0. Types only — this module emits no runtime code, so any layer may import it without
|
|
5
|
+
* creating a dependency edge.
|
|
6
|
+
*
|
|
7
|
+
* Two conventions run through the whole file:
|
|
8
|
+
*
|
|
9
|
+
* 1. A field that may be absent is declared `T | undefined` and is always present as a key.
|
|
10
|
+
* Optional (`?`) is reserved for *options* the caller passes in. Reading a result should
|
|
11
|
+
* never require knowing whether a key exists.
|
|
12
|
+
* 2. Anything that can be checked against the file is exposed twice — as parsed value and as
|
|
13
|
+
* the raw bytes it came from. edfcore never destroys evidence.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { EdfDiagnosticCode, EdfSeverity } from './diagnostics/codes.js';
|
|
17
|
+
|
|
18
|
+
export type {
|
|
19
|
+
EdfDiagnosticCode,
|
|
20
|
+
EdfKnownDiagnosticCode,
|
|
21
|
+
EdfSeverity,
|
|
22
|
+
} from './diagnostics/codes.js';
|
|
23
|
+
|
|
24
|
+
// ===========================================================================
|
|
25
|
+
// Structural platform shims
|
|
26
|
+
//
|
|
27
|
+
// edfcore compiles with `lib: ["ES2022"]` and `types: []`, so neither the DOM nor
|
|
28
|
+
// @types/node can leak into the published .d.ts. These structural interfaces are what the
|
|
29
|
+
// real platform types are checked against — `tests/types/shim-assignability.test-d.ts`
|
|
30
|
+
// asserts the real ones remain assignable.
|
|
31
|
+
// ===========================================================================
|
|
32
|
+
|
|
33
|
+
export interface AbortSignalLike {
|
|
34
|
+
readonly aborted: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface BlobLike {
|
|
38
|
+
readonly size: number;
|
|
39
|
+
slice(start?: number, end?: number): BlobLike;
|
|
40
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface HttpResponseLike {
|
|
44
|
+
readonly status: number;
|
|
45
|
+
readonly headers: { get(name: string): string | null };
|
|
46
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* `globalThis.fetch` is assignable to this.
|
|
51
|
+
*
|
|
52
|
+
* `signal` is deliberately absent from `init`: naming it would drag in the real `AbortSignal`
|
|
53
|
+
* by parameter contravariance, which is the exact DOM dependency these shims exist to avoid.
|
|
54
|
+
* It is still passed at runtime.
|
|
55
|
+
*/
|
|
56
|
+
export type FetchLike = (
|
|
57
|
+
url: string,
|
|
58
|
+
init: { headers: Record<string, string>; method?: string },
|
|
59
|
+
) => Promise<HttpResponseLike>;
|
|
60
|
+
|
|
61
|
+
// ===========================================================================
|
|
62
|
+
// I/O
|
|
63
|
+
// ===========================================================================
|
|
64
|
+
|
|
65
|
+
export interface ReadOptions {
|
|
66
|
+
readonly signal?: AbortSignalLike;
|
|
67
|
+
/** Refuse before allocating rather than dying inside it. Default 256 MiB. */
|
|
68
|
+
readonly maxMaterializeBytes?: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface ParseOptions {
|
|
72
|
+
/**
|
|
73
|
+
* When true the first would-be diagnostic throws `EdfFormatError` carrying it, so every
|
|
74
|
+
* `diagnostics` array is consequently empty. Check order is pinned and tested, which is what
|
|
75
|
+
* makes error identity stable across refactors.
|
|
76
|
+
*/
|
|
77
|
+
readonly strict?: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export type OpenOptions = ParseOptions & ReadOptions;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* A random-access byte range reader.
|
|
84
|
+
*
|
|
85
|
+
* CONTRACT, verified on every call including user-supplied sources: `read` resolves with
|
|
86
|
+
* EXACTLY `length` bytes or rejects. It never pads and never truncates. The returned array is
|
|
87
|
+
* owned by the caller, so a caching implementation must hand back a copy.
|
|
88
|
+
*/
|
|
89
|
+
export interface ByteSource {
|
|
90
|
+
readonly byteLength: number;
|
|
91
|
+
read(offset: number, length: number, options?: ReadOptions): Promise<Uint8Array>;
|
|
92
|
+
close?(): Promise<void> | void;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface HttpSourceOptions extends ReadOptions {
|
|
96
|
+
readonly fetch?: FetchLike;
|
|
97
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
98
|
+
readonly byteLength?: number;
|
|
99
|
+
readonly maxConcurrency?: number;
|
|
100
|
+
/** Server answered 200 instead of 206. Default false, i.e. throw rather than silently
|
|
101
|
+
* buffering gigabytes the caller did not ask for. */
|
|
102
|
+
readonly allowFullDownload?: boolean;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface CacheOptions {
|
|
106
|
+
/** Block size in bytes. Default 1 MiB. Blocks are byte-aligned, not record-aligned: the
|
|
107
|
+
* cache is format-independent by construction and never sees a header to learn a record
|
|
108
|
+
* size from. Round this to a multiple of `header.recordByteLength` yourself if you want
|
|
109
|
+
* block boundaries to fall on record boundaries. */
|
|
110
|
+
readonly blockBytes?: number;
|
|
111
|
+
/** LRU budget. Default 64 MiB. */
|
|
112
|
+
readonly maxBytes?: number;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ===========================================================================
|
|
116
|
+
// Header model
|
|
117
|
+
// ===========================================================================
|
|
118
|
+
|
|
119
|
+
export type EdfVariant = 'EDF' | 'EDF+C' | 'EDF+D' | 'BDF' | 'BDF+C' | 'BDF+D';
|
|
120
|
+
|
|
121
|
+
export interface EdfCalendarDate {
|
|
122
|
+
readonly year: number;
|
|
123
|
+
/** 1-12. Not a JavaScript month index. */
|
|
124
|
+
readonly month: number;
|
|
125
|
+
readonly day: number;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface EdfClockTime {
|
|
129
|
+
readonly hour: number;
|
|
130
|
+
readonly minute: number;
|
|
131
|
+
readonly second: number;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* EDF records local time at the patient with no timezone, so edfcore never produces a `Date`:
|
|
136
|
+
* a `Date` silently applies the reader's zone, and is worst exactly at DST boundaries.
|
|
137
|
+
*/
|
|
138
|
+
export interface EdfStartTime {
|
|
139
|
+
/** From the `dd.mm.yy` header field, through the 1985-2084 rule. */
|
|
140
|
+
readonly headerDate: EdfCalendarDate | undefined;
|
|
141
|
+
/** From the EDF+ recording-identification `Startdate` subfield — the only unambiguous
|
|
142
|
+
* four-digit year, and the only way past 2084. */
|
|
143
|
+
readonly recordingIdDate: EdfCalendarDate | undefined;
|
|
144
|
+
readonly resolvedDate: EdfCalendarDate | undefined;
|
|
145
|
+
readonly dateSource: 'headerField' | 'recordingIdField' | 'none';
|
|
146
|
+
readonly clock: EdfClockTime;
|
|
147
|
+
readonly secondsSinceMidnight: number;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface EdfPatientId {
|
|
151
|
+
readonly raw: string;
|
|
152
|
+
readonly conformant: boolean;
|
|
153
|
+
readonly code: string | undefined;
|
|
154
|
+
readonly sex: 'F' | 'M' | undefined;
|
|
155
|
+
readonly birthDate: EdfCalendarDate | undefined;
|
|
156
|
+
readonly name: string | undefined;
|
|
157
|
+
readonly extraSubfields: readonly string[];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export interface EdfRecordingId {
|
|
161
|
+
readonly raw: string;
|
|
162
|
+
readonly conformant: boolean;
|
|
163
|
+
readonly startDate: EdfCalendarDate | undefined;
|
|
164
|
+
readonly investigationCode: string | undefined;
|
|
165
|
+
readonly technicianCode: string | undefined;
|
|
166
|
+
readonly equipmentCode: string | undefined;
|
|
167
|
+
readonly extraSubfields: readonly string[];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* `physical = bitValue * (offset + digital)`.
|
|
172
|
+
*
|
|
173
|
+
* This is EDFlib's exact expression, kept verbatim for float64 bit-parity with pyEDFlib.
|
|
174
|
+
* It is *not* the numerically better form, and it must not be "simplified" — see
|
|
175
|
+
* `src/decode/physical.ts`.
|
|
176
|
+
*/
|
|
177
|
+
export interface EdfScale {
|
|
178
|
+
readonly bitValue: number;
|
|
179
|
+
readonly offset: number;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Header text exactly as written, before trimming or interpretation. */
|
|
183
|
+
export interface EdfRawSignalFields {
|
|
184
|
+
readonly label: string;
|
|
185
|
+
readonly transducerType: string;
|
|
186
|
+
readonly physicalDimension: string;
|
|
187
|
+
readonly physicalMinimum: string;
|
|
188
|
+
readonly physicalMaximum: string;
|
|
189
|
+
readonly digitalMinimum: string;
|
|
190
|
+
readonly digitalMaximum: string;
|
|
191
|
+
readonly prefiltering: string;
|
|
192
|
+
readonly samplesPerRecord: string;
|
|
193
|
+
readonly reserved: string;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface EdfRawHeaderFields {
|
|
197
|
+
readonly version: string;
|
|
198
|
+
readonly patientId: string;
|
|
199
|
+
readonly recordingId: string;
|
|
200
|
+
readonly startDate: string;
|
|
201
|
+
readonly startTime: string;
|
|
202
|
+
readonly headerByteLength: string;
|
|
203
|
+
readonly reserved: string;
|
|
204
|
+
readonly recordCount: string;
|
|
205
|
+
readonly recordDuration: string;
|
|
206
|
+
readonly signalCount: string;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export interface EdfSignal {
|
|
210
|
+
readonly index: number;
|
|
211
|
+
readonly kind: 'data' | 'annotations';
|
|
212
|
+
/** Trimmed. `raw.label` keeps the padding. */
|
|
213
|
+
readonly label: string;
|
|
214
|
+
readonly transducerType: string;
|
|
215
|
+
readonly prefiltering: string;
|
|
216
|
+
/** Trimmed. `raw.physicalDimension` keeps the padding and the exact bytes. */
|
|
217
|
+
readonly physicalDimension: string;
|
|
218
|
+
/** Normalised for comparison only: the several encodings of micro all become `u`. */
|
|
219
|
+
readonly unit: string;
|
|
220
|
+
readonly physicalMinimum: number;
|
|
221
|
+
/** MAY be less than the minimum: that is a negative amplifier gain, it is legal, and
|
|
222
|
+
* edfcore never "fixes" it. */
|
|
223
|
+
readonly physicalMaximum: number;
|
|
224
|
+
readonly digitalMinimum: number;
|
|
225
|
+
readonly digitalMaximum: number;
|
|
226
|
+
/** Authoritative. Sample indexing uses this, never a rate. */
|
|
227
|
+
readonly samplesPerRecord: number;
|
|
228
|
+
/** Derived. `undefined` exactly when `recordDurationSeconds === 0`, which is legal.
|
|
229
|
+
* Never index by this. */
|
|
230
|
+
readonly sampleRateHz: number | undefined;
|
|
231
|
+
readonly sampleCount: number;
|
|
232
|
+
/** `undefined` when scaling is impossible or unsafe: a degenerate or inverted digital
|
|
233
|
+
* range, a degenerate physical range, or a log-transformed channel. `toPhysical` then
|
|
234
|
+
* throws `EdfScalingError`; `decodeDigital` keeps working. */
|
|
235
|
+
readonly scale: EdfScale | undefined;
|
|
236
|
+
/** Byte offset of this signal's block within one data record. */
|
|
237
|
+
readonly recordByteOffset: number;
|
|
238
|
+
readonly recordByteLength: number;
|
|
239
|
+
readonly raw: EdfRawSignalFields;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export interface EdfHeader {
|
|
243
|
+
readonly variant: EdfVariant;
|
|
244
|
+
readonly continuity: 'continuous' | 'discontinuous';
|
|
245
|
+
readonly bytesPerSample: 2 | 3;
|
|
246
|
+
/** Always the computed `256 * (ns + 1)`, never the declared value. */
|
|
247
|
+
readonly headerByteLength: number;
|
|
248
|
+
/** What the file claims at offset 184. Kept so a mismatch stays visible. */
|
|
249
|
+
readonly declaredHeaderByteLength: number;
|
|
250
|
+
readonly recordByteLength: number;
|
|
251
|
+
readonly dataByteLength: number;
|
|
252
|
+
/** MAY legitimately be 0 — then never divide by it. */
|
|
253
|
+
readonly recordDurationSeconds: number;
|
|
254
|
+
readonly recordDurationTicks: bigint;
|
|
255
|
+
/** Resolved and non-negative. */
|
|
256
|
+
readonly recordCount: number;
|
|
257
|
+
/** Verbatim. `-1` means the writer never closed the file. */
|
|
258
|
+
readonly declaredRecordCount: number;
|
|
259
|
+
readonly recordCountSource: 'headerField' | 'sourceByteLength';
|
|
260
|
+
readonly startTime: EdfStartTime;
|
|
261
|
+
readonly patient: EdfPatientId;
|
|
262
|
+
readonly recording: EdfRecordingId;
|
|
263
|
+
readonly signals: readonly EdfSignal[];
|
|
264
|
+
readonly dataSignalIndices: readonly number[];
|
|
265
|
+
readonly annotationSignalIndices: readonly number[];
|
|
266
|
+
/** The full 44 reserved bytes, verbatim. */
|
|
267
|
+
readonly reserved: string;
|
|
268
|
+
readonly raw: EdfRawHeaderFields;
|
|
269
|
+
/** The whole header, for hexdumps and bug reports. */
|
|
270
|
+
readonly rawBytes: Uint8Array;
|
|
271
|
+
readonly diagnostics: readonly EdfDiagnostic[];
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ===========================================================================
|
|
275
|
+
// Time
|
|
276
|
+
// ===========================================================================
|
|
277
|
+
|
|
278
|
+
/** Start plus count, never start plus end: there is no inclusive/exclusive ambiguity. */
|
|
279
|
+
export interface RecordRange {
|
|
280
|
+
readonly start: number;
|
|
281
|
+
readonly count: number;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export interface EdfSegment {
|
|
285
|
+
readonly index: number;
|
|
286
|
+
readonly records: RecordRange;
|
|
287
|
+
readonly startSeconds: number;
|
|
288
|
+
readonly startTicks: bigint;
|
|
289
|
+
readonly durationSeconds: number;
|
|
290
|
+
readonly endSeconds: number;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export interface EdfGap {
|
|
294
|
+
readonly beforeSegmentIndex: number;
|
|
295
|
+
readonly afterSegmentIndex: number;
|
|
296
|
+
readonly startSeconds: number;
|
|
297
|
+
readonly endSeconds: number;
|
|
298
|
+
readonly durationSeconds: number;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export interface EdfLocation {
|
|
302
|
+
readonly recordIndex: number;
|
|
303
|
+
readonly recordStartSeconds: number;
|
|
304
|
+
readonly offsetInRecordSeconds: number;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export interface EdfTimeline {
|
|
308
|
+
readonly recordCount: number;
|
|
309
|
+
readonly recordDurationSeconds: number;
|
|
310
|
+
/** Sub-second start carried by record 0's timekeeping TAL. In [0, 1). */
|
|
311
|
+
readonly startOffsetSeconds: number;
|
|
312
|
+
readonly startOffsetTicks: bigint;
|
|
313
|
+
/** Last record end minus first record start. Includes gaps. */
|
|
314
|
+
readonly spanSeconds: number;
|
|
315
|
+
/** Sum of record durations. Equals `spanSeconds` exactly when the file is contiguous. */
|
|
316
|
+
readonly coveredSeconds: number;
|
|
317
|
+
readonly diagnostics: readonly EdfDiagnostic[];
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export interface EdfRecordIndex {
|
|
321
|
+
/** `probed` = record 0 and the last record only. `complete` = every record verified. */
|
|
322
|
+
readonly coverage: 'probed' | 'complete';
|
|
323
|
+
readonly recordCount: number;
|
|
324
|
+
/** Present only when `coverage === 'complete'`. Deliberately absent otherwise: no property
|
|
325
|
+
* on this object may ever read as "continuous" when we have not checked. */
|
|
326
|
+
readonly segments: readonly EdfSegment[] | undefined;
|
|
327
|
+
readonly gaps: readonly EdfGap[] | undefined;
|
|
328
|
+
/** One targeted read of that record's annotation region. Memoised. */
|
|
329
|
+
onsetTicks(recordIndex: number, options?: ReadOptions): Promise<bigint>;
|
|
330
|
+
/** O(log recordCount) probes. Onsets are monotonic; any observed violation is fatal. */
|
|
331
|
+
locate(seconds: number, options?: ReadOptions): Promise<EdfLocation | undefined>;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export interface BuildIndexOptions extends ParseOptions, ReadOptions {
|
|
335
|
+
readonly onProgress?: (done: number, total: number) => void;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// ===========================================================================
|
|
339
|
+
// Samples and annotations
|
|
340
|
+
// ===========================================================================
|
|
341
|
+
|
|
342
|
+
export interface EdfChunkSignal {
|
|
343
|
+
readonly signalIndex: number;
|
|
344
|
+
/** The truth. Never padded to a round number. */
|
|
345
|
+
readonly sampleCount: number;
|
|
346
|
+
readonly digital: Int32Array;
|
|
347
|
+
/** Index of the first sample on this signal's own sample grid. */
|
|
348
|
+
readonly firstSampleIndex: number;
|
|
349
|
+
/** Start of this signal's samples. A record-aligned read gives every signal the same
|
|
350
|
+
* value, since they all begin at the same record; it becomes genuinely per-signal after
|
|
351
|
+
* `trimToWindow`, where each signal's own sample grid decides the boundary. */
|
|
352
|
+
readonly startSeconds: number;
|
|
353
|
+
/** Counted during decode, so it costs nothing. A non-zero count means the declared
|
|
354
|
+
* digital range is wrong, not that the samples are. */
|
|
355
|
+
readonly outOfDigitalRangeCount: number;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export interface EdfChunk {
|
|
359
|
+
readonly records: RecordRange;
|
|
360
|
+
readonly startSeconds: number;
|
|
361
|
+
readonly durationSeconds: number;
|
|
362
|
+
readonly byteOffset: number;
|
|
363
|
+
/** Bytes actually read from the source. Makes overread visible instead of invisible. */
|
|
364
|
+
readonly byteLength: number;
|
|
365
|
+
readonly signals: readonly EdfChunkSignal[];
|
|
366
|
+
readonly precededByGap: EdfGap | undefined;
|
|
367
|
+
readonly diagnostics: readonly EdfDiagnostic[];
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export interface EdfAnnotation {
|
|
371
|
+
/** Verbatim on-disk value, relative to the header startdate/starttime (EDF+ 2.2.4). */
|
|
372
|
+
readonly onsetSecondsFromHeaderStart: number;
|
|
373
|
+
/** Rebased to the first record's true start — the EDFlib/pyEDFlib/MNE convention. */
|
|
374
|
+
readonly onsetSecondsFromFirstRecord: number;
|
|
375
|
+
/** Exact, in 100 ns units. Compare event times with this, never with the floats. */
|
|
376
|
+
readonly onsetTicks: bigint;
|
|
377
|
+
/** The original digits, so precision is never lost to a round-trip. */
|
|
378
|
+
readonly onsetRaw: string;
|
|
379
|
+
readonly durationSeconds: number | undefined;
|
|
380
|
+
readonly durationTicks: bigint | undefined;
|
|
381
|
+
readonly durationRaw: string | undefined;
|
|
382
|
+
/** Verbatim. Never trimmed, never case-folded. */
|
|
383
|
+
readonly text: string;
|
|
384
|
+
/** From the EDF+ `description@@channel` convention. */
|
|
385
|
+
readonly channelLabel: string | undefined;
|
|
386
|
+
readonly signalIndex: number;
|
|
387
|
+
readonly recordIndex: number;
|
|
388
|
+
readonly byteOffsetInRecord: number;
|
|
389
|
+
readonly textEncoding: 'utf-8' | 'latin-1-fallback';
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
export interface EdfAnnotationsResult {
|
|
393
|
+
/** Timekeeping TALs and empty texts excluded. Stable sort by
|
|
394
|
+
* (onsetTicks, signalIndex, byteOffsetInRecord). */
|
|
395
|
+
readonly annotations: readonly EdfAnnotation[];
|
|
396
|
+
/** One entry per record in the decoded range. This is the primitive the timeline is
|
|
397
|
+
* built from. */
|
|
398
|
+
readonly recordOnsetTicks: BigInt64Array;
|
|
399
|
+
readonly diagnostics: readonly EdfDiagnostic[];
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// ===========================================================================
|
|
403
|
+
// Diagnostics
|
|
404
|
+
// ===========================================================================
|
|
405
|
+
|
|
406
|
+
export interface EdfDiagnostic {
|
|
407
|
+
readonly code: EdfDiagnosticCode;
|
|
408
|
+
readonly severity: EdfSeverity;
|
|
409
|
+
/** Names the field, the raw bytes as written, the rule, and an actionable next step. */
|
|
410
|
+
readonly message: string;
|
|
411
|
+
readonly field: string | undefined;
|
|
412
|
+
readonly byteOffset: number | undefined;
|
|
413
|
+
readonly byteLength: number | undefined;
|
|
414
|
+
readonly rawBytes: Uint8Array | undefined;
|
|
415
|
+
readonly raw: string | undefined;
|
|
416
|
+
readonly expected: string | undefined;
|
|
417
|
+
readonly actual: string | undefined;
|
|
418
|
+
readonly signalIndex: number | undefined;
|
|
419
|
+
readonly recordIndex: number | undefined;
|
|
420
|
+
/** A spec clause, e.g. 'EDF+ additional specification 5'. Verifiable and stable, unlike a
|
|
421
|
+
* claim about how some other library behaves. */
|
|
422
|
+
readonly specReference: string | undefined;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// ===========================================================================
|
|
426
|
+
// Recording and selections
|
|
427
|
+
// ===========================================================================
|
|
428
|
+
|
|
429
|
+
export interface EdfRecording {
|
|
430
|
+
readonly source: ByteSource;
|
|
431
|
+
readonly header: EdfHeader;
|
|
432
|
+
readonly timeline: EdfTimeline;
|
|
433
|
+
readonly index: EdfRecordIndex;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
export interface RecordSelection {
|
|
437
|
+
readonly records: RecordRange;
|
|
438
|
+
/** Required, with no "all signals" default: a 256-channel file must never be read
|
|
439
|
+
* wholesale because an argument was omitted. */
|
|
440
|
+
readonly signalIndices: readonly number[];
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
export interface WindowSelection {
|
|
444
|
+
readonly startSeconds: number;
|
|
445
|
+
readonly durationSeconds: number;
|
|
446
|
+
readonly signalIndices: readonly number[];
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export interface DecodeAnnotationsOptions extends ParseOptions {
|
|
450
|
+
/** Defaults to every annotation signal. Only the first carries timekeeping. */
|
|
451
|
+
readonly signalIndices?: readonly number[];
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/** Header-only triage. Reads at most 128 KiB and never throws on malformed content. */
|
|
455
|
+
export interface EdfInspection {
|
|
456
|
+
readonly ok: boolean;
|
|
457
|
+
readonly variant: EdfVariant | undefined;
|
|
458
|
+
readonly header: EdfHeader | undefined;
|
|
459
|
+
readonly byteLength: number;
|
|
460
|
+
readonly bytesRead: number;
|
|
461
|
+
readonly headerBytes: Uint8Array | undefined;
|
|
462
|
+
readonly diagnostics: readonly EdfDiagnostic[];
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// ===========================================================================
|
|
466
|
+
// Validation (edfcore/validate)
|
|
467
|
+
// ===========================================================================
|
|
468
|
+
|
|
469
|
+
export interface ValidateOptions extends ReadOptions {
|
|
470
|
+
/** Reuse a completed index so conformance costs one traversal, not two. */
|
|
471
|
+
readonly index?: EdfRecordIndex;
|
|
472
|
+
readonly scanSamples?: boolean;
|
|
473
|
+
readonly onProgress?: (done: number, total: number) => void;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
export interface ObservedSignalStats {
|
|
477
|
+
readonly signalIndex: number;
|
|
478
|
+
readonly observedDigitalMin: number;
|
|
479
|
+
readonly observedDigitalMax: number;
|
|
480
|
+
readonly outOfDigitalRangeCount: number;
|
|
481
|
+
readonly sampleCount: number;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export interface ValidationReport {
|
|
485
|
+
readonly ok: boolean;
|
|
486
|
+
readonly diagnostics: readonly EdfDiagnostic[];
|
|
487
|
+
readonly recordsScanned: number;
|
|
488
|
+
readonly bytesRead: number;
|
|
489
|
+
readonly signalStats: readonly ObservedSignalStats[];
|
|
490
|
+
}
|