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,268 @@
|
|
|
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
|
+
|
|
23
|
+
import { trimEdfField } from '../bytes/latin1.js';
|
|
24
|
+
import type { DiagnosticInit, DiagnosticSink } from '../diagnostics/collector.js';
|
|
25
|
+
import type { EdfRawSignalFields, EdfScale } from '../types.js';
|
|
26
|
+
|
|
27
|
+
/** EDF+ 'edffloat': a channel whose values are log-compressed, so the linear map is wrong. */
|
|
28
|
+
const LOG_TRANSFORMED_DIMENSION = 'Filtered';
|
|
29
|
+
|
|
30
|
+
/** U+00B5 MICRO SIGN, U+03BC GREEK SMALL LETTER MU. A raw 0xB5 header byte decodes to the
|
|
31
|
+
* former through Latin-1, so both spellings and the byte are covered. */
|
|
32
|
+
const MICRO_SIGNS = /[µμ]/g;
|
|
33
|
+
const MICRO_REPLACEMENT = 'u';
|
|
34
|
+
|
|
35
|
+
const RANGE_SPEC_REFERENCE = 'EDF+ additional specification 5';
|
|
36
|
+
|
|
37
|
+
/** Byte offsets of the per-signal fields a scaling diagnostic points at. */
|
|
38
|
+
export interface ScaleFieldOffsets {
|
|
39
|
+
readonly physicalDimension: number;
|
|
40
|
+
readonly physicalMinimum: number;
|
|
41
|
+
readonly physicalMaximum: number;
|
|
42
|
+
readonly digitalMinimum: number;
|
|
43
|
+
readonly digitalMaximum: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ScaleInput {
|
|
47
|
+
readonly signalIndex: number;
|
|
48
|
+
/** Trimmed label, for the message. */
|
|
49
|
+
readonly label: string;
|
|
50
|
+
/** Physical dimension exactly as written; `'Filtered'` is refused after trimming. */
|
|
51
|
+
readonly physicalDimension: string;
|
|
52
|
+
readonly physicalMinimum: number;
|
|
53
|
+
readonly physicalMaximum: number;
|
|
54
|
+
readonly digitalMinimum: number;
|
|
55
|
+
readonly digitalMaximum: number;
|
|
56
|
+
/** The raw per-signal field text, so a message can quote the bytes as written. */
|
|
57
|
+
readonly raw: EdfRawSignalFields | undefined;
|
|
58
|
+
/** Where those fields live in the header. */
|
|
59
|
+
readonly byteOffsets: ScaleFieldOffsets | undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Collapse the encodings of micro to `'u'` so units can be compared.
|
|
64
|
+
*
|
|
65
|
+
* For comparison ONLY: `signal.physicalDimension` stays exactly as the file wrote it. Nothing
|
|
66
|
+
* else is touched — case is meaningful (`mV` is not `MV`), and edfcore does not normalise units
|
|
67
|
+
* to SI volts.
|
|
68
|
+
*/
|
|
69
|
+
export function normaliseUnit(physicalDimension: string): string {
|
|
70
|
+
return trimEdfField(physicalDimension).replace(MICRO_SIGNS, MICRO_REPLACEMENT);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function isLogTransformed(physicalDimension: string): boolean {
|
|
74
|
+
return trimEdfField(physicalDimension) === LOG_TRANSFORMED_DIMENSION;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** A diagnostic init with the fields that are only known when the caller supplied context. */
|
|
78
|
+
function scaleDiagnostic(
|
|
79
|
+
input: ScaleInput,
|
|
80
|
+
code: DiagnosticInit['code'],
|
|
81
|
+
message: string,
|
|
82
|
+
field: keyof ScaleFieldOffsets,
|
|
83
|
+
expected: string,
|
|
84
|
+
actual: string,
|
|
85
|
+
specReference: string,
|
|
86
|
+
): DiagnosticInit {
|
|
87
|
+
const byteOffset = input.byteOffsets?.[field];
|
|
88
|
+
const raw = input.raw?.[field];
|
|
89
|
+
return {
|
|
90
|
+
code,
|
|
91
|
+
message,
|
|
92
|
+
field,
|
|
93
|
+
signalIndex: input.signalIndex,
|
|
94
|
+
expected,
|
|
95
|
+
actual,
|
|
96
|
+
specReference,
|
|
97
|
+
...(byteOffset === undefined ? {} : { byteOffset }),
|
|
98
|
+
...(raw === undefined ? {} : { raw }),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function describe(input: ScaleInput): string {
|
|
103
|
+
return `signal ${input.signalIndex} ${JSON.stringify(input.label)}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** `'"0 " at byte offset 3400'`, or the value alone when the caller gave no context. */
|
|
107
|
+
function quoteField(input: ScaleInput, field: keyof ScaleFieldOffsets): string {
|
|
108
|
+
const raw = input.raw?.[field];
|
|
109
|
+
const byteOffset = input.byteOffsets?.[field];
|
|
110
|
+
const rawText = raw === undefined ? '' : `raw ${JSON.stringify(raw)}`;
|
|
111
|
+
const offsetText = byteOffset === undefined ? '' : `at byte offset ${byteOffset}`;
|
|
112
|
+
const parts = [rawText, offsetText].filter((part) => part.length > 0);
|
|
113
|
+
return parts.length === 0 ? '' : ` (${parts.join(' ')})`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The scale for one signal, or `undefined` when edfcore refuses to invent one.
|
|
118
|
+
*
|
|
119
|
+
* The refusals are checked in this fixed order: degenerate digital range, degenerate physical
|
|
120
|
+
* range, inverted digital range, log-transformed channel, and finally a derived gain that is not
|
|
121
|
+
* a usable float64 number. Each is deferred-fatal — `decodeDigital` keeps working on the signal
|
|
122
|
+
* and `toPhysical` throws `EdfScalingError`.
|
|
123
|
+
*
|
|
124
|
+
* A non-finite input returns `undefined` without a diagnostic: the only way to get here with
|
|
125
|
+
* one is a numeric field that failed its grammar, which the caller has already reported
|
|
126
|
+
* against the field itself.
|
|
127
|
+
*/
|
|
128
|
+
export function buildScale(input: ScaleInput, sink: DiagnosticSink): EdfScale | undefined {
|
|
129
|
+
const { physicalMinimum, physicalMaximum, digitalMinimum, digitalMaximum } = input;
|
|
130
|
+
if (
|
|
131
|
+
!Number.isFinite(physicalMinimum) ||
|
|
132
|
+
!Number.isFinite(physicalMaximum) ||
|
|
133
|
+
!Number.isFinite(digitalMinimum) ||
|
|
134
|
+
!Number.isFinite(digitalMaximum)
|
|
135
|
+
) {
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (digitalMaximum === digitalMinimum) {
|
|
140
|
+
sink.report(
|
|
141
|
+
scaleDiagnostic(
|
|
142
|
+
input,
|
|
143
|
+
'DEGENERATE_DIGITAL_RANGE',
|
|
144
|
+
`${describe(input)} declares digitalMinimum == digitalMaximum == ${digitalMinimum}, ` +
|
|
145
|
+
'so physical scale is undefined (division by zero)' +
|
|
146
|
+
`${quoteField(input, 'digitalMinimum')}. ${RANGE_SPEC_REFERENCE}: "Digital maximum ` +
|
|
147
|
+
'must be larger than Digital minimum". Next: decodeDigital() still works on this ' +
|
|
148
|
+
'signal; edfcore will not invent a gain.',
|
|
149
|
+
'digitalMinimum',
|
|
150
|
+
'digitalMaximum > digitalMinimum',
|
|
151
|
+
`${digitalMinimum} == ${digitalMaximum}`,
|
|
152
|
+
RANGE_SPEC_REFERENCE,
|
|
153
|
+
),
|
|
154
|
+
);
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (physicalMaximum === physicalMinimum) {
|
|
159
|
+
sink.report(
|
|
160
|
+
scaleDiagnostic(
|
|
161
|
+
input,
|
|
162
|
+
'DEGENERATE_PHYSICAL_RANGE',
|
|
163
|
+
`${describe(input)} declares physicalMinimum == physicalMaximum == ` +
|
|
164
|
+
`${physicalMinimum}, so every digital value would map to the same physical value` +
|
|
165
|
+
`${quoteField(input, 'physicalMinimum')}. ${RANGE_SPEC_REFERENCE}: "Physical ` +
|
|
166
|
+
'maximum must differ from Physical minimum". Next: decodeDigital() still works on ' +
|
|
167
|
+
'this signal; edfcore will not invent a gain.',
|
|
168
|
+
'physicalMinimum',
|
|
169
|
+
'physicalMaximum != physicalMinimum',
|
|
170
|
+
`${physicalMinimum} == ${physicalMaximum}`,
|
|
171
|
+
RANGE_SPEC_REFERENCE,
|
|
172
|
+
),
|
|
173
|
+
);
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (digitalMinimum > digitalMaximum) {
|
|
178
|
+
sink.report(
|
|
179
|
+
scaleDiagnostic(
|
|
180
|
+
input,
|
|
181
|
+
'INVERTED_DIGITAL_RANGE',
|
|
182
|
+
`${describe(input)} declares digitalMinimum ${digitalMinimum} greater than ` +
|
|
183
|
+
`digitalMaximum ${digitalMaximum}${quoteField(input, 'digitalMinimum')}. ` +
|
|
184
|
+
`${RANGE_SPEC_REFERENCE}: "Digital maximum must be larger than Digital minimum". ` +
|
|
185
|
+
'Unlike an inverted physical range this has no sanctioned meaning, so edfcore will ' +
|
|
186
|
+
'not guess whether the fields were swapped or the samples inverted. Next: ' +
|
|
187
|
+
'decodeDigital() still works on this signal.',
|
|
188
|
+
'digitalMinimum',
|
|
189
|
+
'digitalMaximum > digitalMinimum',
|
|
190
|
+
`${digitalMinimum} > ${digitalMaximum}`,
|
|
191
|
+
RANGE_SPEC_REFERENCE,
|
|
192
|
+
),
|
|
193
|
+
);
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (isLogTransformed(input.physicalDimension)) {
|
|
198
|
+
sink.report(
|
|
199
|
+
scaleDiagnostic(
|
|
200
|
+
input,
|
|
201
|
+
'LOG_TRANSFORMED_CHANNEL',
|
|
202
|
+
`${describe(input)} has physical dimension exactly "${LOG_TRANSFORMED_DIMENSION}"` +
|
|
203
|
+
`${quoteField(input, 'physicalDimension')}, which marks a logarithmically ` +
|
|
204
|
+
'transformed channel (EDF specification edffloat.html): its samples are ' +
|
|
205
|
+
'log-compressed, so the linear digital-to-physical map would be wrong by orders of ' +
|
|
206
|
+
'magnitude. Next: decodeDigital() still works on this signal; edfcore detects the ' +
|
|
207
|
+
'transform and refuses it rather than applying an inverse it cannot verify.',
|
|
208
|
+
'physicalDimension',
|
|
209
|
+
'a linear physical dimension',
|
|
210
|
+
LOG_TRANSFORMED_DIMENSION,
|
|
211
|
+
'EDF specification edffloat.html',
|
|
212
|
+
),
|
|
213
|
+
);
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (physicalMinimum > physicalMaximum) {
|
|
218
|
+
sink.report(
|
|
219
|
+
scaleDiagnostic(
|
|
220
|
+
input,
|
|
221
|
+
'INVERTED_PHYSICAL_RANGE',
|
|
222
|
+
`${describe(input)} declares physicalMinimum ${physicalMinimum} greater than ` +
|
|
223
|
+
`physicalMaximum ${physicalMaximum}${quoteField(input, 'physicalMinimum')}. This ` +
|
|
224
|
+
'is legal and encodes a negative amplifier gain (EDF FAQ Q6), so bitValue comes ' +
|
|
225
|
+
'out negative. Next: nothing to do — edfcore never swaps the two, because a silent ' +
|
|
226
|
+
'polarity flip is a clinically wrong result that looks normal.',
|
|
227
|
+
'physicalMinimum',
|
|
228
|
+
'physicalMinimum < physicalMaximum, or a deliberate negative gain',
|
|
229
|
+
`${physicalMinimum} > ${physicalMaximum}`,
|
|
230
|
+
'EDF FAQ Q6 (negative amplifier gain)',
|
|
231
|
+
),
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// EDFlib's exact expression. See the module comment before changing either line.
|
|
236
|
+
const bitValue = (physicalMaximum - physicalMinimum) / (digitalMaximum - digitalMinimum);
|
|
237
|
+
const offset = physicalMaximum / bitValue - digitalMaximum;
|
|
238
|
+
|
|
239
|
+
// The four INPUTS being finite does not make the DERIVED pair finite. An 8-byte field may write
|
|
240
|
+
// an exponent, so a physical range can underflow against the digital range (`0`..`5E-324` over
|
|
241
|
+
// -1..1 gives bitValue 0 and offset Infinity) or overflow it (`-9.9E307`..`9.9E307` gives
|
|
242
|
+
// bitValue Infinity). Either way `bitValue * (offset + digital)` is NaN or +/-Infinity for every
|
|
243
|
+
// sample in the channel, which is a fabricated gain by another name — so the scale is refused
|
|
244
|
+
// exactly as the degenerate cases above are, and `decodeDigital` keeps working.
|
|
245
|
+
if (bitValue === 0 || !Number.isFinite(bitValue) || !Number.isFinite(offset)) {
|
|
246
|
+
sink.report(
|
|
247
|
+
scaleDiagnostic(
|
|
248
|
+
input,
|
|
249
|
+
'DEGENERATE_PHYSICAL_RANGE',
|
|
250
|
+
`${describe(input)} declares the physical range ${physicalMinimum}..${physicalMaximum} ` +
|
|
251
|
+
`over the digital range ${digitalMinimum}..${digitalMaximum}` +
|
|
252
|
+
`${quoteField(input, 'physicalMaximum')}, and the gain those imply is not a usable ` +
|
|
253
|
+
`float64 number: bitValue is ${bitValue} and offset is ${offset}, so ` +
|
|
254
|
+
'bitValue * (offset + digital) is NaN or infinite for every sample. ' +
|
|
255
|
+
`${RANGE_SPEC_REFERENCE}. Next: decodeDigital() still works on this signal; edfcore ` +
|
|
256
|
+
'will not invent a gain, and a physical range this far outside float64 is a corrupt ' +
|
|
257
|
+
'field rather than a calibration.',
|
|
258
|
+
'physicalMaximum',
|
|
259
|
+
'a physical range that defines a finite, non-zero gain',
|
|
260
|
+
`bitValue ${bitValue}, offset ${offset}`,
|
|
261
|
+
RANGE_SPEC_REFERENCE,
|
|
262
|
+
),
|
|
263
|
+
);
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return { bitValue, offset };
|
|
268
|
+
}
|