edfcore 0.3.54 → 0.3.55
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 +23 -0
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/stream.d.ts.map +1 -1
- package/dist/stream.js +37 -1
- package/dist/stream.js.map +1 -1
- package/package.json +1 -1
- package/src/constants.ts +1 -1
- package/src/stream.ts +41 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,29 @@ 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.55
|
|
10
|
+
|
|
11
|
+
- **Fixed** `streamRecords` never comparing record onsets across a chunk boundary, so an
|
|
12
|
+
always-fatal `TIMELINE_NOT_MONOTONIC` was suppressed and chunks were yielded in reverse time
|
|
13
|
+
order — and whether it fired at all depended on `chunkRecords`.
|
|
14
|
+
- `readRecords` runs `assertMonotonicOnsetArray` over the onsets of the chunk it just read, so
|
|
15
|
+
every adjacent pair **inside** a chunk is checked and no pair that straddles two is.
|
|
16
|
+
`readWindow` hands a whole contiguous run to one call, so it checks all of them; splitting the
|
|
17
|
+
same run into `chunkRecords`-sized reads checked none of the seams.
|
|
18
|
+
- On an eight-record file whose only backwards pair is 3 → 4, `readWindow` threw and
|
|
19
|
+
`streamRecords` threw at `chunkRecords` 3 and 5 while returning the data at 1, 2, 4 and 256.
|
|
20
|
+
At `chunkRecords: 1` **every** pair is a seam, so nothing was checked at all and the chunks came
|
|
21
|
+
back at 0, 1, 2, 4, 3, 5, 6, 7 seconds.
|
|
22
|
+
- `chunkRecords` is documented as "the unit of I/O and of memory". A performance knob must never
|
|
23
|
+
decide whether a file is refused — the fourth time that shape has been swept out of this
|
|
24
|
+
package.
|
|
25
|
+
- A consumer that places each chunk at its own `startSeconds`, which is what the docs prescribe,
|
|
26
|
+
silently overwrote earlier trace with later samples.
|
|
27
|
+
- The seam check costs no extra read. A chunk's span is `lastOnset + recordDuration - firstOnset`,
|
|
28
|
+
so its last record's onset is `startTicks + durationTicks - recordDurationTicks`, already on the
|
|
29
|
+
same rebased axis as the next chunk's `startTicks`. It is reset per run, because `readWindow` does
|
|
30
|
+
not compare across a gap either and a streamed chunk must stay the object a read would give.
|
|
31
|
+
|
|
9
32
|
## 0.3.54
|
|
10
33
|
|
|
11
34
|
- **Fixed** `decodeStatusWord` reading the BioSemi quality flags from the wrong bits. Both
|
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.55";
|
|
113
113
|
//# sourceMappingURL=constants.d.ts.map
|
package/dist/constants.js
CHANGED
package/dist/stream.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;
|
|
1
|
+
{"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,WAAW,EAAe,eAAe,EAAE,MAAM,YAAY,CAAC;AAKpG;;;;;;;GAOG;AACH,wBAAuB,aAAa,CAClC,SAAS,EAAE,YAAY,EACvB,SAAS,EAAE,eAAe,EAC1B,OAAO,CAAC,EAAE,WAAW,GACpB,cAAc,CAAC,QAAQ,EAAE,IAAI,EAAE,SAAS,CAAC,CAgF3C"}
|
package/dist/stream.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* `readWindow` chunk would, so a consumer sees the discontinuities rather than a smooth lie.
|
|
14
14
|
*/
|
|
15
15
|
import { readRecords, resolveSignals } from './recording.js';
|
|
16
|
+
import { assertMonotonicOnsets } from './time/timeline.js';
|
|
16
17
|
import { resolveTimeWindow } from './time/window.js';
|
|
17
18
|
/** How many records to read at a time when the caller does not say. */
|
|
18
19
|
const DEFAULT_STREAM_RECORDS = 256;
|
|
@@ -38,8 +39,12 @@ export async function* streamRecords(recording, selection, options) {
|
|
|
38
39
|
// Every other selection error in the package surfaces on the spot; this one waited for data.
|
|
39
40
|
resolveSignals(recording.header, selection.signalIndices);
|
|
40
41
|
const ranges = resolveTimeWindow(recording.timeline, recording.index, selection.startSeconds, selection.durationSeconds);
|
|
42
|
+
const recordDurationTicks = recording.header.recordDurationTicks;
|
|
41
43
|
for (const run of ranges) {
|
|
42
44
|
let scanned = 0;
|
|
45
|
+
// The last record of the previous chunk of THIS run. Reset per run, because `readWindow` does
|
|
46
|
+
// not compare across a gap either and a streamed chunk must be the same object it would get.
|
|
47
|
+
let previousRecord;
|
|
43
48
|
while (scanned < run.count) {
|
|
44
49
|
const records = {
|
|
45
50
|
start: run.start + scanned,
|
|
@@ -47,7 +52,38 @@ export async function* streamRecords(recording, selection, options) {
|
|
|
47
52
|
};
|
|
48
53
|
// readRecords, not a private path: the chunk a consumer gets from streaming and the chunk
|
|
49
54
|
// it gets from reading must be the same object in every respect, including its diagnostics.
|
|
50
|
-
|
|
55
|
+
const chunk = await readRecords(recording, { signalIndices: selection.signalIndices, records }, options);
|
|
56
|
+
/*
|
|
57
|
+
* The one pair `readRecords` structurally cannot see: the SEAM between two chunks.
|
|
58
|
+
*
|
|
59
|
+
* It runs `assertMonotonicOnsetArray` over the onsets of the chunk it just read, so every
|
|
60
|
+
* adjacent pair inside a chunk is compared and no pair that straddles two is. `readWindow`
|
|
61
|
+
* hands a whole contiguous run to one call and therefore checks all of them; splitting the
|
|
62
|
+
* same run into `chunkRecords`-sized reads checked none of the seams.
|
|
63
|
+
*
|
|
64
|
+
* TIMELINE_NOT_MONOTONIC is ALWAYS fatal — `readWindow` on the identical window throws —
|
|
65
|
+
* and the streaming path returned the data instead, in reverse time order: a consumer that
|
|
66
|
+
* places each chunk at its own `startSeconds`, which is what the docs prescribe, overwrote
|
|
67
|
+
* earlier trace with later samples. Worse, whether it fired at all depended on
|
|
68
|
+
* `chunkRecords`: on one eight-record file it threw at 3 and 5 and stayed silent at 1, 2, 4
|
|
69
|
+
* and 256, and `chunkRecords` is documented as a memory knob. A performance parameter must
|
|
70
|
+
* never decide whether a file is refused (fixed in 0.3.55).
|
|
71
|
+
*
|
|
72
|
+
* No extra read: a chunk's span is `lastOnset + recordDuration - firstOnset`, so its last
|
|
73
|
+
* record's onset is `startTicks + durationTicks - recordDurationTicks`, on the same rebased
|
|
74
|
+
* axis as the next chunk's `startTicks`.
|
|
75
|
+
*/
|
|
76
|
+
if (previousRecord !== undefined) {
|
|
77
|
+
assertMonotonicOnsets(previousRecord, {
|
|
78
|
+
recordIndex: records.start,
|
|
79
|
+
onsetTicks: chunk.startTicks,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
previousRecord = {
|
|
83
|
+
recordIndex: records.start + records.count - 1,
|
|
84
|
+
onsetTicks: chunk.startTicks + chunk.durationTicks - recordDurationTicks,
|
|
85
|
+
};
|
|
86
|
+
yield chunk;
|
|
51
87
|
scanned += records.count;
|
|
52
88
|
}
|
|
53
89
|
}
|
package/dist/stream.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream.js","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAGrD,uEAAuE;AACvE,MAAM,sBAAsB,GAAG,GAAG,CAAC;AAEnC;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC,aAAa,CAClC,SAAuB,EACvB,SAA0B,EAC1B,OAAqB;IAErB,MAAM,YAAY,GAAG,SAAS,CAAC,YAAY,IAAI,sBAAsB,CAAC;IACtE,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;QAC5D,MAAM,IAAI,UAAU,CAClB,2EAA2E,YAAY,IAAI;YACzF,mFAAmF,CACtF,CAAC;IACJ,CAAC;IAED,+FAA+F;IAC/F,+FAA+F;IAC/F,2FAA2F;IAC3F,sFAAsF;IACtF,6FAA6F;IAC7F,6FAA6F;IAC7F,cAAc,CAAC,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,aAAa,CAAC,CAAC;IAE1D,MAAM,MAAM,GAAG,iBAAiB,CAC9B,SAAS,CAAC,QAAQ,EAClB,SAAS,CAAC,KAAK,EACf,SAAS,CAAC,YAAY,EACtB,SAAS,CAAC,eAAe,CAC1B,CAAC;IAEF,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QACzB,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,OAAO,OAAO,GAAG,GAAG,CAAC,KAAK,EAAE,CAAC;YAC3B,MAAM,OAAO,GAAgB;gBAC3B,KAAK,EAAE,GAAG,CAAC,KAAK,GAAG,OAAO;gBAC1B,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,GAAG,CAAC,KAAK,GAAG,OAAO,CAAC;aACnD,CAAC;YACF,0FAA0F;YAC1F,4FAA4F;YAC5F,MAAM,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"stream.js","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAGrD,uEAAuE;AACvE,MAAM,sBAAsB,GAAG,GAAG,CAAC;AAEnC;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC,aAAa,CAClC,SAAuB,EACvB,SAA0B,EAC1B,OAAqB;IAErB,MAAM,YAAY,GAAG,SAAS,CAAC,YAAY,IAAI,sBAAsB,CAAC;IACtE,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;QAC5D,MAAM,IAAI,UAAU,CAClB,2EAA2E,YAAY,IAAI;YACzF,mFAAmF,CACtF,CAAC;IACJ,CAAC;IAED,+FAA+F;IAC/F,+FAA+F;IAC/F,2FAA2F;IAC3F,sFAAsF;IACtF,6FAA6F;IAC7F,6FAA6F;IAC7F,cAAc,CAAC,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,aAAa,CAAC,CAAC;IAE1D,MAAM,MAAM,GAAG,iBAAiB,CAC9B,SAAS,CAAC,QAAQ,EAClB,SAAS,CAAC,KAAK,EACf,SAAS,CAAC,YAAY,EACtB,SAAS,CAAC,eAAe,CAC1B,CAAC;IAEF,MAAM,mBAAmB,GAAG,SAAS,CAAC,MAAM,CAAC,mBAAmB,CAAC;IAEjE,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QACzB,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,8FAA8F;QAC9F,6FAA6F;QAC7F,IAAI,cAAuE,CAAC;QAE5E,OAAO,OAAO,GAAG,GAAG,CAAC,KAAK,EAAE,CAAC;YAC3B,MAAM,OAAO,GAAgB;gBAC3B,KAAK,EAAE,GAAG,CAAC,KAAK,GAAG,OAAO;gBAC1B,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,GAAG,CAAC,KAAK,GAAG,OAAO,CAAC;aACnD,CAAC;YACF,0FAA0F;YAC1F,4FAA4F;YAC5F,MAAM,KAAK,GAAG,MAAM,WAAW,CAC7B,SAAS,EACT,EAAE,aAAa,EAAE,SAAS,CAAC,aAAa,EAAE,OAAO,EAAE,EACnD,OAAO,CACR,CAAC;YAEF;;;;;;;;;;;;;;;;;;;eAmBG;YACH,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;gBACjC,qBAAqB,CAAC,cAAc,EAAE;oBACpC,WAAW,EAAE,OAAO,CAAC,KAAK;oBAC1B,UAAU,EAAE,KAAK,CAAC,UAAU;iBAC7B,CAAC,CAAC;YACL,CAAC;YACD,cAAc,GAAG;gBACf,WAAW,EAAE,OAAO,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,GAAG,CAAC;gBAC9C,UAAU,EAAE,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,aAAa,GAAG,mBAAmB;aACzE,CAAC;YAEF,MAAM,KAAK,CAAC;YACZ,OAAO,IAAI,OAAO,CAAC,KAAK,CAAC;QAC3B,CAAC;IACH,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
package/src/constants.ts
CHANGED
package/src/stream.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { readRecords, resolveSignals } from './recording.js';
|
|
17
|
+
import { assertMonotonicOnsets } from './time/timeline.js';
|
|
17
18
|
import { resolveTimeWindow } from './time/window.js';
|
|
18
19
|
import type { EdfChunk, EdfRecording, ReadOptions, RecordRange, StreamSelection } from './types.js';
|
|
19
20
|
|
|
@@ -56,8 +57,14 @@ export async function* streamRecords(
|
|
|
56
57
|
selection.durationSeconds,
|
|
57
58
|
);
|
|
58
59
|
|
|
60
|
+
const recordDurationTicks = recording.header.recordDurationTicks;
|
|
61
|
+
|
|
59
62
|
for (const run of ranges) {
|
|
60
63
|
let scanned = 0;
|
|
64
|
+
// The last record of the previous chunk of THIS run. Reset per run, because `readWindow` does
|
|
65
|
+
// not compare across a gap either and a streamed chunk must be the same object it would get.
|
|
66
|
+
let previousRecord: { recordIndex: number; onsetTicks: bigint } | undefined;
|
|
67
|
+
|
|
61
68
|
while (scanned < run.count) {
|
|
62
69
|
const records: RecordRange = {
|
|
63
70
|
start: run.start + scanned,
|
|
@@ -65,11 +72,44 @@ export async function* streamRecords(
|
|
|
65
72
|
};
|
|
66
73
|
// readRecords, not a private path: the chunk a consumer gets from streaming and the chunk
|
|
67
74
|
// it gets from reading must be the same object in every respect, including its diagnostics.
|
|
68
|
-
|
|
75
|
+
const chunk = await readRecords(
|
|
69
76
|
recording,
|
|
70
77
|
{ signalIndices: selection.signalIndices, records },
|
|
71
78
|
options,
|
|
72
79
|
);
|
|
80
|
+
|
|
81
|
+
/*
|
|
82
|
+
* The one pair `readRecords` structurally cannot see: the SEAM between two chunks.
|
|
83
|
+
*
|
|
84
|
+
* It runs `assertMonotonicOnsetArray` over the onsets of the chunk it just read, so every
|
|
85
|
+
* adjacent pair inside a chunk is compared and no pair that straddles two is. `readWindow`
|
|
86
|
+
* hands a whole contiguous run to one call and therefore checks all of them; splitting the
|
|
87
|
+
* same run into `chunkRecords`-sized reads checked none of the seams.
|
|
88
|
+
*
|
|
89
|
+
* TIMELINE_NOT_MONOTONIC is ALWAYS fatal — `readWindow` on the identical window throws —
|
|
90
|
+
* and the streaming path returned the data instead, in reverse time order: a consumer that
|
|
91
|
+
* places each chunk at its own `startSeconds`, which is what the docs prescribe, overwrote
|
|
92
|
+
* earlier trace with later samples. Worse, whether it fired at all depended on
|
|
93
|
+
* `chunkRecords`: on one eight-record file it threw at 3 and 5 and stayed silent at 1, 2, 4
|
|
94
|
+
* and 256, and `chunkRecords` is documented as a memory knob. A performance parameter must
|
|
95
|
+
* never decide whether a file is refused (fixed in 0.3.55).
|
|
96
|
+
*
|
|
97
|
+
* No extra read: a chunk's span is `lastOnset + recordDuration - firstOnset`, so its last
|
|
98
|
+
* record's onset is `startTicks + durationTicks - recordDurationTicks`, on the same rebased
|
|
99
|
+
* axis as the next chunk's `startTicks`.
|
|
100
|
+
*/
|
|
101
|
+
if (previousRecord !== undefined) {
|
|
102
|
+
assertMonotonicOnsets(previousRecord, {
|
|
103
|
+
recordIndex: records.start,
|
|
104
|
+
onsetTicks: chunk.startTicks,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
previousRecord = {
|
|
108
|
+
recordIndex: records.start + records.count - 1,
|
|
109
|
+
onsetTicks: chunk.startTicks + chunk.durationTicks - recordDurationTicks,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
yield chunk;
|
|
73
113
|
scanned += records.count;
|
|
74
114
|
}
|
|
75
115
|
}
|