edfcore 0.2.7 → 0.2.8
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 +11 -0
- package/dist/chunks.d.ts +29 -0
- package/dist/chunks.d.ts.map +1 -0
- package/dist/chunks.js +139 -0
- package/dist/chunks.js.map +1 -0
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/chunks.ts +164 -0
- package/src/constants.ts +1 -1
- package/src/index.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,17 @@ 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.2.8
|
|
10
|
+
|
|
11
|
+
- **Added** `mergeChunks(chunks)`. `readWindow` splits at every discontinuity, and joining the
|
|
12
|
+
pieces by hand is where the gap gets lost: concatenating two runs five minutes apart dates every
|
|
13
|
+
sample after the join five minutes early, with nothing in the result to say so. This refuses,
|
|
14
|
+
and refuses a chunk already narrowed by `trimToWindow` too — that one is invisible to a record
|
|
15
|
+
adjacency check.
|
|
16
|
+
- **Fixed** the 200,000-record call-stack test timing out under load. It ran within a few hundred
|
|
17
|
+
milliseconds of vitest's 5 s default on its own, so it failed intermittently in a full run and
|
|
18
|
+
the failure read as a regression in code it does not touch.
|
|
19
|
+
|
|
9
20
|
## 0.2.7
|
|
10
21
|
|
|
11
22
|
- **Added** `edfcore gaps <file>`. Runs a full scan rather than reading the two probes `openEdf`
|
package/dist/chunks.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Joining chunks that a read returned as several.
|
|
3
|
+
*
|
|
4
|
+
* Layer 7, and pure: nothing here reads. `readWindow` splits at every discontinuity, so a window
|
|
5
|
+
* over an EDF+D file comes back as one chunk per contiguous run. Code that then wants ONE array —
|
|
6
|
+
* a filter, an FFT, a CSV writer — has to join them, and joining is where the gap gets lost.
|
|
7
|
+
*
|
|
8
|
+
* Concatenating two runs separated by five minutes produces an array in which sample `i` and
|
|
9
|
+
* sample `i + 1` are five minutes apart. Every time derived from an index past that point is
|
|
10
|
+
* wrong by five minutes, and nothing in the result says so. `mergeChunks` refuses instead. A
|
|
11
|
+
* caller who genuinely wants the samples end to end can concatenate them in three lines and own
|
|
12
|
+
* the consequence; what they should not get is a helper that hides it.
|
|
13
|
+
*
|
|
14
|
+
* The refusals are caller mistakes, not file defects, so they are plain `RangeError`s — the same
|
|
15
|
+
* convention every option check in the package follows.
|
|
16
|
+
*/
|
|
17
|
+
import type { EdfChunk } from './types.js';
|
|
18
|
+
/**
|
|
19
|
+
* One chunk covering every input chunk, or a `RangeError` explaining why they do not join.
|
|
20
|
+
*
|
|
21
|
+
* Accepts only chunks that are adjacent, in order, gapless, and read with the same signals in the
|
|
22
|
+
* same order. A single chunk is returned as-is, so the common continuous-file case costs nothing.
|
|
23
|
+
*
|
|
24
|
+
* The samples are copied, so the result holds a second copy of the data the inputs already hold.
|
|
25
|
+
* That is unavoidable — `Int32Array`s are not splices of one buffer — and it is why this is a
|
|
26
|
+
* separate call rather than something `readWindow` does on the way out.
|
|
27
|
+
*/
|
|
28
|
+
export declare function mergeChunks(chunks: readonly EdfChunk[]): EdfChunk;
|
|
29
|
+
//# sourceMappingURL=chunks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunks.d.ts","sourceRoot":"","sources":["../src/chunks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAiC,MAAM,YAAY,CAAC;AA+D1E;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,QAAQ,EAAE,GAAG,QAAQ,CAwEjE"}
|
package/dist/chunks.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Joining chunks that a read returned as several.
|
|
3
|
+
*
|
|
4
|
+
* Layer 7, and pure: nothing here reads. `readWindow` splits at every discontinuity, so a window
|
|
5
|
+
* over an EDF+D file comes back as one chunk per contiguous run. Code that then wants ONE array —
|
|
6
|
+
* a filter, an FFT, a CSV writer — has to join them, and joining is where the gap gets lost.
|
|
7
|
+
*
|
|
8
|
+
* Concatenating two runs separated by five minutes produces an array in which sample `i` and
|
|
9
|
+
* sample `i + 1` are five minutes apart. Every time derived from an index past that point is
|
|
10
|
+
* wrong by five minutes, and nothing in the result says so. `mergeChunks` refuses instead. A
|
|
11
|
+
* caller who genuinely wants the samples end to end can concatenate them in three lines and own
|
|
12
|
+
* the consequence; what they should not get is a helper that hides it.
|
|
13
|
+
*
|
|
14
|
+
* The refusals are caller mistakes, not file defects, so they are plain `RangeError`s — the same
|
|
15
|
+
* convention every option check in the package follows.
|
|
16
|
+
*/
|
|
17
|
+
import { appendDiagnostics } from './diagnostics/collector.js';
|
|
18
|
+
/** Reads as one line at the call site, and keeps the `chunks[i]` non-null assertions out of it. */
|
|
19
|
+
function at(chunks, index) {
|
|
20
|
+
const chunk = chunks[index];
|
|
21
|
+
if (chunk === undefined)
|
|
22
|
+
throw new RangeError(`mergeChunks: no chunk at ${index}.`);
|
|
23
|
+
return chunk;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Everything that makes two chunks joinable, checked before a byte is allocated.
|
|
27
|
+
*
|
|
28
|
+
* The record-adjacency test is the obvious one. The per-signal sample-index test is the one that
|
|
29
|
+
* earns its place: `trimToWindow` narrows a chunk on each signal's own grid, so two chunks can
|
|
30
|
+
* still be record-adjacent after a trim has removed the samples between them. Comparing
|
|
31
|
+
* `firstSampleIndex` against the previous chunk's end catches exactly that, per signal, which is
|
|
32
|
+
* the granularity at which it actually happens.
|
|
33
|
+
*/
|
|
34
|
+
function assertJoinable(previous, next, index) {
|
|
35
|
+
if (next.precededByGap !== undefined) {
|
|
36
|
+
throw new RangeError(`mergeChunks: chunk ${index} is preceded by a gap of ${next.precededByGap.durationSeconds} s. ` +
|
|
37
|
+
'Concatenating across it would put two samples that are seconds apart next to each other ' +
|
|
38
|
+
'in one array, and every time computed from an index after the join would be wrong by the ' +
|
|
39
|
+
'gap. Merge each contiguous run separately.');
|
|
40
|
+
}
|
|
41
|
+
const expectedStart = previous.records.start + previous.records.count;
|
|
42
|
+
if (next.records.start !== expectedStart) {
|
|
43
|
+
throw new RangeError(`mergeChunks: chunk ${index} starts at record ${next.records.start}, but the chunk before ` +
|
|
44
|
+
`it ends at ${expectedStart}. Chunks must be adjacent and in order.`);
|
|
45
|
+
}
|
|
46
|
+
if (next.signals.length !== previous.signals.length) {
|
|
47
|
+
throw new RangeError(`mergeChunks: chunk ${index} carries ${next.signals.length} signal(s), the chunk before it ` +
|
|
48
|
+
`${previous.signals.length}. Every chunk must have been read with the same signal selection.`);
|
|
49
|
+
}
|
|
50
|
+
for (let i = 0; i < next.signals.length; i += 1) {
|
|
51
|
+
const before = previous.signals[i];
|
|
52
|
+
const after = next.signals[i];
|
|
53
|
+
if (after.signalIndex !== before.signalIndex) {
|
|
54
|
+
throw new RangeError(`mergeChunks: chunk ${index} has signal ${after.signalIndex} in position ${i}, the chunk ` +
|
|
55
|
+
`before it signal ${before.signalIndex}. The selection must be in the same order too.`);
|
|
56
|
+
}
|
|
57
|
+
const expectedSample = before.firstSampleIndex + before.sampleCount;
|
|
58
|
+
if (after.firstSampleIndex !== expectedSample) {
|
|
59
|
+
throw new RangeError(`mergeChunks: signal ${after.signalIndex} of chunk ${index} starts at sample ` +
|
|
60
|
+
`${after.firstSampleIndex}, but the chunk before it ends at ${expectedSample}. ` +
|
|
61
|
+
'A trimmed chunk cannot be merged with the one after it — trim after merging, not before.');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* One chunk covering every input chunk, or a `RangeError` explaining why they do not join.
|
|
67
|
+
*
|
|
68
|
+
* Accepts only chunks that are adjacent, in order, gapless, and read with the same signals in the
|
|
69
|
+
* same order. A single chunk is returned as-is, so the common continuous-file case costs nothing.
|
|
70
|
+
*
|
|
71
|
+
* The samples are copied, so the result holds a second copy of the data the inputs already hold.
|
|
72
|
+
* That is unavoidable — `Int32Array`s are not splices of one buffer — and it is why this is a
|
|
73
|
+
* separate call rather than something `readWindow` does on the way out.
|
|
74
|
+
*/
|
|
75
|
+
export function mergeChunks(chunks) {
|
|
76
|
+
if (chunks.length === 0) {
|
|
77
|
+
throw new RangeError('mergeChunks: nothing to merge. `readWindow` returns [] for a window that lands past the ' +
|
|
78
|
+
'end of the recording, so check the length before merging.');
|
|
79
|
+
}
|
|
80
|
+
const first = at(chunks, 0);
|
|
81
|
+
if (chunks.length === 1)
|
|
82
|
+
return first;
|
|
83
|
+
for (let i = 1; i < chunks.length; i += 1) {
|
|
84
|
+
assertJoinable(at(chunks, i - 1), at(chunks, i), i);
|
|
85
|
+
}
|
|
86
|
+
const last = at(chunks, chunks.length - 1);
|
|
87
|
+
const signals = first.signals.map((firstSignal, position) => {
|
|
88
|
+
let total = 0;
|
|
89
|
+
let outOfRange = 0;
|
|
90
|
+
for (const chunk of chunks) {
|
|
91
|
+
const signal = chunk.signals[position];
|
|
92
|
+
total += signal.sampleCount;
|
|
93
|
+
outOfRange += signal.outOfDigitalRangeCount;
|
|
94
|
+
}
|
|
95
|
+
const digital = new Int32Array(total);
|
|
96
|
+
let written = 0;
|
|
97
|
+
for (const chunk of chunks) {
|
|
98
|
+
const signal = chunk.signals[position];
|
|
99
|
+
// `digital` may be longer than `sampleCount` — the decoder is allowed to hand back a buffer
|
|
100
|
+
// it sized for whole records. `sampleCount` is the truth, so the subarray is taken from it.
|
|
101
|
+
digital.set(signal.digital.subarray(0, signal.sampleCount), written);
|
|
102
|
+
written += signal.sampleCount;
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
signalIndex: firstSignal.signalIndex,
|
|
106
|
+
sampleCount: total,
|
|
107
|
+
digital,
|
|
108
|
+
firstSampleIndex: firstSignal.firstSampleIndex,
|
|
109
|
+
startSeconds: firstSignal.startSeconds,
|
|
110
|
+
outOfDigitalRangeCount: outOfRange,
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
let byteLength = 0;
|
|
114
|
+
const diagnostics = [];
|
|
115
|
+
for (const chunk of chunks) {
|
|
116
|
+
byteLength += chunk.byteLength;
|
|
117
|
+
// Not `push(...chunk.diagnostics)`: a scan over a damaged annotation section reports one
|
|
118
|
+
// diagnostic per record, and the spread blows the call stack past ~125,000 of them (0.1.6).
|
|
119
|
+
appendDiagnostics(diagnostics, chunk.diagnostics);
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
records: {
|
|
123
|
+
start: first.records.start,
|
|
124
|
+
count: last.records.start + last.records.count - first.records.start,
|
|
125
|
+
},
|
|
126
|
+
startSeconds: first.startSeconds,
|
|
127
|
+
// Two float operations against the ends, not a sum of N durations: adding the durations up
|
|
128
|
+
// accumulates rounding once per chunk, and the run is contiguous so the ends are the truth.
|
|
129
|
+
durationSeconds: last.startSeconds + last.durationSeconds - first.startSeconds,
|
|
130
|
+
byteOffset: first.byteOffset,
|
|
131
|
+
byteLength,
|
|
132
|
+
signals: Object.freeze(signals),
|
|
133
|
+
// The gap BEFORE the whole run survives the merge. It describes what precedes the first
|
|
134
|
+
// chunk, which is still what precedes the merged one.
|
|
135
|
+
precededByGap: first.precededByGap,
|
|
136
|
+
diagnostics: Object.freeze(diagnostics),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=chunks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunks.js","sourceRoot":"","sources":["../src/chunks.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.2.
|
|
112
|
+
export declare const VERSION = "0.2.8";
|
|
113
113
|
//# sourceMappingURL=constants.d.ts.map
|
package/dist/constants.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -43,6 +43,7 @@ export { readHeader, readRecordBytes } from './io/read.js';
|
|
|
43
43
|
export { buildRecordIndex, buildTimeline, contiguityOf } from './record-index.js';
|
|
44
44
|
export { annotationsAt, countAnnotationsByText, filterAnnotationsByText, filterAnnotationsByTime, } from './annotations-query.js';
|
|
45
45
|
export { decodeStatusWord, getStatusSignal, readTriggers } from './biosemi.js';
|
|
46
|
+
export { mergeChunks } from './chunks.js';
|
|
46
47
|
export { envelopeOfSamples, readEnvelope, readEnvelopeAtResolution, toPhysicalEnvelope, } from './envelope.js';
|
|
47
48
|
export { formatHeader } from './format-header.js';
|
|
48
49
|
export { inspectEdf } from './inspect.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -56,6 +56,7 @@ export { buildRecordIndex, buildTimeline, contiguityOf } from './record-index.js
|
|
|
56
56
|
// ===========================================================================
|
|
57
57
|
export { annotationsAt, countAnnotationsByText, filterAnnotationsByText, filterAnnotationsByTime, } from './annotations-query.js';
|
|
58
58
|
export { decodeStatusWord, getStatusSignal, readTriggers } from './biosemi.js';
|
|
59
|
+
export { mergeChunks } from './chunks.js';
|
|
59
60
|
export { envelopeOfSamples, readEnvelope, readEnvelopeAtResolution, toPhysicalEnvelope, } from './envelope.js';
|
|
60
61
|
export { formatHeader } from './format-header.js';
|
|
61
62
|
export { inspectEdf } from './inspect.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AA0EH,OAAO,EACL,wBAAwB,EACxB,cAAc,EACd,uBAAuB,EACvB,QAAQ,EACR,cAAc,EACd,aAAa,EACb,eAAe,EACf,cAAc,EACd,UAAU,GACX,MAAM,aAAa,CAAC;AAErB,8EAA8E;AAC9E,YAAY;AACZ,8EAA8E;AAE9E,OAAO,EACL,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,sBAAsB,EACtB,gCAAgC,EAChC,gBAAgB,EAChB,OAAO,GACR,MAAM,gBAAgB,CAAC;AAExB,8EAA8E;AAC9E,2EAA2E;AAC3E,2CAA2C;AAC3C,8EAA8E;AAE9E,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EACL,uBAAuB,EACvB,WAAW,EACX,SAAS,EACT,iBAAiB,EACjB,YAAY,GACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEnE,8EAA8E;AAC9E,yEAAyE;AACzE,2EAA2E;AAC3E,6CAA6C;AAC7C,8EAA8E;AAE9E,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,8EAA8E;AAC9E,uCAAuC;AACvC,8EAA8E;AAE9E,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAElF,8EAA8E;AAC9E,oBAAoB;AACpB,8EAA8E;AAE9E,OAAO,EACL,aAAa,EACb,sBAAsB,EACtB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC/E,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACnF,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AA0EH,OAAO,EACL,wBAAwB,EACxB,cAAc,EACd,uBAAuB,EACvB,QAAQ,EACR,cAAc,EACd,aAAa,EACb,eAAe,EACf,cAAc,EACd,UAAU,GACX,MAAM,aAAa,CAAC;AAErB,8EAA8E;AAC9E,YAAY;AACZ,8EAA8E;AAE9E,OAAO,EACL,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,qBAAqB,EACrB,eAAe,EACf,eAAe,EACf,sBAAsB,EACtB,gCAAgC,EAChC,gBAAgB,EAChB,OAAO,GACR,MAAM,gBAAgB,CAAC;AAExB,8EAA8E;AAC9E,2EAA2E;AAC3E,2CAA2C;AAC3C,8EAA8E;AAE9E,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EACL,uBAAuB,EACvB,WAAW,EACX,SAAS,EACT,iBAAiB,EACjB,YAAY,GACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEnE,8EAA8E;AAC9E,yEAAyE;AACzE,2EAA2E;AAC3E,6CAA6C;AAC7C,8EAA8E;AAE9E,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,8EAA8E;AAC9E,uCAAuC;AACvC,8EAA8E;AAE9E,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAElF,8EAA8E;AAC9E,oBAAoB;AACpB,8EAA8E;AAE9E,OAAO,EACL,aAAa,EACb,sBAAsB,EACtB,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC/E,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EACL,iBAAiB,EACjB,YAAY,EACZ,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACnF,OAAO,EACL,aAAa,EACb,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
|
package/package.json
CHANGED
package/src/chunks.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Joining chunks that a read returned as several.
|
|
3
|
+
*
|
|
4
|
+
* Layer 7, and pure: nothing here reads. `readWindow` splits at every discontinuity, so a window
|
|
5
|
+
* over an EDF+D file comes back as one chunk per contiguous run. Code that then wants ONE array —
|
|
6
|
+
* a filter, an FFT, a CSV writer — has to join them, and joining is where the gap gets lost.
|
|
7
|
+
*
|
|
8
|
+
* Concatenating two runs separated by five minutes produces an array in which sample `i` and
|
|
9
|
+
* sample `i + 1` are five minutes apart. Every time derived from an index past that point is
|
|
10
|
+
* wrong by five minutes, and nothing in the result says so. `mergeChunks` refuses instead. A
|
|
11
|
+
* caller who genuinely wants the samples end to end can concatenate them in three lines and own
|
|
12
|
+
* the consequence; what they should not get is a helper that hides it.
|
|
13
|
+
*
|
|
14
|
+
* The refusals are caller mistakes, not file defects, so they are plain `RangeError`s — the same
|
|
15
|
+
* convention every option check in the package follows.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { appendDiagnostics } from './diagnostics/collector.js';
|
|
19
|
+
import type { EdfChunk, EdfChunkSignal, EdfDiagnostic } from './types.js';
|
|
20
|
+
|
|
21
|
+
/** Reads as one line at the call site, and keeps the `chunks[i]` non-null assertions out of it. */
|
|
22
|
+
function at(chunks: readonly EdfChunk[], index: number): EdfChunk {
|
|
23
|
+
const chunk = chunks[index];
|
|
24
|
+
if (chunk === undefined) throw new RangeError(`mergeChunks: no chunk at ${index}.`);
|
|
25
|
+
return chunk;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Everything that makes two chunks joinable, checked before a byte is allocated.
|
|
30
|
+
*
|
|
31
|
+
* The record-adjacency test is the obvious one. The per-signal sample-index test is the one that
|
|
32
|
+
* earns its place: `trimToWindow` narrows a chunk on each signal's own grid, so two chunks can
|
|
33
|
+
* still be record-adjacent after a trim has removed the samples between them. Comparing
|
|
34
|
+
* `firstSampleIndex` against the previous chunk's end catches exactly that, per signal, which is
|
|
35
|
+
* the granularity at which it actually happens.
|
|
36
|
+
*/
|
|
37
|
+
function assertJoinable(previous: EdfChunk, next: EdfChunk, index: number): void {
|
|
38
|
+
if (next.precededByGap !== undefined) {
|
|
39
|
+
throw new RangeError(
|
|
40
|
+
`mergeChunks: chunk ${index} is preceded by a gap of ${next.precededByGap.durationSeconds} s. ` +
|
|
41
|
+
'Concatenating across it would put two samples that are seconds apart next to each other ' +
|
|
42
|
+
'in one array, and every time computed from an index after the join would be wrong by the ' +
|
|
43
|
+
'gap. Merge each contiguous run separately.',
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const expectedStart = previous.records.start + previous.records.count;
|
|
48
|
+
if (next.records.start !== expectedStart) {
|
|
49
|
+
throw new RangeError(
|
|
50
|
+
`mergeChunks: chunk ${index} starts at record ${next.records.start}, but the chunk before ` +
|
|
51
|
+
`it ends at ${expectedStart}. Chunks must be adjacent and in order.`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (next.signals.length !== previous.signals.length) {
|
|
56
|
+
throw new RangeError(
|
|
57
|
+
`mergeChunks: chunk ${index} carries ${next.signals.length} signal(s), the chunk before it ` +
|
|
58
|
+
`${previous.signals.length}. Every chunk must have been read with the same signal selection.`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
for (let i = 0; i < next.signals.length; i += 1) {
|
|
63
|
+
const before = previous.signals[i] as EdfChunkSignal;
|
|
64
|
+
const after = next.signals[i] as EdfChunkSignal;
|
|
65
|
+
if (after.signalIndex !== before.signalIndex) {
|
|
66
|
+
throw new RangeError(
|
|
67
|
+
`mergeChunks: chunk ${index} has signal ${after.signalIndex} in position ${i}, the chunk ` +
|
|
68
|
+
`before it signal ${before.signalIndex}. The selection must be in the same order too.`,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
const expectedSample = before.firstSampleIndex + before.sampleCount;
|
|
72
|
+
if (after.firstSampleIndex !== expectedSample) {
|
|
73
|
+
throw new RangeError(
|
|
74
|
+
`mergeChunks: signal ${after.signalIndex} of chunk ${index} starts at sample ` +
|
|
75
|
+
`${after.firstSampleIndex}, but the chunk before it ends at ${expectedSample}. ` +
|
|
76
|
+
'A trimmed chunk cannot be merged with the one after it — trim after merging, not before.',
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* One chunk covering every input chunk, or a `RangeError` explaining why they do not join.
|
|
84
|
+
*
|
|
85
|
+
* Accepts only chunks that are adjacent, in order, gapless, and read with the same signals in the
|
|
86
|
+
* same order. A single chunk is returned as-is, so the common continuous-file case costs nothing.
|
|
87
|
+
*
|
|
88
|
+
* The samples are copied, so the result holds a second copy of the data the inputs already hold.
|
|
89
|
+
* That is unavoidable — `Int32Array`s are not splices of one buffer — and it is why this is a
|
|
90
|
+
* separate call rather than something `readWindow` does on the way out.
|
|
91
|
+
*/
|
|
92
|
+
export function mergeChunks(chunks: readonly EdfChunk[]): EdfChunk {
|
|
93
|
+
if (chunks.length === 0) {
|
|
94
|
+
throw new RangeError(
|
|
95
|
+
'mergeChunks: nothing to merge. `readWindow` returns [] for a window that lands past the ' +
|
|
96
|
+
'end of the recording, so check the length before merging.',
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const first = at(chunks, 0);
|
|
101
|
+
if (chunks.length === 1) return first;
|
|
102
|
+
|
|
103
|
+
for (let i = 1; i < chunks.length; i += 1) {
|
|
104
|
+
assertJoinable(at(chunks, i - 1), at(chunks, i), i);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const last = at(chunks, chunks.length - 1);
|
|
108
|
+
|
|
109
|
+
const signals = first.signals.map((firstSignal, position) => {
|
|
110
|
+
let total = 0;
|
|
111
|
+
let outOfRange = 0;
|
|
112
|
+
for (const chunk of chunks) {
|
|
113
|
+
const signal = chunk.signals[position] as EdfChunkSignal;
|
|
114
|
+
total += signal.sampleCount;
|
|
115
|
+
outOfRange += signal.outOfDigitalRangeCount;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const digital = new Int32Array(total);
|
|
119
|
+
let written = 0;
|
|
120
|
+
for (const chunk of chunks) {
|
|
121
|
+
const signal = chunk.signals[position] as EdfChunkSignal;
|
|
122
|
+
// `digital` may be longer than `sampleCount` — the decoder is allowed to hand back a buffer
|
|
123
|
+
// it sized for whole records. `sampleCount` is the truth, so the subarray is taken from it.
|
|
124
|
+
digital.set(signal.digital.subarray(0, signal.sampleCount), written);
|
|
125
|
+
written += signal.sampleCount;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
signalIndex: firstSignal.signalIndex,
|
|
130
|
+
sampleCount: total,
|
|
131
|
+
digital,
|
|
132
|
+
firstSampleIndex: firstSignal.firstSampleIndex,
|
|
133
|
+
startSeconds: firstSignal.startSeconds,
|
|
134
|
+
outOfDigitalRangeCount: outOfRange,
|
|
135
|
+
} satisfies EdfChunkSignal;
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
let byteLength = 0;
|
|
139
|
+
const diagnostics: EdfDiagnostic[] = [];
|
|
140
|
+
for (const chunk of chunks) {
|
|
141
|
+
byteLength += chunk.byteLength;
|
|
142
|
+
// Not `push(...chunk.diagnostics)`: a scan over a damaged annotation section reports one
|
|
143
|
+
// diagnostic per record, and the spread blows the call stack past ~125,000 of them (0.1.6).
|
|
144
|
+
appendDiagnostics(diagnostics, chunk.diagnostics);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
records: {
|
|
149
|
+
start: first.records.start,
|
|
150
|
+
count: last.records.start + last.records.count - first.records.start,
|
|
151
|
+
},
|
|
152
|
+
startSeconds: first.startSeconds,
|
|
153
|
+
// Two float operations against the ends, not a sum of N durations: adding the durations up
|
|
154
|
+
// accumulates rounding once per chunk, and the run is contiguous so the ends are the truth.
|
|
155
|
+
durationSeconds: last.startSeconds + last.durationSeconds - first.startSeconds,
|
|
156
|
+
byteOffset: first.byteOffset,
|
|
157
|
+
byteLength,
|
|
158
|
+
signals: Object.freeze(signals),
|
|
159
|
+
// The gap BEFORE the whole run survives the merge. It describes what precedes the first
|
|
160
|
+
// chunk, which is still what precedes the merged one.
|
|
161
|
+
precededByGap: first.precededByGap,
|
|
162
|
+
diagnostics: Object.freeze(diagnostics),
|
|
163
|
+
};
|
|
164
|
+
}
|
package/src/constants.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -171,6 +171,7 @@ export {
|
|
|
171
171
|
filterAnnotationsByTime,
|
|
172
172
|
} from './annotations-query.js';
|
|
173
173
|
export { decodeStatusWord, getStatusSignal, readTriggers } from './biosemi.js';
|
|
174
|
+
export { mergeChunks } from './chunks.js';
|
|
174
175
|
export {
|
|
175
176
|
envelopeOfSamples,
|
|
176
177
|
readEnvelope,
|