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
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Digital-to-physical scale, and the decision to refuse one.
|
|
3
|
+
*
|
|
4
|
+
* Layer 2. Sole owner of `EdfScale` construction and of the four conditions under which a
|
|
5
|
+
* signal gets `scale: undefined` instead of a fabricated gain.
|
|
6
|
+
*
|
|
7
|
+
* The expression is EDFlib's, verbatim:
|
|
8
|
+
*
|
|
9
|
+
* bitValue = (physicalMaximum - physicalMinimum) / (digitalMaximum - digitalMinimum)
|
|
10
|
+
* offset = physicalMaximum / bitValue - digitalMaximum
|
|
11
|
+
* physical = bitValue * (offset + digital)
|
|
12
|
+
*
|
|
13
|
+
* The textbook `physicalMinimum + (digital - digitalMinimum) * gain` form is numerically
|
|
14
|
+
* *better* and is deliberately not used: it shifts up to ~45% of samples by one ULP on
|
|
15
|
+
* asymmetric ranges, which would forfeit float64 bit-parity with pyEDFlib for a divergence ten
|
|
16
|
+
* orders of magnitude below the quantisation floor. Do not "simplify" this.
|
|
17
|
+
*
|
|
18
|
+
* `physicalMinimum > physicalMaximum` is legal — it is how a negative amplifier gain is
|
|
19
|
+
* written — and produces a negative `bitValue`. The two are never swapped: a silent polarity
|
|
20
|
+
* flip is a clinically wrong result that looks perfectly normal.
|
|
21
|
+
*/
|
|
22
|
+
import { trimEdfField } from '../bytes/latin1.js';
|
|
23
|
+
/** EDF+ 'edffloat': a channel whose values are log-compressed, so the linear map is wrong. */
|
|
24
|
+
const LOG_TRANSFORMED_DIMENSION = 'Filtered';
|
|
25
|
+
/** U+00B5 MICRO SIGN, U+03BC GREEK SMALL LETTER MU. A raw 0xB5 header byte decodes to the
|
|
26
|
+
* former through Latin-1, so both spellings and the byte are covered. */
|
|
27
|
+
const MICRO_SIGNS = /[µμ]/g;
|
|
28
|
+
const MICRO_REPLACEMENT = 'u';
|
|
29
|
+
const RANGE_SPEC_REFERENCE = 'EDF+ additional specification 5';
|
|
30
|
+
/**
|
|
31
|
+
* Collapse the encodings of micro to `'u'` so units can be compared.
|
|
32
|
+
*
|
|
33
|
+
* For comparison ONLY: `signal.physicalDimension` stays exactly as the file wrote it. Nothing
|
|
34
|
+
* else is touched — case is meaningful (`mV` is not `MV`), and edfcore does not normalise units
|
|
35
|
+
* to SI volts.
|
|
36
|
+
*/
|
|
37
|
+
export function normaliseUnit(physicalDimension) {
|
|
38
|
+
return trimEdfField(physicalDimension).replace(MICRO_SIGNS, MICRO_REPLACEMENT);
|
|
39
|
+
}
|
|
40
|
+
function isLogTransformed(physicalDimension) {
|
|
41
|
+
return trimEdfField(physicalDimension) === LOG_TRANSFORMED_DIMENSION;
|
|
42
|
+
}
|
|
43
|
+
/** A diagnostic init with the fields that are only known when the caller supplied context. */
|
|
44
|
+
function scaleDiagnostic(input, code, message, field, expected, actual, specReference) {
|
|
45
|
+
const byteOffset = input.byteOffsets?.[field];
|
|
46
|
+
const raw = input.raw?.[field];
|
|
47
|
+
return {
|
|
48
|
+
code,
|
|
49
|
+
message,
|
|
50
|
+
field,
|
|
51
|
+
signalIndex: input.signalIndex,
|
|
52
|
+
expected,
|
|
53
|
+
actual,
|
|
54
|
+
specReference,
|
|
55
|
+
...(byteOffset === undefined ? {} : { byteOffset }),
|
|
56
|
+
...(raw === undefined ? {} : { raw }),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function describe(input) {
|
|
60
|
+
return `signal ${input.signalIndex} ${JSON.stringify(input.label)}`;
|
|
61
|
+
}
|
|
62
|
+
/** `'"0 " at byte offset 3400'`, or the value alone when the caller gave no context. */
|
|
63
|
+
function quoteField(input, field) {
|
|
64
|
+
const raw = input.raw?.[field];
|
|
65
|
+
const byteOffset = input.byteOffsets?.[field];
|
|
66
|
+
const rawText = raw === undefined ? '' : `raw ${JSON.stringify(raw)}`;
|
|
67
|
+
const offsetText = byteOffset === undefined ? '' : `at byte offset ${byteOffset}`;
|
|
68
|
+
const parts = [rawText, offsetText].filter((part) => part.length > 0);
|
|
69
|
+
return parts.length === 0 ? '' : ` (${parts.join(' ')})`;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The scale for one signal, or `undefined` when edfcore refuses to invent one.
|
|
73
|
+
*
|
|
74
|
+
* The refusals are checked in this fixed order: degenerate digital range, degenerate physical
|
|
75
|
+
* range, inverted digital range, log-transformed channel, and finally a derived gain that is not
|
|
76
|
+
* a usable float64 number. Each is deferred-fatal — `decodeDigital` keeps working on the signal
|
|
77
|
+
* and `toPhysical` throws `EdfScalingError`.
|
|
78
|
+
*
|
|
79
|
+
* A non-finite input returns `undefined` without a diagnostic: the only way to get here with
|
|
80
|
+
* one is a numeric field that failed its grammar, which the caller has already reported
|
|
81
|
+
* against the field itself.
|
|
82
|
+
*/
|
|
83
|
+
export function buildScale(input, sink) {
|
|
84
|
+
const { physicalMinimum, physicalMaximum, digitalMinimum, digitalMaximum } = input;
|
|
85
|
+
if (!Number.isFinite(physicalMinimum) ||
|
|
86
|
+
!Number.isFinite(physicalMaximum) ||
|
|
87
|
+
!Number.isFinite(digitalMinimum) ||
|
|
88
|
+
!Number.isFinite(digitalMaximum)) {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
if (digitalMaximum === digitalMinimum) {
|
|
92
|
+
sink.report(scaleDiagnostic(input, 'DEGENERATE_DIGITAL_RANGE', `${describe(input)} declares digitalMinimum == digitalMaximum == ${digitalMinimum}, ` +
|
|
93
|
+
'so physical scale is undefined (division by zero)' +
|
|
94
|
+
`${quoteField(input, 'digitalMinimum')}. ${RANGE_SPEC_REFERENCE}: "Digital maximum ` +
|
|
95
|
+
'must be larger than Digital minimum". Next: decodeDigital() still works on this ' +
|
|
96
|
+
'signal; edfcore will not invent a gain.', 'digitalMinimum', 'digitalMaximum > digitalMinimum', `${digitalMinimum} == ${digitalMaximum}`, RANGE_SPEC_REFERENCE));
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
if (physicalMaximum === physicalMinimum) {
|
|
100
|
+
sink.report(scaleDiagnostic(input, 'DEGENERATE_PHYSICAL_RANGE', `${describe(input)} declares physicalMinimum == physicalMaximum == ` +
|
|
101
|
+
`${physicalMinimum}, so every digital value would map to the same physical value` +
|
|
102
|
+
`${quoteField(input, 'physicalMinimum')}. ${RANGE_SPEC_REFERENCE}: "Physical ` +
|
|
103
|
+
'maximum must differ from Physical minimum". Next: decodeDigital() still works on ' +
|
|
104
|
+
'this signal; edfcore will not invent a gain.', 'physicalMinimum', 'physicalMaximum != physicalMinimum', `${physicalMinimum} == ${physicalMaximum}`, RANGE_SPEC_REFERENCE));
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
if (digitalMinimum > digitalMaximum) {
|
|
108
|
+
sink.report(scaleDiagnostic(input, 'INVERTED_DIGITAL_RANGE', `${describe(input)} declares digitalMinimum ${digitalMinimum} greater than ` +
|
|
109
|
+
`digitalMaximum ${digitalMaximum}${quoteField(input, 'digitalMinimum')}. ` +
|
|
110
|
+
`${RANGE_SPEC_REFERENCE}: "Digital maximum must be larger than Digital minimum". ` +
|
|
111
|
+
'Unlike an inverted physical range this has no sanctioned meaning, so edfcore will ' +
|
|
112
|
+
'not guess whether the fields were swapped or the samples inverted. Next: ' +
|
|
113
|
+
'decodeDigital() still works on this signal.', 'digitalMinimum', 'digitalMaximum > digitalMinimum', `${digitalMinimum} > ${digitalMaximum}`, RANGE_SPEC_REFERENCE));
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
if (isLogTransformed(input.physicalDimension)) {
|
|
117
|
+
sink.report(scaleDiagnostic(input, 'LOG_TRANSFORMED_CHANNEL', `${describe(input)} has physical dimension exactly "${LOG_TRANSFORMED_DIMENSION}"` +
|
|
118
|
+
`${quoteField(input, 'physicalDimension')}, which marks a logarithmically ` +
|
|
119
|
+
'transformed channel (EDF specification edffloat.html): its samples are ' +
|
|
120
|
+
'log-compressed, so the linear digital-to-physical map would be wrong by orders of ' +
|
|
121
|
+
'magnitude. Next: decodeDigital() still works on this signal; edfcore detects the ' +
|
|
122
|
+
'transform and refuses it rather than applying an inverse it cannot verify.', 'physicalDimension', 'a linear physical dimension', LOG_TRANSFORMED_DIMENSION, 'EDF specification edffloat.html'));
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
if (physicalMinimum > physicalMaximum) {
|
|
126
|
+
sink.report(scaleDiagnostic(input, 'INVERTED_PHYSICAL_RANGE', `${describe(input)} declares physicalMinimum ${physicalMinimum} greater than ` +
|
|
127
|
+
`physicalMaximum ${physicalMaximum}${quoteField(input, 'physicalMinimum')}. This ` +
|
|
128
|
+
'is legal and encodes a negative amplifier gain (EDF FAQ Q6), so bitValue comes ' +
|
|
129
|
+
'out negative. Next: nothing to do — edfcore never swaps the two, because a silent ' +
|
|
130
|
+
'polarity flip is a clinically wrong result that looks normal.', 'physicalMinimum', 'physicalMinimum < physicalMaximum, or a deliberate negative gain', `${physicalMinimum} > ${physicalMaximum}`, 'EDF FAQ Q6 (negative amplifier gain)'));
|
|
131
|
+
}
|
|
132
|
+
// EDFlib's exact expression. See the module comment before changing either line.
|
|
133
|
+
const bitValue = (physicalMaximum - physicalMinimum) / (digitalMaximum - digitalMinimum);
|
|
134
|
+
const offset = physicalMaximum / bitValue - digitalMaximum;
|
|
135
|
+
// The four INPUTS being finite does not make the DERIVED pair finite. An 8-byte field may write
|
|
136
|
+
// an exponent, so a physical range can underflow against the digital range (`0`..`5E-324` over
|
|
137
|
+
// -1..1 gives bitValue 0 and offset Infinity) or overflow it (`-9.9E307`..`9.9E307` gives
|
|
138
|
+
// bitValue Infinity). Either way `bitValue * (offset + digital)` is NaN or +/-Infinity for every
|
|
139
|
+
// sample in the channel, which is a fabricated gain by another name — so the scale is refused
|
|
140
|
+
// exactly as the degenerate cases above are, and `decodeDigital` keeps working.
|
|
141
|
+
if (bitValue === 0 || !Number.isFinite(bitValue) || !Number.isFinite(offset)) {
|
|
142
|
+
sink.report(scaleDiagnostic(input, 'DEGENERATE_PHYSICAL_RANGE', `${describe(input)} declares the physical range ${physicalMinimum}..${physicalMaximum} ` +
|
|
143
|
+
`over the digital range ${digitalMinimum}..${digitalMaximum}` +
|
|
144
|
+
`${quoteField(input, 'physicalMaximum')}, and the gain those imply is not a usable ` +
|
|
145
|
+
`float64 number: bitValue is ${bitValue} and offset is ${offset}, so ` +
|
|
146
|
+
'bitValue * (offset + digital) is NaN or infinite for every sample. ' +
|
|
147
|
+
`${RANGE_SPEC_REFERENCE}. Next: decodeDigital() still works on this signal; edfcore ` +
|
|
148
|
+
'will not invent a gain, and a physical range this far outside float64 is a corrupt ' +
|
|
149
|
+
'field rather than a calibration.', 'physicalMaximum', 'a physical range that defines a finite, non-zero gain', `bitValue ${bitValue}, offset ${offset}`, RANGE_SPEC_REFERENCE));
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
return { bitValue, offset };
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=scale.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scale.js","sourceRoot":"","sources":["../../src/header/scale.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The per-signal header, and where each signal's bytes live inside a data record.
|
|
3
|
+
*
|
|
4
|
+
* Layer 2. Sole owner of `recordByteOffset` arithmetic.
|
|
5
|
+
*
|
|
6
|
+
* The per-signal header is FIELD-MAJOR: all `ns` labels, then all `ns` transducer types, and so
|
|
7
|
+
* on for ten fields, `ns * 256` bytes in total. It is NOT one 256-byte struct per signal, and
|
|
8
|
+
* reading it as one is the single most common EDF bug — it happens to produce plausible output
|
|
9
|
+
* for a one-signal file, which is exactly how it survives a first test.
|
|
10
|
+
*
|
|
11
|
+
* address(field, i) = 256 + ns * SIGNAL_FIELD_BLOCK_OFFSETS[field] + i * WIDTHS[field]
|
|
12
|
+
*
|
|
13
|
+
* Inside a data record the signals are stored back to back in signal order, each contributing
|
|
14
|
+
* `samplesPerRecord * bytesPerSample` bytes. Both numbers come from this file and nowhere else.
|
|
15
|
+
*/
|
|
16
|
+
import { SIGNAL_FIELD_WIDTHS } from '../constants.js';
|
|
17
|
+
import { type DiagnosticSink } from '../diagnostics/collector.js';
|
|
18
|
+
import type { EdfSignal } from '../types.js';
|
|
19
|
+
import type { EdfVariantInfo } from './variant.js';
|
|
20
|
+
/** The ten per-signal fields, in file order. */
|
|
21
|
+
export type SignalFieldName = keyof typeof SIGNAL_FIELD_WIDTHS;
|
|
22
|
+
/**
|
|
23
|
+
* Everything about one signal that does not depend on the resolved record count.
|
|
24
|
+
*
|
|
25
|
+
* The record count is resolved AFTER the per-signal blocks — recovering it needs the record
|
|
26
|
+
* size, which needs `samplesPerRecord` — so `sampleCount` is the one field that cannot be filled
|
|
27
|
+
* in here. `buildSignals` adds it once the count is known.
|
|
28
|
+
*/
|
|
29
|
+
export type SignalDraft = Omit<EdfSignal, 'sampleCount'>;
|
|
30
|
+
export interface SignalHeaderInput {
|
|
31
|
+
/** At least `256 * (signalCount + 1)` bytes; the caller has already refused anything less. */
|
|
32
|
+
readonly headerBytes: Uint8Array;
|
|
33
|
+
readonly signalCount: number;
|
|
34
|
+
readonly variant: EdfVariantInfo;
|
|
35
|
+
/** May be 0, in which case no signal gets a sample rate. Never divided by. */
|
|
36
|
+
readonly recordDurationSeconds: number;
|
|
37
|
+
}
|
|
38
|
+
export interface ParsedSignalHeaders {
|
|
39
|
+
readonly signals: readonly SignalDraft[];
|
|
40
|
+
/** `bytesPerSample * SUM(samplesPerRecord)`. May be 0; the caller decides that is fatal. */
|
|
41
|
+
readonly recordByteLength: number;
|
|
42
|
+
readonly dataSignalIndices: readonly number[];
|
|
43
|
+
readonly annotationSignalIndices: readonly number[];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The byte offset of one per-signal field, for one signal.
|
|
47
|
+
*
|
|
48
|
+
* Plain arithmetic on plain numbers: a 512-signal header is 131 KB, and nothing here is ever
|
|
49
|
+
* near 2^31, but the same expression is what every offset in the file is built on and `|0` has
|
|
50
|
+
* no business anywhere in it.
|
|
51
|
+
*/
|
|
52
|
+
export declare function signalFieldOffset(field: SignalFieldName, signalCount: number, signalIndex: number): number;
|
|
53
|
+
/**
|
|
54
|
+
* Parse the whole per-signal header.
|
|
55
|
+
*
|
|
56
|
+
* Check order within one signal is fixed and deliberate: samples per record (the offsets depend
|
|
57
|
+
* on it), then the digital range, then the physical range, then the range checks, then the
|
|
58
|
+
* annotation-signal conformance, then the scale. Cross-signal checks — duplicate labels, then a
|
|
59
|
+
* missing EDF+ marker — run once at the end, when every label is known.
|
|
60
|
+
*/
|
|
61
|
+
export declare function parseSignalHeaders(input: SignalHeaderInput, sink: DiagnosticSink): ParsedSignalHeaders;
|
|
62
|
+
/** The drafts, completed with the sample count the resolved record count implies. */
|
|
63
|
+
export declare function buildSignals(drafts: readonly SignalDraft[], recordCount: number): readonly EdfSignal[];
|
|
64
|
+
//# sourceMappingURL=signals.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signals.d.ts","sourceRoot":"","sources":["../../src/header/signals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,EAGL,mBAAmB,EACpB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,KAAK,cAAc,EAAc,MAAM,6BAA6B,CAAC;AAC9E,OAAO,KAAK,EAAgC,SAAS,EAAE,MAAM,aAAa,CAAC;AAI3E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD,gDAAgD;AAChD,MAAM,MAAM,eAAe,GAAG,MAAM,OAAO,mBAAmB,CAAC;AAE/D;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;AAEzD,MAAM,WAAW,iBAAiB;IAChC,8FAA8F;IAC9F,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAC;IACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,8EAA8E;IAC9E,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;CACxC;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAC;IACzC,4FAA4F;IAC5F,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9C,QAAQ,CAAC,uBAAuB,EAAE,SAAS,MAAM,EAAE,CAAC;CACrD;AAqBD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,eAAe,EACtB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,MAAM,CAMR;AAsRD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,iBAAiB,EACxB,IAAI,EAAE,cAAc,GACnB,mBAAmB,CA0JrB;AAED,qFAAqF;AACrF,wBAAgB,YAAY,CAC1B,MAAM,EAAE,SAAS,WAAW,EAAE,EAC9B,WAAW,EAAE,MAAM,GAClB,SAAS,SAAS,EAAE,CAItB"}
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The per-signal header, and where each signal's bytes live inside a data record.
|
|
3
|
+
*
|
|
4
|
+
* Layer 2. Sole owner of `recordByteOffset` arithmetic.
|
|
5
|
+
*
|
|
6
|
+
* The per-signal header is FIELD-MAJOR: all `ns` labels, then all `ns` transducer types, and so
|
|
7
|
+
* on for ten fields, `ns * 256` bytes in total. It is NOT one 256-byte struct per signal, and
|
|
8
|
+
* reading it as one is the single most common EDF bug — it happens to produce plausible output
|
|
9
|
+
* for a one-signal file, which is exactly how it survives a first test.
|
|
10
|
+
*
|
|
11
|
+
* address(field, i) = 256 + ns * SIGNAL_FIELD_BLOCK_OFFSETS[field] + i * WIDTHS[field]
|
|
12
|
+
*
|
|
13
|
+
* Inside a data record the signals are stored back to back in signal order, each contributing
|
|
14
|
+
* `samplesPerRecord * bytesPerSample` bytes. Both numbers come from this file and nowhere else.
|
|
15
|
+
*/
|
|
16
|
+
import { trimEdfField } from '../bytes/latin1.js';
|
|
17
|
+
import { parseEdfInteger, parseEdfNumber } from '../bytes/numbers.js';
|
|
18
|
+
import { readAsciiField } from '../bytes/view.js';
|
|
19
|
+
import { EDF_HEADER_BLOCK_BYTES, SIGNAL_FIELD_BLOCK_OFFSETS, SIGNAL_FIELD_WIDTHS, } from '../constants.js';
|
|
20
|
+
import { fatalError } from '../diagnostics/collector.js';
|
|
21
|
+
import { requireNumericField } from './fields.js';
|
|
22
|
+
import { isAnnotationLabel } from './lookup.js';
|
|
23
|
+
import { buildScale, normaliseUnit } from './scale.js';
|
|
24
|
+
const SIGNAL_SPEC_REFERENCE = 'EDF specification, header record, per-signal fields (field-major, ns * 256 bytes from ' +
|
|
25
|
+
'byte 256)';
|
|
26
|
+
const DIGITAL_RANGE_SPEC_REFERENCE = 'EDF+ additional specification 5';
|
|
27
|
+
const ANNOTATION_SPEC_REFERENCE = 'EDF+ specification 2.2.4 (the "EDF Annotations" signal)';
|
|
28
|
+
const FIELD_DESCRIPTIONS = {
|
|
29
|
+
label: 'label',
|
|
30
|
+
transducerType: 'transducer type',
|
|
31
|
+
physicalDimension: 'physical dimension',
|
|
32
|
+
physicalMinimum: 'physical minimum',
|
|
33
|
+
physicalMaximum: 'physical maximum',
|
|
34
|
+
digitalMinimum: 'digital minimum',
|
|
35
|
+
digitalMaximum: 'digital maximum',
|
|
36
|
+
prefiltering: 'prefiltering',
|
|
37
|
+
samplesPerRecord: 'number of samples in each data record',
|
|
38
|
+
reserved: 'reserved field',
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* The byte offset of one per-signal field, for one signal.
|
|
42
|
+
*
|
|
43
|
+
* Plain arithmetic on plain numbers: a 512-signal header is 131 KB, and nothing here is ever
|
|
44
|
+
* near 2^31, but the same expression is what every offset in the file is built on and `|0` has
|
|
45
|
+
* no business anywhere in it.
|
|
46
|
+
*/
|
|
47
|
+
export function signalFieldOffset(field, signalCount, signalIndex) {
|
|
48
|
+
return (EDF_HEADER_BLOCK_BYTES +
|
|
49
|
+
signalCount * SIGNAL_FIELD_BLOCK_OFFSETS[field] +
|
|
50
|
+
signalIndex * SIGNAL_FIELD_WIDTHS[field]);
|
|
51
|
+
}
|
|
52
|
+
function readSignalField(input, field, signalIndex) {
|
|
53
|
+
return readAsciiField(input.headerBytes, signalFieldOffset(field, input.signalCount, signalIndex), SIGNAL_FIELD_WIDTHS[field]);
|
|
54
|
+
}
|
|
55
|
+
function readRawSignalFields(input, signalIndex) {
|
|
56
|
+
return {
|
|
57
|
+
label: readSignalField(input, 'label', signalIndex),
|
|
58
|
+
transducerType: readSignalField(input, 'transducerType', signalIndex),
|
|
59
|
+
physicalDimension: readSignalField(input, 'physicalDimension', signalIndex),
|
|
60
|
+
physicalMinimum: readSignalField(input, 'physicalMinimum', signalIndex),
|
|
61
|
+
physicalMaximum: readSignalField(input, 'physicalMaximum', signalIndex),
|
|
62
|
+
digitalMinimum: readSignalField(input, 'digitalMinimum', signalIndex),
|
|
63
|
+
digitalMaximum: readSignalField(input, 'digitalMaximum', signalIndex),
|
|
64
|
+
prefiltering: readSignalField(input, 'prefiltering', signalIndex),
|
|
65
|
+
samplesPerRecord: readSignalField(input, 'samplesPerRecord', signalIndex),
|
|
66
|
+
reserved: readSignalField(input, 'reserved', signalIndex),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function scaleFieldOffsets(input, signalIndex) {
|
|
70
|
+
const ns = input.signalCount;
|
|
71
|
+
return {
|
|
72
|
+
physicalDimension: signalFieldOffset('physicalDimension', ns, signalIndex),
|
|
73
|
+
physicalMinimum: signalFieldOffset('physicalMinimum', ns, signalIndex),
|
|
74
|
+
physicalMaximum: signalFieldOffset('physicalMaximum', ns, signalIndex),
|
|
75
|
+
digitalMinimum: signalFieldOffset('digitalMinimum', ns, signalIndex),
|
|
76
|
+
digitalMaximum: signalFieldOffset('digitalMaximum', ns, signalIndex),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function numericContext(input, field, signalIndex, expected, nextStep, specReference) {
|
|
80
|
+
const byteOffset = signalFieldOffset(field, input.signalCount, signalIndex);
|
|
81
|
+
const byteLength = SIGNAL_FIELD_WIDTHS[field];
|
|
82
|
+
return {
|
|
83
|
+
field,
|
|
84
|
+
description: `${FIELD_DESCRIPTIONS[field]} of signal ${signalIndex} (${byteLength} bytes at offset ` +
|
|
85
|
+
`${byteOffset})`,
|
|
86
|
+
byteOffset,
|
|
87
|
+
byteLength,
|
|
88
|
+
expected,
|
|
89
|
+
specReference,
|
|
90
|
+
nextStep,
|
|
91
|
+
signalIndex,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* `samplesPerRecord` for one signal: the field every byte offset in the data section is built
|
|
96
|
+
* from, so it is fatal in every way it can fail except one.
|
|
97
|
+
*
|
|
98
|
+
* Zero is the exception. It is a warning: a signal contributing no samples is a writer's mistake
|
|
99
|
+
* rather than an unreadable file, every other signal still has an exact offset, and refusing the
|
|
100
|
+
* file would refuse the readable channels with it.
|
|
101
|
+
*/
|
|
102
|
+
function parseSamplesPerRecord(input, raw, signalIndex, sink) {
|
|
103
|
+
const context = numericContext(input, 'samplesPerRecord', signalIndex, 'a whole number of samples, written in ASCII digits', 'every byte offset inside a data record is a running sum of this field over the signals ' +
|
|
104
|
+
'before it, so no sample in the file can be located while it is unreadable', SIGNAL_SPEC_REFERENCE);
|
|
105
|
+
const samplesPerRecord = requireNumericField(parseEdfInteger(raw), context, sink);
|
|
106
|
+
if (samplesPerRecord < 0) {
|
|
107
|
+
throw fatalError({
|
|
108
|
+
code: 'NUMERIC_FIELD_INVALID',
|
|
109
|
+
message: `${context.description} is ${JSON.stringify(raw)}, i.e. ${samplesPerRecord} samples. A ` +
|
|
110
|
+
'signal cannot contribute a negative number of bytes to a data record, and the ' +
|
|
111
|
+
`offsets of every later signal are a running sum of this field. ${context.specReference}. ` +
|
|
112
|
+
`Next: ${context.nextStep}.`,
|
|
113
|
+
field: context.field,
|
|
114
|
+
byteOffset: context.byteOffset,
|
|
115
|
+
byteLength: context.byteLength,
|
|
116
|
+
raw,
|
|
117
|
+
expected: '0 or more samples',
|
|
118
|
+
actual: String(samplesPerRecord),
|
|
119
|
+
signalIndex,
|
|
120
|
+
specReference: context.specReference,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
if (samplesPerRecord === 0) {
|
|
124
|
+
sink.report({
|
|
125
|
+
code: 'ZERO_SAMPLES_PER_RECORD',
|
|
126
|
+
message: `${context.description} is ${JSON.stringify(raw)}: this signal contributes no bytes to ` +
|
|
127
|
+
`a data record and therefore carries no samples at all. ${context.specReference}: a ` +
|
|
128
|
+
'signal is present in every data record. Next: the signal is still exposed, with ' +
|
|
129
|
+
'sampleCount 0 and a zero-length block; decoding it returns an empty array, and the ' +
|
|
130
|
+
'other signals are unaffected because their offsets skip it exactly.',
|
|
131
|
+
field: context.field,
|
|
132
|
+
byteOffset: context.byteOffset,
|
|
133
|
+
byteLength: context.byteLength,
|
|
134
|
+
raw,
|
|
135
|
+
expected: '1 or more samples',
|
|
136
|
+
actual: '0',
|
|
137
|
+
signalIndex,
|
|
138
|
+
specReference: SIGNAL_SPEC_REFERENCE,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return samplesPerRecord;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* The declared digital range against what the sample width can represent: 16-bit two's
|
|
145
|
+
* complement for EDF, 24-bit for BDF.
|
|
146
|
+
*
|
|
147
|
+
* A warning, not an error. The declaration is what scaling uses, and a range wider than the
|
|
148
|
+
* format means the writer's scale is inconsistent with its own samples — but the samples still
|
|
149
|
+
* decode, and edfcore reports the mismatch rather than choosing a range for it.
|
|
150
|
+
*/
|
|
151
|
+
function checkDigitalRangeFitsFormat(input, signalIndex, digitalMinimum, digitalMaximum, raw, sink) {
|
|
152
|
+
const low = input.variant.digitalMinimumLimit;
|
|
153
|
+
const high = input.variant.digitalMaximumLimit;
|
|
154
|
+
const outside = [digitalMinimum, digitalMaximum].some((value) => value < low || value > high);
|
|
155
|
+
if (!outside)
|
|
156
|
+
return;
|
|
157
|
+
const byteOffset = signalFieldOffset('digitalMinimum', input.signalCount, signalIndex);
|
|
158
|
+
sink.report({
|
|
159
|
+
code: 'DIGITAL_RANGE_EXCEEDS_FORMAT',
|
|
160
|
+
message: `signal ${signalIndex} declares the digital range ${digitalMinimum}..${digitalMaximum} ` +
|
|
161
|
+
`(raw ${JSON.stringify(raw.digitalMinimum)} and ` +
|
|
162
|
+
`${JSON.stringify(raw.digitalMaximum)} at byte offsets ${byteOffset} and ` +
|
|
163
|
+
`${signalFieldOffset('digitalMaximum', input.signalCount, signalIndex)}), but a ` +
|
|
164
|
+
`${input.variant.family} sample is ${input.variant.bytesPerSample * 8}-bit two's ` +
|
|
165
|
+
`complement and can only hold ${low}..${high}. ${DIGITAL_RANGE_SPEC_REFERENCE}. Next: ` +
|
|
166
|
+
'the declared range is used for scaling exactly as written — edfcore never clamps — so ' +
|
|
167
|
+
'expect physical values that extrapolate beyond the declared physical range.',
|
|
168
|
+
field: 'digitalMinimum',
|
|
169
|
+
byteOffset,
|
|
170
|
+
byteLength: SIGNAL_FIELD_WIDTHS.digitalMinimum,
|
|
171
|
+
raw: raw.digitalMinimum,
|
|
172
|
+
expected: `${low}..${high}`,
|
|
173
|
+
actual: `${digitalMinimum}..${digitalMaximum}`,
|
|
174
|
+
signalIndex,
|
|
175
|
+
specReference: DIGITAL_RANGE_SPEC_REFERENCE,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* The EDF+ requirements on an annotation signal's own header block.
|
|
180
|
+
*
|
|
181
|
+
* Reported and then ignored: a non-conformant annotation header says nothing about whether the
|
|
182
|
+
* TALs inside the channel are readable, and they usually are. The digital range is checked
|
|
183
|
+
* against the exact values the dialect prescribes, not against the representable range.
|
|
184
|
+
*/
|
|
185
|
+
function checkAnnotationSignalHeader(input, signalIndex, raw, digitalMinimum, digitalMaximum, sink) {
|
|
186
|
+
const expectedMinimum = input.variant.digitalMinimumLimit;
|
|
187
|
+
const expectedMaximum = input.variant.digitalMaximumLimit;
|
|
188
|
+
const defects = [];
|
|
189
|
+
if (digitalMinimum !== expectedMinimum) {
|
|
190
|
+
defects.push(`digital minimum ${JSON.stringify(raw.digitalMinimum)} instead of ${expectedMinimum}`);
|
|
191
|
+
}
|
|
192
|
+
if (digitalMaximum !== expectedMaximum) {
|
|
193
|
+
defects.push(`digital maximum ${JSON.stringify(raw.digitalMaximum)} instead of ${expectedMaximum}`);
|
|
194
|
+
}
|
|
195
|
+
if (trimEdfField(raw.transducerType).length > 0) {
|
|
196
|
+
defects.push(`a non-blank transducer type ${JSON.stringify(trimEdfField(raw.transducerType))}`);
|
|
197
|
+
}
|
|
198
|
+
if (trimEdfField(raw.prefiltering).length > 0) {
|
|
199
|
+
defects.push(`a non-blank prefiltering field ${JSON.stringify(trimEdfField(raw.prefiltering))}`);
|
|
200
|
+
}
|
|
201
|
+
if (defects.length === 0)
|
|
202
|
+
return;
|
|
203
|
+
sink.report({
|
|
204
|
+
code: 'ANNOTATION_SIGNAL_HEADER_NONCONFORMANT',
|
|
205
|
+
message: `annotation signal ${signalIndex} (label ${JSON.stringify(trimEdfField(raw.label))}) has ` +
|
|
206
|
+
`${defects.join(', ')}. ${ANNOTATION_SPEC_REFERENCE}: the annotations signal must ` +
|
|
207
|
+
`declare digital minimum ${expectedMinimum} and digital maximum ${expectedMaximum}, and ` +
|
|
208
|
+
'leave transducer type and prefiltering blank, because the channel carries text rather ' +
|
|
209
|
+
'than measurements. Next: the annotations are read anyway — the header block of an ' +
|
|
210
|
+
'annotation signal only sizes its region, and that comes from samplesPerRecord.',
|
|
211
|
+
field: 'label',
|
|
212
|
+
byteOffset: signalFieldOffset('label', input.signalCount, signalIndex),
|
|
213
|
+
byteLength: SIGNAL_FIELD_WIDTHS.label,
|
|
214
|
+
raw: raw.label,
|
|
215
|
+
expected: `digital range ${expectedMinimum}..${expectedMaximum}, blank transducer and prefiltering`,
|
|
216
|
+
actual: defects.join(', '),
|
|
217
|
+
signalIndex,
|
|
218
|
+
specReference: ANNOTATION_SPEC_REFERENCE,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* One diagnostic per duplicated label, naming every signal that carries it.
|
|
223
|
+
*
|
|
224
|
+
* Annotation signals are excluded, and that is not a leniency: EDF+ allows several annotation
|
|
225
|
+
* channels and requires every one of them to be labelled `'EDF Annotations'`, so warning about
|
|
226
|
+
* them would report a defect against a conformant file — and under `strict` it would refuse one.
|
|
227
|
+
* `getSignal(header, 'EDF Annotations')` still throws on a file with two of them; the way to
|
|
228
|
+
* address those channels is `header.annotationSignalIndices`.
|
|
229
|
+
*/
|
|
230
|
+
function checkDuplicateLabels(input, drafts, sink) {
|
|
231
|
+
const byLabel = new Map();
|
|
232
|
+
for (const draft of drafts) {
|
|
233
|
+
if (draft.kind === 'annotations')
|
|
234
|
+
continue;
|
|
235
|
+
const indices = byLabel.get(draft.label);
|
|
236
|
+
if (indices === undefined)
|
|
237
|
+
byLabel.set(draft.label, [draft.index]);
|
|
238
|
+
else
|
|
239
|
+
indices.push(draft.index);
|
|
240
|
+
}
|
|
241
|
+
for (const [label, indices] of byLabel) {
|
|
242
|
+
const collision = indices[1];
|
|
243
|
+
if (collision === undefined)
|
|
244
|
+
continue;
|
|
245
|
+
sink.report({
|
|
246
|
+
code: 'DUPLICATE_SIGNAL_LABEL',
|
|
247
|
+
message: `signals ${indices.join(', ')} all carry the label ${JSON.stringify(label)}. A label ` +
|
|
248
|
+
'identifies a channel, so a repeated one leaves no way to name these signals apart. ' +
|
|
249
|
+
`${SIGNAL_SPEC_REFERENCE}. Next: getSignal(header, ${JSON.stringify(label)}) throws ` +
|
|
250
|
+
'EdfAmbiguousChannelError listing these indices rather than silently returning the ' +
|
|
251
|
+
'first; use findSignals() to see them all, or select by index.',
|
|
252
|
+
field: 'label',
|
|
253
|
+
byteOffset: signalFieldOffset('label', input.signalCount, collision),
|
|
254
|
+
byteLength: SIGNAL_FIELD_WIDTHS.label,
|
|
255
|
+
raw: label,
|
|
256
|
+
expected: 'a label unique within the file',
|
|
257
|
+
actual: `${indices.length} signals labelled ${JSON.stringify(label)}`,
|
|
258
|
+
signalIndex: collision,
|
|
259
|
+
specReference: SIGNAL_SPEC_REFERENCE,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Parse the whole per-signal header.
|
|
265
|
+
*
|
|
266
|
+
* Check order within one signal is fixed and deliberate: samples per record (the offsets depend
|
|
267
|
+
* on it), then the digital range, then the physical range, then the range checks, then the
|
|
268
|
+
* annotation-signal conformance, then the scale. Cross-signal checks — duplicate labels, then a
|
|
269
|
+
* missing EDF+ marker — run once at the end, when every label is known.
|
|
270
|
+
*/
|
|
271
|
+
export function parseSignalHeaders(input, sink) {
|
|
272
|
+
const drafts = [];
|
|
273
|
+
const dataSignalIndices = [];
|
|
274
|
+
const annotationSignalIndices = [];
|
|
275
|
+
let recordByteOffset = 0;
|
|
276
|
+
for (let index = 0; index < input.signalCount; index++) {
|
|
277
|
+
const raw = readRawSignalFields(input, index);
|
|
278
|
+
const label = trimEdfField(raw.label);
|
|
279
|
+
const kind = isAnnotationLabel(label) ? 'annotations' : 'data';
|
|
280
|
+
const samplesPerRecord = parseSamplesPerRecord(input, raw.samplesPerRecord, index, sink);
|
|
281
|
+
const digitalMinimum = requireNumericField(parseEdfInteger(raw.digitalMinimum), numericContext(input, 'digitalMinimum', index, 'a whole number, written in ASCII digits', 'the digital range defines the scale of this signal, and edfcore will not read a ' +
|
|
282
|
+
'physical value off a range it cannot read', DIGITAL_RANGE_SPEC_REFERENCE), sink);
|
|
283
|
+
const digitalMaximum = requireNumericField(parseEdfInteger(raw.digitalMaximum), numericContext(input, 'digitalMaximum', index, 'a whole number, written in ASCII digits', 'the digital range defines the scale of this signal, and edfcore will not read a ' +
|
|
284
|
+
'physical value off a range it cannot read', DIGITAL_RANGE_SPEC_REFERENCE), sink);
|
|
285
|
+
const physicalMinimum = requireNumericField(parseEdfNumber(raw.physicalMinimum), numericContext(input, 'physicalMinimum', index, 'a number, written in ASCII digits with "." as the decimal separator', 'the physical range defines the scale of this signal, and edfcore will not invent a ' +
|
|
286
|
+
'gain for a range it cannot read', SIGNAL_SPEC_REFERENCE), sink);
|
|
287
|
+
const physicalMaximum = requireNumericField(parseEdfNumber(raw.physicalMaximum), numericContext(input, 'physicalMaximum', index, 'a number, written in ASCII digits with "." as the decimal separator', 'the physical range defines the scale of this signal, and edfcore will not invent a ' +
|
|
288
|
+
'gain for a range it cannot read', SIGNAL_SPEC_REFERENCE), sink);
|
|
289
|
+
checkDigitalRangeFitsFormat(input, index, digitalMinimum, digitalMaximum, raw, sink);
|
|
290
|
+
let scale;
|
|
291
|
+
if (kind === 'annotations') {
|
|
292
|
+
checkAnnotationSignalHeader(input, index, raw, digitalMinimum, digitalMaximum, sink);
|
|
293
|
+
// No scale is built for an annotation channel, and that is not a refusal: its bytes are
|
|
294
|
+
// TAL text, so there is no measurement to scale. Running the degenerate-range checks over
|
|
295
|
+
// it would report a defect about a number nobody may use.
|
|
296
|
+
annotationSignalIndices.push(index);
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
scale = buildScale({
|
|
300
|
+
signalIndex: index,
|
|
301
|
+
label,
|
|
302
|
+
physicalDimension: raw.physicalDimension,
|
|
303
|
+
physicalMinimum,
|
|
304
|
+
physicalMaximum,
|
|
305
|
+
digitalMinimum,
|
|
306
|
+
digitalMaximum,
|
|
307
|
+
raw,
|
|
308
|
+
byteOffsets: scaleFieldOffsets(input, index),
|
|
309
|
+
}, sink);
|
|
310
|
+
dataSignalIndices.push(index);
|
|
311
|
+
}
|
|
312
|
+
const recordByteLength = samplesPerRecord * input.variant.bytesPerSample;
|
|
313
|
+
drafts.push({
|
|
314
|
+
index,
|
|
315
|
+
kind,
|
|
316
|
+
label,
|
|
317
|
+
transducerType: trimEdfField(raw.transducerType),
|
|
318
|
+
prefiltering: trimEdfField(raw.prefiltering),
|
|
319
|
+
physicalDimension: trimEdfField(raw.physicalDimension),
|
|
320
|
+
unit: normaliseUnit(raw.physicalDimension),
|
|
321
|
+
physicalMinimum,
|
|
322
|
+
physicalMaximum,
|
|
323
|
+
digitalMinimum,
|
|
324
|
+
digitalMaximum,
|
|
325
|
+
samplesPerRecord,
|
|
326
|
+
// Undefined exactly when the record duration is 0, which is legal. Dividing by it there
|
|
327
|
+
// would produce Infinity and label it a sample rate.
|
|
328
|
+
sampleRateHz: input.recordDurationSeconds === 0
|
|
329
|
+
? undefined
|
|
330
|
+
: samplesPerRecord / input.recordDurationSeconds,
|
|
331
|
+
scale,
|
|
332
|
+
recordByteOffset,
|
|
333
|
+
recordByteLength,
|
|
334
|
+
raw,
|
|
335
|
+
});
|
|
336
|
+
recordByteOffset += recordByteLength;
|
|
337
|
+
}
|
|
338
|
+
checkDuplicateLabels(input, drafts, sink);
|
|
339
|
+
const firstAnnotation = annotationSignalIndices[0];
|
|
340
|
+
if (firstAnnotation !== undefined && !input.variant.isPlus) {
|
|
341
|
+
const first = firstAnnotation;
|
|
342
|
+
const label = drafts[first]?.label ?? input.variant.annotationsLabel;
|
|
343
|
+
sink.report({
|
|
344
|
+
code: 'MISSING_EDFPLUS_MARKER',
|
|
345
|
+
message: `signal ${first} is labelled ${JSON.stringify(label)} but the reserved field of the ` +
|
|
346
|
+
'fixed header carries no EDF+/BDF+ marker, so the file claims to be plain ' +
|
|
347
|
+
`${input.variant.family} while carrying an EDF+ annotations channel. EDF+ ` +
|
|
348
|
+
'specification 2.1.1: an EDF+ file states "EDF+C" or "EDF+D" in the reserved field. ' +
|
|
349
|
+
'Next: the annotations are parsed anyway, and the channel is never exposed as an ' +
|
|
350
|
+
'ordinary signal — it is in header.annotationSignalIndices, not in ' +
|
|
351
|
+
'header.dataSignalIndices — because decoding TAL text as samples produces numbers that ' +
|
|
352
|
+
'look like a signal.',
|
|
353
|
+
field: 'reserved',
|
|
354
|
+
byteOffset: signalFieldOffset('label', input.signalCount, first),
|
|
355
|
+
byteLength: SIGNAL_FIELD_WIDTHS.label,
|
|
356
|
+
raw: label,
|
|
357
|
+
expected: '"EDF+C" or "EDF+D" in the reserved field at offset 192',
|
|
358
|
+
actual: 'no EDF+/BDF+ marker',
|
|
359
|
+
signalIndex: first,
|
|
360
|
+
specReference: 'EDF+ specification 2.1.1 (the EDF+ header)',
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
return {
|
|
364
|
+
signals: Object.freeze(drafts),
|
|
365
|
+
recordByteLength: recordByteOffset,
|
|
366
|
+
dataSignalIndices: Object.freeze(dataSignalIndices),
|
|
367
|
+
annotationSignalIndices: Object.freeze(annotationSignalIndices),
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
/** The drafts, completed with the sample count the resolved record count implies. */
|
|
371
|
+
export function buildSignals(drafts, recordCount) {
|
|
372
|
+
return Object.freeze(drafts.map((draft) => ({ ...draft, sampleCount: draft.samplesPerRecord * recordCount })));
|
|
373
|
+
}
|
|
374
|
+
//# sourceMappingURL=signals.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signals.js","sourceRoot":"","sources":["../../src/header/signals.ts"],"names":[],"mappings":"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"}
|