edfcore 0.3.30 → 0.3.32
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/CHANGELOG.md +46 -0
- package/dist/biosemi.d.ts.map +1 -1
- package/dist/biosemi.js +16 -2
- package/dist/biosemi.js.map +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/diagnostics/format.d.ts.map +1 -1
- package/dist/diagnostics/format.js +31 -4
- package/dist/diagnostics/format.js.map +1 -1
- package/package.json +1 -1
- package/src/biosemi.ts +16 -2
- package/src/constants.ts +1 -1
- package/src/diagnostics/format.ts +30 -3
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,52 @@ alone does not tell you whether you were affected.
|
|
|
6
6
|
edfcore is pre-1.0. Patch releases have carried behaviour changes where the old behaviour was a
|
|
7
7
|
defect; those are called out below.
|
|
8
8
|
|
|
9
|
+
## 0.3.32
|
|
10
|
+
|
|
11
|
+
- **Fixed** `readTriggers` timestamping a Status sample by TRUNCATING to a whole tick, so an
|
|
12
|
+
event's own reported time mapped back to the previous sample. `sampleAt(event.seconds)` returned
|
|
13
|
+
sample **100** for an event `readTriggers` called sample 101.
|
|
14
|
+
- A sample boundary need not fall on a whole tick — 10^7 / 512 is 19531.25 — and edfcore's two
|
|
15
|
+
sample-start functions round UP for the reason `gridSampleStartTicks` gives in its own comment:
|
|
16
|
+
*"Truncating would return 23,437 — a tick that lies inside sample 0 — so `gridSampleIndexAt`
|
|
17
|
+
would send it straight back to the previous sample."* `readTriggers` was the one function that
|
|
18
|
+
truncated, and three boundaries in four are affected at any power-of-two rate BioSemi actually
|
|
19
|
+
uses.
|
|
20
|
+
- The ERP consequence is the sharp one. Align a window to the stimulus with
|
|
21
|
+
`sampleStartSecondsOf(rec, status, 101)` and the event sat one tick before the window's left
|
|
22
|
+
edge, so the onset came back as sample **102** — 2 ms at 512 Hz, on the one number an evoked-
|
|
23
|
+
potential pipeline reads. A one-sample-wide trigger at that edge disappeared entirely, which
|
|
24
|
+
contradicts this function's own rule that the first in-window sample always produces an event.
|
|
25
|
+
- Now `ceilDiv`, the same rule as `gridSampleStartTicks` and `sampleStartTicksOf`. An event's tick
|
|
26
|
+
is the first whole tick at or after the sample's true start, so `sampleAt`, `sampleStartTicksOf`,
|
|
27
|
+
a window bound and `readTriggers` all name the same sample.
|
|
28
|
+
|
|
29
|
+
## 0.3.31
|
|
30
|
+
|
|
31
|
+
**Two ways patient identification reached the output with `--patient` absent.** Both produced text
|
|
32
|
+
that LOOKED redacted — `raw:` and `actual:` said `[redacted]` — which is worse than an obvious leak,
|
|
33
|
+
because a reader has no reason to check.
|
|
34
|
+
|
|
35
|
+
- **A NUL-padded identification field was printed verbatim.** `redactDiagnostic` substituted
|
|
36
|
+
spellings derived from `raw`, including `raw.trim()`. Every identification diagnostic builds its
|
|
37
|
+
message from `trimEdfField(raw)`, and `trimEdfField` strips 0x20 **and 0x00** while
|
|
38
|
+
`String.prototype.trim` strips whitespace but not U+0000. On a field padded with NULs — which a
|
|
39
|
+
large share of real writers emit, and which `header/fields.ts` treats as normal — none of the
|
|
40
|
+
spellings matched. `edfcore header` printed the whole name and MRN; `edfcore validate` printed it
|
|
41
|
+
twice.
|
|
42
|
+
- **`DATE_IMPLAUSIBLE` printed the patient's date of birth.** It spells the date `2050-05-02` while
|
|
43
|
+
the file writes `02-MAY-2050`, so no spelling derived from `raw` could ever match it — and it
|
|
44
|
+
fires on a perfectly conformant identification field, with no NUL padding and no grammar
|
|
45
|
+
violation needed. `formatHeader`'s own comment names "a name and a birth date" as what the flag
|
|
46
|
+
exists to withhold.
|
|
47
|
+
- Both close at the substitution: the field's `trimEdfField` spelling and the diagnostic's own
|
|
48
|
+
`actual`, captured before `actual` is replaced, are now removed from the message too. `actual`
|
|
49
|
+
already carries whatever the message chose to print, whatever spelling that is.
|
|
50
|
+
- 0.2.26 established that withholding `header.patient` while the diagnostic below it spells the
|
|
51
|
+
same string out is not withholding it at all. These are the two spellings that fix could not
|
|
52
|
+
reach. The rule, the code, the byte offset and the recording's own start date are not patient
|
|
53
|
+
data and stay readable.
|
|
54
|
+
|
|
9
55
|
## 0.3.30
|
|
10
56
|
|
|
11
57
|
- **Fixed** `readEnvelopeAtResolution` crushing the tail of a run into one bucket whenever the
|
package/dist/biosemi.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"biosemi.d.ts","sourceRoot":"","sources":["../src/biosemi.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAQH,OAAO,KAAK,EACV,SAAS,EACT,YAAY,EAEZ,SAAS,EACT,aAAa,EACb,eAAe,EACf,WAAW,EAEX,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAWpB;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAOxE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAS9D;AAqBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAsB,YAAY,CAChC,SAAS,EAAE,YAAY,EACvB,SAAS,EAAE,gBAAgB,EAC3B,OAAO,CAAC,EAAE,WAAW,GACpB,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"biosemi.d.ts","sourceRoot":"","sources":["../src/biosemi.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAQH,OAAO,KAAK,EACV,SAAS,EACT,YAAY,EAEZ,SAAS,EACT,aAAa,EACb,eAAe,EACf,WAAW,EAEX,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAWpB;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAOxE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAS9D;AAqBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAsB,YAAY,CAChC,SAAS,EAAE,YAAY,EACvB,SAAS,EAAE,gBAAgB,EAC3B,OAAO,CAAC,EAAE,WAAW,GACpB,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC,CA+GrC"}
|
package/dist/biosemi.js
CHANGED
|
@@ -18,7 +18,7 @@ import { decodeDigitalCounted } from './decode/digital.js';
|
|
|
18
18
|
import { readRecordBytes } from './io/read.js';
|
|
19
19
|
import { scanChunkRecords } from './record-index.js';
|
|
20
20
|
import { gapBefore } from './recording.js';
|
|
21
|
-
import { secondsToTicks, ticksToSeconds } from './tal/ticks.js';
|
|
21
|
+
import { ceilDiv, secondsToTicks, ticksToSeconds } from './tal/ticks.js';
|
|
22
22
|
import { resolveTimeWindow } from './time/window.js';
|
|
23
23
|
/** BioSemi's own label for the channel. Matched case-insensitively after trimming. */
|
|
24
24
|
const STATUS_LABEL = 'status';
|
|
@@ -169,9 +169,23 @@ export async function readTriggers(recording, selection, options) {
|
|
|
169
169
|
const word = decodeStatusWord(decoded.digital[r * samplesPerRecord + s]);
|
|
170
170
|
const changed = previous === undefined || word.trigger !== previous;
|
|
171
171
|
previous = word.trigger;
|
|
172
|
+
// CEILING, the same rule `gridSampleStartTicks` and `sampleStartTicksOf` use, and for
|
|
173
|
+
// the reason the first of those states in its own comment: a sample boundary need not
|
|
174
|
+
// fall on a whole tick, and truncating returns a tick that lies inside the PREVIOUS
|
|
175
|
+
// sample. 10^7 / 512 is 19531.25, so three boundaries in four are affected on an
|
|
176
|
+
// ordinary 512 Hz BioSemi file.
|
|
177
|
+
//
|
|
178
|
+
// Truncating made an event's own reported time round-trip to the wrong sample —
|
|
179
|
+
// `sampleAt(event.seconds)` returned 100 for an event readTriggers called sample 101 —
|
|
180
|
+
// and a window aligned to the stimulus with `sampleStartSecondsOf` reported the onset a
|
|
181
|
+
// sample late, which at 512 Hz is 2 ms on the one number an ERP pipeline reads. A
|
|
182
|
+
// one-sample-wide trigger at the window's left edge disappeared entirely, contradicting
|
|
183
|
+
// this function's own rule that the first in-window sample always produces an event
|
|
184
|
+
// (fixed in 0.3.32).
|
|
185
|
+
//
|
|
172
186
|
// A zero record duration puts every sample of the record at its start instant.
|
|
173
187
|
const ticks = durationTicks > 0n && samplesPerRecord > 0
|
|
174
|
-
? recordStartTicks + (BigInt(s) * durationTicks
|
|
188
|
+
? recordStartTicks + ceilDiv(BigInt(s) * durationTicks, BigInt(samplesPerRecord))
|
|
175
189
|
: recordStartTicks;
|
|
176
190
|
if (ticks < windowStartTicks || ticks >= windowEndTicks)
|
|
177
191
|
continue;
|
package/dist/biosemi.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"biosemi.js","sourceRoot":"","sources":["../src/biosemi.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"biosemi.js","sourceRoot":"","sources":["../src/biosemi.ts"],"names":[],"mappings":"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"}
|
package/dist/constants.d.ts
CHANGED
|
@@ -109,5 +109,5 @@ export declare const SIGNAL_FIELD_BLOCK_OFFSETS: {
|
|
|
109
109
|
readonly reserved: 224;
|
|
110
110
|
};
|
|
111
111
|
/** Published package version. Kept in sync with package.json by a test. */
|
|
112
|
-
export declare const VERSION = "0.3.
|
|
112
|
+
export declare const VERSION = "0.3.32";
|
|
113
113
|
//# sourceMappingURL=constants.d.ts.map
|
package/dist/constants.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/diagnostics/format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/diagnostics/format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAe,MAAM,aAAa,CAAC;AAE9D,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3C;AAgBD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,SAAS,aAAa,EAAE,EACrC,OAAO,CAAC,EAAE,wBAAwB,GACjC,MAAM,CAkBR"}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* Output is deterministic and asserted as such: no locale-sensitive number or date formatting,
|
|
9
9
|
* no iteration over an unordered collection, and no ANSI escapes unless `color` is requested.
|
|
10
10
|
*/
|
|
11
|
+
import { trimEdfField } from '../bytes/latin1.js';
|
|
11
12
|
import { printable } from '../text/printable.js';
|
|
12
13
|
const INDENT = ' ';
|
|
13
14
|
/** A report is a summary, not a hex dump; longer runs are elided with a count. */
|
|
@@ -60,11 +61,37 @@ const REDACTED = '[redacted]';
|
|
|
60
61
|
function redactDiagnostic(diagnostic) {
|
|
61
62
|
const raw = diagnostic.raw;
|
|
62
63
|
let message = diagnostic.message;
|
|
64
|
+
const spellings = [];
|
|
63
65
|
if (raw !== undefined) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
/*
|
|
67
|
+
* `raw.trim()` is not the same as the field's trimmed value, and that gap was the leak.
|
|
68
|
+
* `trimEdfField` strips 0x20 AND 0x00; `String.prototype.trim` strips whitespace but not
|
|
69
|
+
* U+0000. Every identification diagnostic builds its message from `trimEdfField(raw)`, so on
|
|
70
|
+
* a field padded with NULs — which a large share of real writers emit, and which
|
|
71
|
+
* `header/fields.ts` treats as normal — none of the four spellings matched and the name went
|
|
72
|
+
* through verbatim while `raw:` and `actual:` said `[redacted]`. Output that LOOKS redacted is
|
|
73
|
+
* worse than an obvious leak (fixed in 0.3.31).
|
|
74
|
+
*/
|
|
75
|
+
spellings.push(JSON.stringify(raw), JSON.stringify(raw.trim()), raw, raw.trim());
|
|
76
|
+
const trimmed = trimEdfField(raw);
|
|
77
|
+
spellings.push(trimmed, JSON.stringify(trimmed));
|
|
78
|
+
}
|
|
79
|
+
/*
|
|
80
|
+
* And the diagnostic's own `actual`, computed BEFORE it is replaced below.
|
|
81
|
+
*
|
|
82
|
+
* A message does not have to spell the value the way the field does. `DATE_IMPLAUSIBLE` writes
|
|
83
|
+
* the patient's date of birth as `2050-05-02` while the file says `02-MAY-2050`, so no spelling
|
|
84
|
+
* derived from `raw` could ever match it — and that diagnostic fires on a perfectly conformant
|
|
85
|
+
* identification field, with no NUL padding and no grammar violation needed. `actual` already
|
|
86
|
+
* carries whatever the message chose to print, which is exactly what has to be substituted.
|
|
87
|
+
*/
|
|
88
|
+
if (diagnostic.actual !== undefined) {
|
|
89
|
+
const actual = String(diagnostic.actual);
|
|
90
|
+
spellings.push(actual, JSON.stringify(actual));
|
|
91
|
+
}
|
|
92
|
+
for (const spelling of spellings) {
|
|
93
|
+
if (spelling.length > 0)
|
|
94
|
+
message = message.split(spelling).join(REDACTED);
|
|
68
95
|
}
|
|
69
96
|
return {
|
|
70
97
|
...diagnostic,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/diagnostics/format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAuBjD,MAAM,MAAM,GAAG,IAAI,CAAC;AAEpB,kFAAkF;AAClF,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAE/B,MAAM,UAAU,GAAG,WAAW,CAAC;AAC/B,MAAM,QAAQ,GAAG,WAAW,CAAC;AAE7B,MAAM,eAAe,GAA0C;IAC7D,KAAK,EAAE,YAAY;IACnB,OAAO,EAAE,YAAY;IACrB,IAAI,EAAE,YAAY;CACnB,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,WAAqC,EACrC,OAAkC;IAElC,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,KAAK,IAAI,CAAC;IACtC,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAClE,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,EAAE,YAAY,IAAI,EAAE,CAAC,CAAC;IAEpD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;QAC/B,MAAM,UAAU,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;QAClC,oFAAoF;QACpF,IAAI,UAAU,KAAK,SAAS;YAAE,SAAS;QACvC,gBAAgB,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;IACrD,CAAC;IAED,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,GAAG,KAAK,CAAC;IAC1C,IAAI,MAAM,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,MAAM,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;IAE7E,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,SAAS,YAAY,CAAC,QAA4B,EAAE,KAAa;IAC/D,IAAI,QAAQ,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IACvE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,QAAQ,GAAG,YAAY,CAAC;AAE9B;;;;;;;;;;GAUG;AACH,SAAS,gBAAgB,CAAC,UAAyB;IACjD,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC;IAC3B,IAAI,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC;IACjC,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtB,
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/diagnostics/format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAuBjD,MAAM,MAAM,GAAG,IAAI,CAAC;AAEpB,kFAAkF;AAClF,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAE/B,MAAM,UAAU,GAAG,WAAW,CAAC;AAC/B,MAAM,QAAQ,GAAG,WAAW,CAAC;AAE7B,MAAM,eAAe,GAA0C;IAC7D,KAAK,EAAE,YAAY;IACnB,OAAO,EAAE,YAAY;IACrB,IAAI,EAAE,YAAY;CACnB,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,WAAqC,EACrC,OAAkC;IAElC,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,KAAK,IAAI,CAAC;IACtC,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAClE,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,EAAE,YAAY,IAAI,EAAE,CAAC,CAAC;IAEpD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;QAC/B,MAAM,UAAU,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;QAClC,oFAAoF;QACpF,IAAI,UAAU,KAAK,SAAS;YAAE,SAAS;QACvC,gBAAgB,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;IACrD,CAAC;IAED,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,GAAG,KAAK,CAAC;IAC1C,IAAI,MAAM,GAAG,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,MAAM,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;IAE7E,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,SAAS,YAAY,CAAC,QAA4B,EAAE,KAAa;IAC/D,IAAI,QAAQ,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IACvE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,QAAQ,GAAG,YAAY,CAAC;AAE9B;;;;;;;;;;GAUG;AACH,SAAS,gBAAgB,CAAC,UAAyB;IACjD,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC;IAC3B,IAAI,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC;IACjC,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtB;;;;;;;;WAQG;QACH,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QACjF,MAAM,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;QAClC,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;IACnD,CAAC;IACD;;;;;;;;OAQG;IACH,IAAI,UAAU,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QACpC,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QACzC,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;IACjD,CAAC;IACD,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC5E,CAAC;IACD,OAAO;QACL,GAAG,UAAU;QACb,OAAO;QACP,GAAG,EAAE,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ;QAC7C,QAAQ,EAAE,SAAS;QACnB,MAAM,EAAE,UAAU,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ;KAC/D,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CACvB,KAAe,EACf,KAAoB,EACpB,KAAc,EACd,YAAiC;IAEjC,MAAM,UAAU,GACd,KAAK,CAAC,KAAK,KAAK,SAAS,IAAI,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAC/F,MAAM,MAAM,GAAG,KAAK,CAClB,GAAG,UAAU,CAAC,QAAQ,KAAK,UAAU,CAAC,IAAI,GAAG,EAC7C,eAAe,CAAC,UAAU,CAAC,QAAQ,CAAC,EACpC,KAAK,CACN,CAAC;IAEF,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACpD,MAAM,KAAK,GAAG,YAAY,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACpC,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;IAC3C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7C,MAAM,IAAI,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;QAC7B,IAAI,IAAI,KAAK,SAAS;YAAE,SAAS;QACjC,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACxC,CAAC;IAED,MAAM,QAAQ,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;IACxC,IAAI,QAAQ,KAAK,SAAS;QAAE,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC3D,IAAI,UAAU,CAAC,GAAG,KAAK,SAAS;QAAE,MAAM,CAAC,KAAK,EAAE,QAAQ,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IACxF,IAAI,UAAU,CAAC,QAAQ,KAAK,SAAS,IAAI,UAAU,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxE,MAAM,CAAC,KAAK,EAAE,UAAU,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IACjE,CAAC;IACD,6FAA6F;IAC7F,+FAA+F;IAC/F,6FAA6F;IAC7F,gGAAgG;IAChG,wFAAwF;IACxF,IAAI,UAAU,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;QACtC,MAAM,CAAC,KAAK,EAAE,aAAa,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAC9E,CAAC;IACD,IAAI,UAAU,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QACpC,MAAM,CAAC,KAAK,EAAE,WAAW,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAC1E,CAAC;IACD,IAAI,UAAU,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;QAC3C,MAAM,CAAC,KAAK,EAAE,SAAS,UAAU,CAAC,aAAa,EAAE,EAAE,KAAK,CAAC,CAAC;IAC5D,CAAC;AACH,CAAC;AAED,SAAS,MAAM,CAAC,KAAe,EAAE,IAAY,EAAE,KAAc;IAC3D,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,GAAG,KAAK,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;AACzD,CAAC;AAED,SAAS,UAAU,CAAC,UAAyB;IAC3C,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,UAAU,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QACxC,KAAK,CAAC,IAAI,CACR,UAAU,CAAC,UAAU,KAAK,SAAS;YACjC,CAAC,CAAC,eAAe,UAAU,CAAC,UAAU,EAAE;YACxC,CAAC,CAAC,eAAe,UAAU,CAAC,UAAU,KAAK,UAAU,CAAC,UAAU,SAAS,CAC5E,CAAC;IACJ,CAAC;IACD,IAAI,UAAU,CAAC,KAAK,KAAK,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IACjE,IAAI,UAAU,CAAC,WAAW,KAAK,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,UAAU,UAAU,CAAC,WAAW,EAAE,CAAC,CAAC;IACzF,IAAI,UAAU,CAAC,WAAW,KAAK,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,UAAU,UAAU,CAAC,WAAW,EAAE,CAAC,CAAC;IACzF,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AACnE,CAAC;AAED,4FAA4F;AAC5F,SAAS,OAAO,CAAC,KAAiB;IAChC,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,mBAAmB,CAAC,CAAC;IACrD,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,IAAI,KAAK,GAAG,EAAE,CAAC;IACf,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QAC7C,KAAK,IAAI,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IACpE,CAAC;IACD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IAC3C,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,KAAK,IAAI,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;AAC/E,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAY;IACpC,OAAO,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,CAAC;AACtC,CAAC;AAED,iGAAiG;AACjG,SAAS,KAAK,CAAC,KAAa;IAC1B,IAAI,GAAG,GAAG,GAAG,CAAC;IACd,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,IAAI;YAAE,GAAG,IAAI,KAAK,IAAI,EAAE,CAAC;aACjD,IAAI,gBAAgB,CAAC,IAAI,CAAC;YAAE,GAAG,IAAI,IAAI,CAAC;aACxC,IAAI,IAAI,IAAI,IAAI;YAAE,GAAG,IAAI,MAAM,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;;YACpE,GAAG,IAAI,OAAO,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC;IAC1C,CAAC;IACD,OAAO,GAAG,GAAG,GAAG,CAAC;AACnB,CAAC;AAED,SAAS,KAAK,CAAC,IAAY,EAAE,KAAa,EAAE,OAAgB;IAC1D,OAAO,OAAO,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,IAAI,GAAG,UAAU,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACzD,CAAC"}
|
package/package.json
CHANGED
package/src/biosemi.ts
CHANGED
|
@@ -19,7 +19,7 @@ import { decodeDigitalCounted } from './decode/digital.js';
|
|
|
19
19
|
import { readRecordBytes } from './io/read.js';
|
|
20
20
|
import { scanChunkRecords } from './record-index.js';
|
|
21
21
|
import { gapBefore } from './recording.js';
|
|
22
|
-
import { secondsToTicks, ticksToSeconds } from './tal/ticks.js';
|
|
22
|
+
import { ceilDiv, secondsToTicks, ticksToSeconds } from './tal/ticks.js';
|
|
23
23
|
import { resolveTimeWindow } from './time/window.js';
|
|
24
24
|
import type {
|
|
25
25
|
EdfHeader,
|
|
@@ -207,10 +207,24 @@ export async function readTriggers(
|
|
|
207
207
|
const changed = previous === undefined || word.trigger !== previous;
|
|
208
208
|
previous = word.trigger;
|
|
209
209
|
|
|
210
|
+
// CEILING, the same rule `gridSampleStartTicks` and `sampleStartTicksOf` use, and for
|
|
211
|
+
// the reason the first of those states in its own comment: a sample boundary need not
|
|
212
|
+
// fall on a whole tick, and truncating returns a tick that lies inside the PREVIOUS
|
|
213
|
+
// sample. 10^7 / 512 is 19531.25, so three boundaries in four are affected on an
|
|
214
|
+
// ordinary 512 Hz BioSemi file.
|
|
215
|
+
//
|
|
216
|
+
// Truncating made an event's own reported time round-trip to the wrong sample —
|
|
217
|
+
// `sampleAt(event.seconds)` returned 100 for an event readTriggers called sample 101 —
|
|
218
|
+
// and a window aligned to the stimulus with `sampleStartSecondsOf` reported the onset a
|
|
219
|
+
// sample late, which at 512 Hz is 2 ms on the one number an ERP pipeline reads. A
|
|
220
|
+
// one-sample-wide trigger at the window's left edge disappeared entirely, contradicting
|
|
221
|
+
// this function's own rule that the first in-window sample always produces an event
|
|
222
|
+
// (fixed in 0.3.32).
|
|
223
|
+
//
|
|
210
224
|
// A zero record duration puts every sample of the record at its start instant.
|
|
211
225
|
const ticks =
|
|
212
226
|
durationTicks > 0n && samplesPerRecord > 0
|
|
213
|
-
? recordStartTicks + (BigInt(s) * durationTicks
|
|
227
|
+
? recordStartTicks + ceilDiv(BigInt(s) * durationTicks, BigInt(samplesPerRecord))
|
|
214
228
|
: recordStartTicks;
|
|
215
229
|
|
|
216
230
|
if (ticks < windowStartTicks || ticks >= windowEndTicks) continue;
|
package/src/constants.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* no iteration over an unordered collection, and no ANSI escapes unless `color` is requested.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import { trimEdfField } from '../bytes/latin1.js';
|
|
12
13
|
import { printable } from '../text/printable.js';
|
|
13
14
|
import type { EdfDiagnostic, EdfSeverity } from '../types.js';
|
|
14
15
|
|
|
@@ -94,10 +95,36 @@ const REDACTED = '[redacted]';
|
|
|
94
95
|
function redactDiagnostic(diagnostic: EdfDiagnostic): EdfDiagnostic {
|
|
95
96
|
const raw = diagnostic.raw;
|
|
96
97
|
let message = diagnostic.message;
|
|
98
|
+
const spellings: string[] = [];
|
|
97
99
|
if (raw !== undefined) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
100
|
+
/*
|
|
101
|
+
* `raw.trim()` is not the same as the field's trimmed value, and that gap was the leak.
|
|
102
|
+
* `trimEdfField` strips 0x20 AND 0x00; `String.prototype.trim` strips whitespace but not
|
|
103
|
+
* U+0000. Every identification diagnostic builds its message from `trimEdfField(raw)`, so on
|
|
104
|
+
* a field padded with NULs — which a large share of real writers emit, and which
|
|
105
|
+
* `header/fields.ts` treats as normal — none of the four spellings matched and the name went
|
|
106
|
+
* through verbatim while `raw:` and `actual:` said `[redacted]`. Output that LOOKS redacted is
|
|
107
|
+
* worse than an obvious leak (fixed in 0.3.31).
|
|
108
|
+
*/
|
|
109
|
+
spellings.push(JSON.stringify(raw), JSON.stringify(raw.trim()), raw, raw.trim());
|
|
110
|
+
const trimmed = trimEdfField(raw);
|
|
111
|
+
spellings.push(trimmed, JSON.stringify(trimmed));
|
|
112
|
+
}
|
|
113
|
+
/*
|
|
114
|
+
* And the diagnostic's own `actual`, computed BEFORE it is replaced below.
|
|
115
|
+
*
|
|
116
|
+
* A message does not have to spell the value the way the field does. `DATE_IMPLAUSIBLE` writes
|
|
117
|
+
* the patient's date of birth as `2050-05-02` while the file says `02-MAY-2050`, so no spelling
|
|
118
|
+
* derived from `raw` could ever match it — and that diagnostic fires on a perfectly conformant
|
|
119
|
+
* identification field, with no NUL padding and no grammar violation needed. `actual` already
|
|
120
|
+
* carries whatever the message chose to print, which is exactly what has to be substituted.
|
|
121
|
+
*/
|
|
122
|
+
if (diagnostic.actual !== undefined) {
|
|
123
|
+
const actual = String(diagnostic.actual);
|
|
124
|
+
spellings.push(actual, JSON.stringify(actual));
|
|
125
|
+
}
|
|
126
|
+
for (const spelling of spellings) {
|
|
127
|
+
if (spelling.length > 0) message = message.split(spelling).join(REDACTED);
|
|
101
128
|
}
|
|
102
129
|
return {
|
|
103
130
|
...diagnostic,
|