edfcore 0.2.19 → 0.2.21
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 +22 -0
- package/dist/annotations-query.d.ts.map +1 -1
- package/dist/annotations-query.js +15 -3
- package/dist/annotations-query.js.map +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/header/lookup.d.ts +16 -0
- package/dist/header/lookup.d.ts.map +1 -1
- package/dist/header/lookup.js +25 -1
- package/dist/header/lookup.js.map +1 -1
- package/package.json +1 -1
- package/src/annotations-query.ts +15 -5
- package/src/constants.ts +1 -1
- package/src/header/lookup.ts +26 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,28 @@ 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.21
|
|
10
|
+
|
|
11
|
+
- **Fixed** `matchSignals` and `filterAnnotationsByText` returning roughly half their matches when
|
|
12
|
+
the caller's `RegExp` carried a `g` or `y` flag. `RegExp.prototype.test` starts from `lastIndex`
|
|
13
|
+
and advances it with those flags, so across an array each element's answer depended on what the
|
|
14
|
+
previous one matched: four EEG channels and `/^EEG/g` gave back the first and the third. Even a
|
|
15
|
+
match-everything pattern stopped returning every signal once it carried the flag, and a second
|
|
16
|
+
call with the same regex object gave a different answer than the first. A `g` flag on a
|
|
17
|
+
membership test means nothing, so both now test against a clone with `lastIndex` reset — cloned
|
|
18
|
+
rather than reset in place, so a shared module-level regex is never mutated.
|
|
19
|
+
|
|
20
|
+
## 0.2.20
|
|
21
|
+
|
|
22
|
+
- **Fixed** `filterAnnotationsByTime` dropping an annotation whose duration was written as an
|
|
23
|
+
explicit `0`. A TAL spells an instant either by omitting the duration field or by writing `0`;
|
|
24
|
+
the two are the same instant, and the docs say edfcore does not distinguish them. The left-edge
|
|
25
|
+
clause keyed on `durationTicks === undefined` — a fact about the writer, not the event — so an
|
|
26
|
+
explicitly-zero marker fell out of the window starting at its own onset AND out of the window
|
|
27
|
+
before it, belonging to no window at all in an adjacent-window partition. It also disagreed with
|
|
28
|
+
`annotationsAt`, which had always treated the two spellings alike. The clause now tests the
|
|
29
|
+
event's actual duration; positive-duration events and the half-open rule are untouched.
|
|
30
|
+
|
|
9
31
|
## 0.2.19
|
|
10
32
|
|
|
11
33
|
- **Fixed** `mergeChunks` merging across a real gap whenever the index had not been scanned — the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"annotations-query.d.ts","sourceRoot":"","sources":["../src/annotations-query.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;
|
|
1
|
+
{"version":3,"file":"annotations-query.d.ts","sourceRoot":"","sources":["../src/annotations-query.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAErE;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,SAAS,aAAa,EAAE,EACrC,MAAM,EAAE,mBAAmB,GAC1B,SAAS,aAAa,EAAE,CAsB1B;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,SAAS,aAAa,EAAE,EACrC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,GACnD,SAAS,aAAa,EAAE,CAU1B;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,SAAS,aAAa,EAAE,GACpC,aAAa,CAAC;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAQlE;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,WAAW,EAAE,SAAS,aAAa,EAAE,EACrC,OAAO,EAAE,MAAM,GACd,SAAS,aAAa,EAAE,CAS1B"}
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* one puts events in the neighbouring window on exactly the files that bother to state their
|
|
19
19
|
* offset.
|
|
20
20
|
*/
|
|
21
|
+
import { matchesText } from './header/lookup.js';
|
|
21
22
|
import { secondsToTicks } from './tal/ticks.js';
|
|
22
23
|
/**
|
|
23
24
|
* The annotations that overlap a time window, in the recording's own timebase.
|
|
@@ -35,8 +36,17 @@ export function filterAnnotationsByTime(annotations, window) {
|
|
|
35
36
|
return Object.freeze(annotations.filter((annotation) => {
|
|
36
37
|
const onset = annotation.onsetTicksFromFirstRecord;
|
|
37
38
|
const end = onset + (annotation.durationTicks ?? 0n);
|
|
38
|
-
// Half-open on both sides: [onset, end) against [from, to).
|
|
39
|
-
|
|
39
|
+
// Half-open on both sides: [onset, end) against [from, to). An instantaneous event has an
|
|
40
|
+
// empty interval, so `end > from` can never hold for one and it needs the second clause.
|
|
41
|
+
//
|
|
42
|
+
// That clause tests `end === onset` — the event's actual duration — and NOT
|
|
43
|
+
// `durationTicks === undefined`, which is a fact about the WRITER rather than the event. A
|
|
44
|
+
// TAL may spell an instant either by omitting the duration field or by writing `0`, the two
|
|
45
|
+
// are the same instant, and `annotations.md` says edfcore does not distinguish them. Keying
|
|
46
|
+
// on the spelling dropped every explicitly-zero event from the window starting at its own
|
|
47
|
+
// onset — and from the previous window too, so in an adjacent-window partition it belonged
|
|
48
|
+
// to no window at all (fixed in 0.2.20).
|
|
49
|
+
return onset < to && (end > from || (end === onset && onset >= from));
|
|
40
50
|
}));
|
|
41
51
|
}
|
|
42
52
|
/**
|
|
@@ -51,7 +61,9 @@ export function filterAnnotationsByText(annotations, match) {
|
|
|
51
61
|
const test = typeof match === 'string'
|
|
52
62
|
? (text) => text === match
|
|
53
63
|
: match instanceof RegExp
|
|
54
|
-
?
|
|
64
|
+
? // Not `match.test` directly: a `g` or `y` flag makes `test` stateful across the array
|
|
65
|
+
// and silently returns about half the true matches. See `matchesText`.
|
|
66
|
+
matchesText(match)
|
|
55
67
|
: match;
|
|
56
68
|
return Object.freeze(annotations.filter((annotation) => test(annotation.text)));
|
|
57
69
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"annotations-query.js","sourceRoot":"","sources":["../src/annotations-query.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGhD;;;;;;;GAOG;AACH,MAAM,UAAU,uBAAuB,CACrC,WAAqC,EACrC,MAA2B;IAE3B,MAAM,IAAI,GAAG,cAAc,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IACjD,MAAM,EAAE,GAAG,IAAI,GAAG,cAAc,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;IACzD,IAAI,EAAE,IAAI,IAAI;QAAE,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAEzC,OAAO,MAAM,CAAC,MAAM,CAClB,WAAW,CAAC,MAAM,CAAC,CAAC,UAAU,EAAE,EAAE;QAChC,MAAM,KAAK,GAAG,UAAU,CAAC,yBAAyB,CAAC;QACnD,MAAM,GAAG,GAAG,KAAK,GAAG,CAAC,UAAU,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC;QACrD,
|
|
1
|
+
{"version":3,"file":"annotations-query.js","sourceRoot":"","sources":["../src/annotations-query.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.21";
|
|
113
113
|
//# sourceMappingURL=constants.d.ts.map
|
package/dist/constants.js
CHANGED
package/dist/header/lookup.d.ts
CHANGED
|
@@ -30,6 +30,22 @@ export declare function findSignals(header: EdfHeader, label: string): readonly
|
|
|
30
30
|
* edfcore could return that would not be a guess.
|
|
31
31
|
*/
|
|
32
32
|
export declare function getSignal(header: EdfHeader, selector: number | string): EdfSignal;
|
|
33
|
+
/**
|
|
34
|
+
* A membership test over a caller's RegExp that cannot be poisoned by its own flags.
|
|
35
|
+
*
|
|
36
|
+
* `RegExp.prototype.test` is STATEFUL when the pattern carries `g` or `y`: it starts from
|
|
37
|
+
* `lastIndex` and advances it on every match. Used across an array — which is what every filter
|
|
38
|
+
* here does — that makes the result depend on what the previous element matched, so `/EEG/g` over
|
|
39
|
+
* four EEG channels returns the first and third and silently drops the other two. The caller sees
|
|
40
|
+
* half a montage with no error, and even a match-everything pattern stops returning every signal
|
|
41
|
+
* once it carries the flag.
|
|
42
|
+
*
|
|
43
|
+
* A `g` flag on a membership test means nothing, so honouring its statefulness serves no one. The
|
|
44
|
+
* regex is CLONED rather than reset in place: resetting the caller's object would mutate an
|
|
45
|
+
* argument, and a module-level `const PATTERN = /x/g` shared with a `String.replace` elsewhere
|
|
46
|
+
* would then behave differently depending on whether edfcore had been called first.
|
|
47
|
+
*/
|
|
48
|
+
export declare function matchesText(match: RegExp): (text: string) => boolean;
|
|
33
49
|
/**
|
|
34
50
|
* Every data signal whose label matches a pattern.
|
|
35
51
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lookup.d.ts","sourceRoot":"","sources":["../../src/header/lookup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAGxD;AAED,8EAA8E;AAC9E,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,SAAS,EAAE,CAGlF;AAMD;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAgCjF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,MAAM,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,GAC3C,SAAS,SAAS,EAAE,CAKtB;AAED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAEjE"}
|
|
1
|
+
{"version":3,"file":"lookup.d.ts","sourceRoot":"","sources":["../../src/header/lookup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAGxD;AAED,8EAA8E;AAC9E,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,SAAS,EAAE,CAGlF;AAMD;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAgCjF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAQpE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,MAAM,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,GAC3C,SAAS,SAAS,EAAE,CAKtB;AAED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAEjE"}
|
package/dist/header/lookup.js
CHANGED
|
@@ -64,6 +64,30 @@ export function getSignal(header, selector) {
|
|
|
64
64
|
'first is how the wrong channel ends up in a paper. Next: call findSignals() to get them ' +
|
|
65
65
|
'all, or select by index.', { label: trimEdfField(selector), matchingIndices });
|
|
66
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* A membership test over a caller's RegExp that cannot be poisoned by its own flags.
|
|
69
|
+
*
|
|
70
|
+
* `RegExp.prototype.test` is STATEFUL when the pattern carries `g` or `y`: it starts from
|
|
71
|
+
* `lastIndex` and advances it on every match. Used across an array — which is what every filter
|
|
72
|
+
* here does — that makes the result depend on what the previous element matched, so `/EEG/g` over
|
|
73
|
+
* four EEG channels returns the first and third and silently drops the other two. The caller sees
|
|
74
|
+
* half a montage with no error, and even a match-everything pattern stops returning every signal
|
|
75
|
+
* once it carries the flag.
|
|
76
|
+
*
|
|
77
|
+
* A `g` flag on a membership test means nothing, so honouring its statefulness serves no one. The
|
|
78
|
+
* regex is CLONED rather than reset in place: resetting the caller's object would mutate an
|
|
79
|
+
* argument, and a module-level `const PATTERN = /x/g` shared with a `String.replace` elsewhere
|
|
80
|
+
* would then behave differently depending on whether edfcore had been called first.
|
|
81
|
+
*/
|
|
82
|
+
export function matchesText(match) {
|
|
83
|
+
const pattern = new RegExp(match.source, match.flags);
|
|
84
|
+
return (text) => {
|
|
85
|
+
// `y` anchors at `lastIndex`, so this also makes a sticky pattern test from the start of each
|
|
86
|
+
// string rather than from wherever the previous element left off.
|
|
87
|
+
pattern.lastIndex = 0;
|
|
88
|
+
return pattern.test(text);
|
|
89
|
+
};
|
|
90
|
+
}
|
|
67
91
|
/**
|
|
68
92
|
* Every data signal whose label matches a pattern.
|
|
69
93
|
*
|
|
@@ -77,7 +101,7 @@ export function getSignal(header, selector) {
|
|
|
77
101
|
* exact-match case `findSignals` already covers.
|
|
78
102
|
*/
|
|
79
103
|
export function matchSignals(header, match) {
|
|
80
|
-
const test = match instanceof RegExp ? (
|
|
104
|
+
const test = match instanceof RegExp ? matchesText(match) : match;
|
|
81
105
|
return Object.freeze(header.signals.filter((signal) => signal.kind === 'data' && test(signal.label)));
|
|
82
106
|
}
|
|
83
107
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lookup.js","sourceRoot":"","sources":["../../src/header/lookup.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"lookup.js","sourceRoot":"","sources":["../../src/header/lookup.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/annotations-query.ts
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* offset.
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
+
import { matchesText } from './header/lookup.js';
|
|
22
23
|
import { secondsToTicks } from './tal/ticks.js';
|
|
23
24
|
import type { EdfAnnotation, EdfAnnotationWindow } from './types.js';
|
|
24
25
|
|
|
@@ -42,10 +43,17 @@ export function filterAnnotationsByTime(
|
|
|
42
43
|
annotations.filter((annotation) => {
|
|
43
44
|
const onset = annotation.onsetTicksFromFirstRecord;
|
|
44
45
|
const end = onset + (annotation.durationTicks ?? 0n);
|
|
45
|
-
// Half-open on both sides: [onset, end) against [from, to).
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
// Half-open on both sides: [onset, end) against [from, to). An instantaneous event has an
|
|
47
|
+
// empty interval, so `end > from` can never hold for one and it needs the second clause.
|
|
48
|
+
//
|
|
49
|
+
// That clause tests `end === onset` — the event's actual duration — and NOT
|
|
50
|
+
// `durationTicks === undefined`, which is a fact about the WRITER rather than the event. A
|
|
51
|
+
// TAL may spell an instant either by omitting the duration field or by writing `0`, the two
|
|
52
|
+
// are the same instant, and `annotations.md` says edfcore does not distinguish them. Keying
|
|
53
|
+
// on the spelling dropped every explicitly-zero event from the window starting at its own
|
|
54
|
+
// onset — and from the previous window too, so in an adjacent-window partition it belonged
|
|
55
|
+
// to no window at all (fixed in 0.2.20).
|
|
56
|
+
return onset < to && (end > from || (end === onset && onset >= from));
|
|
49
57
|
}),
|
|
50
58
|
);
|
|
51
59
|
}
|
|
@@ -66,7 +74,9 @@ export function filterAnnotationsByText(
|
|
|
66
74
|
typeof match === 'string'
|
|
67
75
|
? (text: string): boolean => text === match
|
|
68
76
|
: match instanceof RegExp
|
|
69
|
-
?
|
|
77
|
+
? // Not `match.test` directly: a `g` or `y` flag makes `test` stateful across the array
|
|
78
|
+
// and silently returns about half the true matches. See `matchesText`.
|
|
79
|
+
matchesText(match)
|
|
70
80
|
: match;
|
|
71
81
|
return Object.freeze(annotations.filter((annotation) => test(annotation.text)));
|
|
72
82
|
}
|
package/src/constants.ts
CHANGED
package/src/header/lookup.ts
CHANGED
|
@@ -80,6 +80,31 @@ export function getSignal(header: EdfHeader, selector: number | string): EdfSign
|
|
|
80
80
|
);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* A membership test over a caller's RegExp that cannot be poisoned by its own flags.
|
|
85
|
+
*
|
|
86
|
+
* `RegExp.prototype.test` is STATEFUL when the pattern carries `g` or `y`: it starts from
|
|
87
|
+
* `lastIndex` and advances it on every match. Used across an array — which is what every filter
|
|
88
|
+
* here does — that makes the result depend on what the previous element matched, so `/EEG/g` over
|
|
89
|
+
* four EEG channels returns the first and third and silently drops the other two. The caller sees
|
|
90
|
+
* half a montage with no error, and even a match-everything pattern stops returning every signal
|
|
91
|
+
* once it carries the flag.
|
|
92
|
+
*
|
|
93
|
+
* A `g` flag on a membership test means nothing, so honouring its statefulness serves no one. The
|
|
94
|
+
* regex is CLONED rather than reset in place: resetting the caller's object would mutate an
|
|
95
|
+
* argument, and a module-level `const PATTERN = /x/g` shared with a `String.replace` elsewhere
|
|
96
|
+
* would then behave differently depending on whether edfcore had been called first.
|
|
97
|
+
*/
|
|
98
|
+
export function matchesText(match: RegExp): (text: string) => boolean {
|
|
99
|
+
const pattern = new RegExp(match.source, match.flags);
|
|
100
|
+
return (text: string): boolean => {
|
|
101
|
+
// `y` anchors at `lastIndex`, so this also makes a sticky pattern test from the start of each
|
|
102
|
+
// string rather than from wherever the previous element left off.
|
|
103
|
+
pattern.lastIndex = 0;
|
|
104
|
+
return pattern.test(text);
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
83
108
|
/**
|
|
84
109
|
* Every data signal whose label matches a pattern.
|
|
85
110
|
*
|
|
@@ -96,7 +121,7 @@ export function matchSignals(
|
|
|
96
121
|
header: EdfHeader,
|
|
97
122
|
match: RegExp | ((label: string) => boolean),
|
|
98
123
|
): readonly EdfSignal[] {
|
|
99
|
-
const test = match instanceof RegExp ? (
|
|
124
|
+
const test = match instanceof RegExp ? matchesText(match) : match;
|
|
100
125
|
return Object.freeze(
|
|
101
126
|
header.signals.filter((signal) => signal.kind === 'data' && test(signal.label)),
|
|
102
127
|
);
|