edf2csv 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 +123 -0
- package/dist/cli/report.d.ts +17 -0
- package/dist/cli/report.d.ts.map +1 -0
- package/dist/cli/report.js +107 -0
- package/dist/cli/report.js.map +1 -0
- package/dist/cli.d.ts +13 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +315 -0
- package/dist/cli.js.map +1 -0
- package/dist/convert/channels.d.ts +40 -0
- package/dist/convert/channels.d.ts.map +1 -0
- package/dist/convert/channels.js +134 -0
- package/dist/convert/channels.js.map +1 -0
- package/dist/convert/plan.d.ts +70 -0
- package/dist/convert/plan.d.ts.map +1 -0
- package/dist/convert/plan.js +127 -0
- package/dist/convert/plan.js.map +1 -0
- package/dist/convert/run.d.ts +48 -0
- package/dist/convert/run.d.ts.map +1 -0
- package/dist/convert/run.js +358 -0
- package/dist/convert/run.js.map +1 -0
- package/dist/convert/time-range.d.ts +63 -0
- package/dist/convert/time-range.d.ts.map +1 -0
- package/dist/convert/time-range.js +188 -0
- package/dist/convert/time-range.js.map +1 -0
- package/dist/convert/timing.d.ts +18 -0
- package/dist/convert/timing.d.ts.map +1 -0
- package/dist/convert/timing.js +69 -0
- package/dist/convert/timing.js.map +1 -0
- package/dist/edf/annotations.d.ts +43 -0
- package/dist/edf/annotations.d.ts.map +1 -0
- package/dist/edf/annotations.js +88 -0
- package/dist/edf/annotations.js.map +1 -0
- package/dist/edf/errors.d.ts +27 -0
- package/dist/edf/errors.d.ts.map +1 -0
- package/dist/edf/errors.js +23 -0
- package/dist/edf/errors.js.map +1 -0
- package/dist/edf/header.d.ts +115 -0
- package/dist/edf/header.d.ts.map +1 -0
- package/dist/edf/header.js +383 -0
- package/dist/edf/header.js.map +1 -0
- package/dist/edf/reader.d.ts +75 -0
- package/dist/edf/reader.d.ts.map +1 -0
- package/dist/edf/reader.js +224 -0
- package/dist/edf/reader.js.map +1 -0
- package/dist/edf/scale.d.ts +46 -0
- package/dist/edf/scale.d.ts.map +1 -0
- package/dist/edf/scale.js +76 -0
- package/dist/edf/scale.js.map +1 -0
- package/dist/format/csv.d.ts +34 -0
- package/dist/format/csv.d.ts.map +1 -0
- package/dist/format/csv.js +136 -0
- package/dist/format/csv.js.map +1 -0
- package/dist/format/number.d.ts +42 -0
- package/dist/format/number.d.ts.map +1 -0
- package/dist/format/number.js +108 -0
- package/dist/format/number.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +8 -0
- package/dist/version.js.map +1 -0
- package/package.json +73 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Channel naming and selection.
|
|
3
|
+
*
|
|
4
|
+
* Two real-world facts drive this module. EDF labels are free text that routinely
|
|
5
|
+
* contain spaces and punctuation ("EEG Fpz-Cz"), and they are not guaranteed to be
|
|
6
|
+
* unique — CHB-MIT recordings ship two channels both labelled "T8-P8", and some
|
|
7
|
+
* carry a channel labelled "-". Labels are therefore preserved verbatim in output,
|
|
8
|
+
* and only disambiguated when the file itself is ambiguous.
|
|
9
|
+
*/
|
|
10
|
+
import type { EdfSignal } from '../edf/header.js';
|
|
11
|
+
export declare class ChannelSelectionError extends Error {
|
|
12
|
+
constructor(message: string);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Column name for a signal.
|
|
16
|
+
*
|
|
17
|
+
* Names are derived from the whole file, not from the current selection, so a given
|
|
18
|
+
* channel always produces the same column regardless of which channels were asked
|
|
19
|
+
* for. Duplicated labels get a `_ch<index>` suffix pointing at their position in
|
|
20
|
+
* the file, which is the only thing that reliably tells them apart.
|
|
21
|
+
*/
|
|
22
|
+
export declare function buildColumnNames(signals: readonly EdfSignal[]): Map<number, string>;
|
|
23
|
+
export interface ChannelSelection {
|
|
24
|
+
signals: EdfSignal[];
|
|
25
|
+
/** Labels that matched more than one channel, so the user knows why they got extras. */
|
|
26
|
+
ambiguous: {
|
|
27
|
+
term: string;
|
|
28
|
+
matched: EdfSignal[];
|
|
29
|
+
}[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Resolve a `--channels` specification against the file's signals.
|
|
33
|
+
*
|
|
34
|
+
* Matching is case-insensitive on the exact label, with `#<index>` available to
|
|
35
|
+
* address a specific channel when labels collide. A term that matches nothing is an
|
|
36
|
+
* error rather than a silent omission: quietly dropping a requested channel would
|
|
37
|
+
* hand the user a CSV that is missing data they explicitly asked for.
|
|
38
|
+
*/
|
|
39
|
+
export declare function selectChannels(signals: readonly EdfSignal[], terms: readonly string[]): ChannelSelection;
|
|
40
|
+
//# sourceMappingURL=channels.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channels.d.ts","sourceRoot":"","sources":["../../src/convert/channels.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElD,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAgBnF;AAMD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,wFAAwF;IACxF,SAAS,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,SAAS,EAAE,CAAA;KAAE,EAAE,CAAC;CACrD;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,gBAAgB,CAiDxG"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Channel naming and selection.
|
|
3
|
+
*
|
|
4
|
+
* Two real-world facts drive this module. EDF labels are free text that routinely
|
|
5
|
+
* contain spaces and punctuation ("EEG Fpz-Cz"), and they are not guaranteed to be
|
|
6
|
+
* unique — CHB-MIT recordings ship two channels both labelled "T8-P8", and some
|
|
7
|
+
* carry a channel labelled "-". Labels are therefore preserved verbatim in output,
|
|
8
|
+
* and only disambiguated when the file itself is ambiguous.
|
|
9
|
+
*/
|
|
10
|
+
export class ChannelSelectionError extends Error {
|
|
11
|
+
constructor(message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = 'ChannelSelectionError';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Column name for a signal.
|
|
18
|
+
*
|
|
19
|
+
* Names are derived from the whole file, not from the current selection, so a given
|
|
20
|
+
* channel always produces the same column regardless of which channels were asked
|
|
21
|
+
* for. Duplicated labels get a `_ch<index>` suffix pointing at their position in
|
|
22
|
+
* the file, which is the only thing that reliably tells them apart.
|
|
23
|
+
*/
|
|
24
|
+
export function buildColumnNames(signals) {
|
|
25
|
+
const counts = new Map();
|
|
26
|
+
for (const signal of signals) {
|
|
27
|
+
if (signal.isAnnotations)
|
|
28
|
+
continue;
|
|
29
|
+
const base = baseName(signal);
|
|
30
|
+
counts.set(base, (counts.get(base) ?? 0) + 1);
|
|
31
|
+
}
|
|
32
|
+
const names = new Map();
|
|
33
|
+
for (const signal of signals) {
|
|
34
|
+
if (signal.isAnnotations)
|
|
35
|
+
continue;
|
|
36
|
+
const base = baseName(signal);
|
|
37
|
+
const duplicated = (counts.get(base) ?? 0) > 1;
|
|
38
|
+
names.set(signal.index, duplicated ? `${base}_ch${signal.index}` : base);
|
|
39
|
+
}
|
|
40
|
+
return names;
|
|
41
|
+
}
|
|
42
|
+
function baseName(signal) {
|
|
43
|
+
return signal.label === '' ? `signal_${signal.index}` : signal.label;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Resolve a `--channels` specification against the file's signals.
|
|
47
|
+
*
|
|
48
|
+
* Matching is case-insensitive on the exact label, with `#<index>` available to
|
|
49
|
+
* address a specific channel when labels collide. A term that matches nothing is an
|
|
50
|
+
* error rather than a silent omission: quietly dropping a requested channel would
|
|
51
|
+
* hand the user a CSV that is missing data they explicitly asked for.
|
|
52
|
+
*/
|
|
53
|
+
export function selectChannels(signals, terms) {
|
|
54
|
+
const candidates = signals.filter((s) => !s.isAnnotations);
|
|
55
|
+
const byLabel = new Map();
|
|
56
|
+
for (const signal of candidates) {
|
|
57
|
+
const key = signal.label.toLowerCase();
|
|
58
|
+
const bucket = byLabel.get(key);
|
|
59
|
+
if (bucket)
|
|
60
|
+
bucket.push(signal);
|
|
61
|
+
else
|
|
62
|
+
byLabel.set(key, [signal]);
|
|
63
|
+
}
|
|
64
|
+
const chosen = new Map();
|
|
65
|
+
const ambiguous = [];
|
|
66
|
+
for (const rawTerm of terms) {
|
|
67
|
+
const term = rawTerm.trim();
|
|
68
|
+
if (term === '')
|
|
69
|
+
continue;
|
|
70
|
+
// '#N' addresses a channel by position, but a label may literally be "#5".
|
|
71
|
+
// A real label always wins, so no channel becomes unreachable.
|
|
72
|
+
const literal = byLabel.get(term.toLowerCase());
|
|
73
|
+
if (term.startsWith('#') && (!literal || literal.length === 0)) {
|
|
74
|
+
const index = Number(term.slice(1));
|
|
75
|
+
const signal = candidates.find((s) => s.index === index);
|
|
76
|
+
if (!signal) {
|
|
77
|
+
throw new ChannelSelectionError(`No channel at position ${term}. This file has signal channels at ` +
|
|
78
|
+
`#${candidates.map((s) => s.index).join(', #')}.`);
|
|
79
|
+
}
|
|
80
|
+
chosen.set(signal.index, signal);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const matched = byLabel.get(term.toLowerCase());
|
|
84
|
+
if (!matched || matched.length === 0) {
|
|
85
|
+
throw new ChannelSelectionError(`No channel named "${term}".${suggest(term, candidates)}\n` +
|
|
86
|
+
`Run with --info to list the channels in this file.`);
|
|
87
|
+
}
|
|
88
|
+
if (matched.length > 1)
|
|
89
|
+
ambiguous.push({ term, matched: [...matched] });
|
|
90
|
+
for (const signal of matched)
|
|
91
|
+
chosen.set(signal.index, signal);
|
|
92
|
+
}
|
|
93
|
+
const selected = [...chosen.values()].sort((a, b) => a.index - b.index);
|
|
94
|
+
if (selected.length === 0) {
|
|
95
|
+
throw new ChannelSelectionError('No channels were selected.');
|
|
96
|
+
}
|
|
97
|
+
return { signals: selected, ambiguous };
|
|
98
|
+
}
|
|
99
|
+
function suggest(term, candidates) {
|
|
100
|
+
// Duplicated labels would otherwise be suggested twice, which reads like two
|
|
101
|
+
// different options while naming the same thing.
|
|
102
|
+
const unique = [...new Set(candidates.map((s) => s.label))];
|
|
103
|
+
const scored = unique
|
|
104
|
+
.map((label) => ({ label, distance: editDistance(term.toLowerCase(), label.toLowerCase()) }))
|
|
105
|
+
.filter((c) => c.label !== '' && c.distance <= Math.max(2, Math.floor(term.length / 3)))
|
|
106
|
+
.sort((a, b) => a.distance - b.distance)
|
|
107
|
+
.slice(0, 3);
|
|
108
|
+
if (scored.length === 0)
|
|
109
|
+
return '';
|
|
110
|
+
return ` Did you mean ${scored.map((c) => `"${c.label}"`).join(', ')}?`;
|
|
111
|
+
}
|
|
112
|
+
/** Levenshtein distance, two-row variant. */
|
|
113
|
+
function editDistance(a, b) {
|
|
114
|
+
if (a === b)
|
|
115
|
+
return 0;
|
|
116
|
+
if (a.length === 0)
|
|
117
|
+
return b.length;
|
|
118
|
+
if (b.length === 0)
|
|
119
|
+
return a.length;
|
|
120
|
+
let previous = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
121
|
+
let current = new Array(b.length + 1).fill(0);
|
|
122
|
+
for (let i = 1; i <= a.length; i++) {
|
|
123
|
+
current[0] = i;
|
|
124
|
+
for (let j = 1; j <= b.length; j++) {
|
|
125
|
+
const cost = a.charCodeAt(i - 1) === b.charCodeAt(j - 1) ? 0 : 1;
|
|
126
|
+
current[j] = Math.min((current[j - 1] ?? 0) + 1, (previous[j] ?? 0) + 1, (previous[j - 1] ?? 0) + cost);
|
|
127
|
+
}
|
|
128
|
+
const swap = previous;
|
|
129
|
+
previous = current;
|
|
130
|
+
current = swap;
|
|
131
|
+
}
|
|
132
|
+
return previous[b.length] ?? 0;
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=channels.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channels.js","sourceRoot":"","sources":["../../src/convert/channels.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turning a request into a concrete conversion plan.
|
|
3
|
+
*
|
|
4
|
+
* The plan is where the tool's central promise is enforced: channels recorded at
|
|
5
|
+
* different sampling rates are never merged into one table. A single wide CSV can
|
|
6
|
+
* only hold mixed rates by inventing samples for the slow channels — MNE, for
|
|
7
|
+
* instance, expands three genuine 1 Hz temperature readings into 768 interpolated
|
|
8
|
+
* values without warning. Instead each distinct rate gets its own file, so every
|
|
9
|
+
* number in every output file is a number that was actually recorded.
|
|
10
|
+
*/
|
|
11
|
+
import type { Diagnostic } from '../edf/errors.js';
|
|
12
|
+
import type { EdfSignal } from '../edf/header.js';
|
|
13
|
+
import type { ResolvedRange } from './time-range.js';
|
|
14
|
+
export interface PlannedChannel {
|
|
15
|
+
signal: EdfSignal;
|
|
16
|
+
column: string;
|
|
17
|
+
decimals: number;
|
|
18
|
+
}
|
|
19
|
+
export interface RateGroup {
|
|
20
|
+
/** Sampling rate in Hz shared by every channel in this group. */
|
|
21
|
+
rate: number;
|
|
22
|
+
samplesPerRecord: number;
|
|
23
|
+
fileName: string;
|
|
24
|
+
timeDecimals: number;
|
|
25
|
+
channels: PlannedChannel[];
|
|
26
|
+
}
|
|
27
|
+
export interface PlanInput {
|
|
28
|
+
signals: readonly EdfSignal[];
|
|
29
|
+
recordDuration: number;
|
|
30
|
+
recordCount: number;
|
|
31
|
+
hasAnnotationChannel: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* True start time of each data record, supplied for discontinuous files. The
|
|
34
|
+
* requested time window is resolved against these rather than against
|
|
35
|
+
* `recordCount * recordDuration`, which for a file with gaps is the amount of
|
|
36
|
+
* data rather than the span of time it covers.
|
|
37
|
+
*/
|
|
38
|
+
recordStarts?: Float64Array | null | undefined;
|
|
39
|
+
}
|
|
40
|
+
export interface PlanOptions {
|
|
41
|
+
channels?: readonly string[] | undefined;
|
|
42
|
+
start?: number | undefined;
|
|
43
|
+
duration?: number | undefined;
|
|
44
|
+
end?: number | undefined;
|
|
45
|
+
annotationsOnly?: boolean | undefined;
|
|
46
|
+
/** Force a fixed number of decimals instead of deriving it per channel. */
|
|
47
|
+
decimals?: number | undefined;
|
|
48
|
+
}
|
|
49
|
+
export interface ConversionPlan {
|
|
50
|
+
groups: RateGroup[];
|
|
51
|
+
range: ResolvedRange;
|
|
52
|
+
columnNames: Map<number, string>;
|
|
53
|
+
writeSignals: boolean;
|
|
54
|
+
diagnostics: Diagnostic[];
|
|
55
|
+
estimate: OutputEstimate;
|
|
56
|
+
}
|
|
57
|
+
export interface OutputEstimate {
|
|
58
|
+
/** Total data rows across every signal file. */
|
|
59
|
+
rows: number;
|
|
60
|
+
/** Approximate size of the signal CSVs on disk. */
|
|
61
|
+
bytes: number;
|
|
62
|
+
/** True when any single file would exceed Excel's row limit. */
|
|
63
|
+
exceedsSpreadsheetLimit: boolean;
|
|
64
|
+
}
|
|
65
|
+
/** Excel and most spreadsheet tools stop at 1,048,576 rows including the header. */
|
|
66
|
+
export declare const SPREADSHEET_ROW_LIMIT = 1048576;
|
|
67
|
+
export declare function buildPlan(input: PlanInput, options?: PlanOptions): ConversionPlan;
|
|
68
|
+
/** `256hz`, `12_5hz` — safe in a filename on every platform. */
|
|
69
|
+
export declare function rateSlug(rate: number): string;
|
|
70
|
+
//# sourceMappingURL=plan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../src/convert/plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAMlD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAErD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,SAAS,SAAS,EAAE,CAAC;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,oBAAoB,EAAE,OAAO,CAAC;IAC9B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,YAAY,GAAG,IAAI,GAAG,SAAS,CAAC;CAChD;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACzC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,eAAe,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACtC,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/B;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,KAAK,EAAE,aAAa,CAAC;IACrB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,QAAQ,EAAE,cAAc,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,uBAAuB,EAAE,OAAO,CAAC;CAClC;AAED,oFAAoF;AACpF,eAAO,MAAM,qBAAqB,UAAY,CAAC;AAE/C,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,GAAE,WAAgB,GAAG,cAAc,CAmDrF;AA4CD,gEAAgE;AAChE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7C"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turning a request into a concrete conversion plan.
|
|
3
|
+
*
|
|
4
|
+
* The plan is where the tool's central promise is enforced: channels recorded at
|
|
5
|
+
* different sampling rates are never merged into one table. A single wide CSV can
|
|
6
|
+
* only hold mixed rates by inventing samples for the slow channels — MNE, for
|
|
7
|
+
* instance, expands three genuine 1 Hz temperature readings into 768 interpolated
|
|
8
|
+
* values without warning. Instead each distinct rate gets its own file, so every
|
|
9
|
+
* number in every output file is a number that was actually recorded.
|
|
10
|
+
*/
|
|
11
|
+
import { formatRate } from '../edf/header.js';
|
|
12
|
+
import { decimalsForSignal } from '../edf/scale.js';
|
|
13
|
+
import { timeDecimals } from '../format/number.js';
|
|
14
|
+
import { buildColumnNames, selectChannels } from './channels.js';
|
|
15
|
+
import { countSamplesInRange, resolveRange } from './time-range.js';
|
|
16
|
+
/** Excel and most spreadsheet tools stop at 1,048,576 rows including the header. */
|
|
17
|
+
export const SPREADSHEET_ROW_LIMIT = 1_048_576;
|
|
18
|
+
export function buildPlan(input, options = {}) {
|
|
19
|
+
const diagnostics = [];
|
|
20
|
+
const columnNames = buildColumnNames(input.signals);
|
|
21
|
+
const range = resolveRange({
|
|
22
|
+
start: options.start,
|
|
23
|
+
duration: options.duration,
|
|
24
|
+
end: options.end,
|
|
25
|
+
recordDuration: input.recordDuration,
|
|
26
|
+
recordCount: input.recordCount,
|
|
27
|
+
recordStarts: input.recordStarts,
|
|
28
|
+
});
|
|
29
|
+
const writeSignals = options.annotationsOnly !== true;
|
|
30
|
+
let chosen = input.signals.filter((s) => !s.isAnnotations);
|
|
31
|
+
if (writeSignals && options.channels && options.channels.length > 0) {
|
|
32
|
+
const selection = selectChannels(input.signals, options.channels);
|
|
33
|
+
chosen = selection.signals;
|
|
34
|
+
for (const { term, matched } of selection.ambiguous) {
|
|
35
|
+
diagnostics.push({
|
|
36
|
+
code: 'DUPLICATE_LABEL',
|
|
37
|
+
severity: 'warning',
|
|
38
|
+
message: `"${term}" matches ${matched.length} channels (positions ` +
|
|
39
|
+
`${matched.map((s) => `#${s.index}`).join(', ')}); all of them were selected.`,
|
|
40
|
+
hint: `Use --channels "#${matched[0]?.index ?? 0}" to pick just one.`,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const groups = writeSignals ? groupByRate(chosen, columnNames, options.decimals) : [];
|
|
45
|
+
const estimate = estimateOutput(groups, range, input.recordDuration, input.recordStarts);
|
|
46
|
+
if (estimate.exceedsSpreadsheetLimit) {
|
|
47
|
+
diagnostics.push({
|
|
48
|
+
code: 'LARGE_OUTPUT',
|
|
49
|
+
severity: 'warning',
|
|
50
|
+
message: `At least one output file will have more than ${SPREADSHEET_ROW_LIMIT.toLocaleString('en-US')} ` +
|
|
51
|
+
`rows, which is more than Excel or Numbers can open.`,
|
|
52
|
+
hint: 'Use --start and --duration to convert a section, or read the file with pandas or R.',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return { groups, range, columnNames, writeSignals, diagnostics, estimate };
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Partition channels by sampling rate, largest first.
|
|
59
|
+
*
|
|
60
|
+
* The common case — every channel at one rate — collapses to a single group and a
|
|
61
|
+
* single `signals.csv`, so the honest behaviour costs nothing when there is nothing
|
|
62
|
+
* to be honest about.
|
|
63
|
+
*/
|
|
64
|
+
function groupByRate(signals, columnNames, forcedDecimals) {
|
|
65
|
+
const byRate = new Map();
|
|
66
|
+
for (const signal of signals) {
|
|
67
|
+
// A channel with no samples has no sampling rate to group by, and would
|
|
68
|
+
// otherwise produce an empty "0hz" file. The header parser already warned.
|
|
69
|
+
if (signal.samplesPerRecord === 0)
|
|
70
|
+
continue;
|
|
71
|
+
const bucket = byRate.get(signal.samplingRate);
|
|
72
|
+
if (bucket)
|
|
73
|
+
bucket.push(signal);
|
|
74
|
+
else
|
|
75
|
+
byRate.set(signal.samplingRate, [signal]);
|
|
76
|
+
}
|
|
77
|
+
const rates = [...byRate.keys()].sort((a, b) => b - a);
|
|
78
|
+
const single = rates.length === 1;
|
|
79
|
+
return rates.map((rate) => {
|
|
80
|
+
const members = byRate.get(rate) ?? [];
|
|
81
|
+
const first = members[0];
|
|
82
|
+
return {
|
|
83
|
+
rate,
|
|
84
|
+
samplesPerRecord: first ? first.samplesPerRecord : 0,
|
|
85
|
+
fileName: single ? 'signals.csv' : `signals_${rateSlug(rate)}.csv`,
|
|
86
|
+
timeDecimals: timeDecimals(rate),
|
|
87
|
+
channels: members.map((signal) => ({
|
|
88
|
+
signal,
|
|
89
|
+
column: columnNames.get(signal.index) ?? `signal_${signal.index}`,
|
|
90
|
+
decimals: forcedDecimals ?? decimalsForSignal(signal),
|
|
91
|
+
})),
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/** `256hz`, `12_5hz` — safe in a filename on every platform. */
|
|
96
|
+
export function rateSlug(rate) {
|
|
97
|
+
return `${formatRate(rate).replace('.', '_')}hz`;
|
|
98
|
+
}
|
|
99
|
+
function estimateOutput(groups, range, recordDuration, recordStarts) {
|
|
100
|
+
let rows = 0;
|
|
101
|
+
let bytes = 0;
|
|
102
|
+
let exceeds = false;
|
|
103
|
+
for (const group of groups) {
|
|
104
|
+
let groupRows = 0;
|
|
105
|
+
for (let record = range.startRecord; record < range.endRecord; record++) {
|
|
106
|
+
const recordStart = recordStarts
|
|
107
|
+
? (recordStarts[record] ?? record * recordDuration)
|
|
108
|
+
: record * recordDuration;
|
|
109
|
+
groupRows += countSamplesInRange({
|
|
110
|
+
recordStart,
|
|
111
|
+
rate: group.rate,
|
|
112
|
+
samplesPerRecord: group.samplesPerRecord,
|
|
113
|
+
startSeconds: range.startSeconds,
|
|
114
|
+
endSeconds: range.endSeconds,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
rows += groupRows;
|
|
118
|
+
if (groupRows + 1 > SPREADSHEET_ROW_LIMIT)
|
|
119
|
+
exceeds = true;
|
|
120
|
+
// A cell is roughly the sign, a few integer digits, the point and its decimals.
|
|
121
|
+
const timeWidth = group.timeDecimals + 6;
|
|
122
|
+
const cellWidth = group.channels.reduce((sum, c) => sum + c.decimals + 6, 0);
|
|
123
|
+
bytes += groupRows * (timeWidth + cellWidth + 1);
|
|
124
|
+
}
|
|
125
|
+
return { rows, bytes, exceedsSpreadsheetLimit: exceeds };
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=plan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan.js","sourceRoot":"","sources":["../../src/convert/plan.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Executing a conversion.
|
|
3
|
+
*
|
|
4
|
+
* Everything is written in a single pass over the data records. All rate groups are
|
|
5
|
+
* open at once and fed from the same batch of bytes, so a file is read once no
|
|
6
|
+
* matter how many output tables it produces, and memory stays flat.
|
|
7
|
+
*/
|
|
8
|
+
import { EdfFile } from '../edf/reader.js';
|
|
9
|
+
import type { Diagnostic } from '../edf/errors.js';
|
|
10
|
+
import type { ConversionPlan, PlanOptions } from './plan.js';
|
|
11
|
+
import { VERSION as TOOL_VERSION } from '../version.js';
|
|
12
|
+
export { TOOL_VERSION };
|
|
13
|
+
export type ConversionErrorCode = 'OUTPUT_EXISTS' | 'OUTPUT_UNWRITABLE' | 'INPUT_OUTPUT_COLLISION' | 'WRITE_FAILED';
|
|
14
|
+
export declare class ConversionError extends Error {
|
|
15
|
+
readonly code: ConversionErrorCode;
|
|
16
|
+
readonly hint: string | undefined;
|
|
17
|
+
constructor(code: ConversionErrorCode, message: string, hint?: string);
|
|
18
|
+
}
|
|
19
|
+
export interface ConvertOptions extends PlanOptions {
|
|
20
|
+
/** Destination directory. Defaults to the input file's name without its extension. */
|
|
21
|
+
outputDir?: string | undefined;
|
|
22
|
+
/** Overwrite an existing output directory. */
|
|
23
|
+
force?: boolean | undefined;
|
|
24
|
+
/** Record a SHA-256 of the input in metadata.json. Costs one extra read of the file. */
|
|
25
|
+
checksum?: boolean | undefined;
|
|
26
|
+
onProgress?: ((progress: ConversionProgress) => void) | undefined;
|
|
27
|
+
}
|
|
28
|
+
export interface ConversionProgress {
|
|
29
|
+
recordsDone: number;
|
|
30
|
+
recordsTotal: number;
|
|
31
|
+
bytesWritten: number;
|
|
32
|
+
}
|
|
33
|
+
export interface WrittenFile {
|
|
34
|
+
name: string;
|
|
35
|
+
rows: number;
|
|
36
|
+
}
|
|
37
|
+
export interface ConvertResult {
|
|
38
|
+
outputDir: string;
|
|
39
|
+
files: WrittenFile[];
|
|
40
|
+
annotationCount: number;
|
|
41
|
+
diagnostics: Diagnostic[];
|
|
42
|
+
plan: ConversionPlan;
|
|
43
|
+
file: EdfFile;
|
|
44
|
+
elapsedMs: number;
|
|
45
|
+
}
|
|
46
|
+
export declare function convert(inputPath: string, options?: ConvertOptions): Promise<ConvertResult>;
|
|
47
|
+
export declare function defaultOutputDir(inputPath: string): string;
|
|
48
|
+
//# sourceMappingURL=run.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/convert/run.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAE3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAMnD,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAa,MAAM,WAAW,CAAC;AAGxE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,eAAe,CAAC;AAExD,OAAO,EAAE,YAAY,EAAE,CAAC;AAExB,MAAM,MAAM,mBAAmB,GAC3B,eAAe,GACf,mBAAmB,GACnB,wBAAwB,GACxB,cAAc,CAAC;AAEnB,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;gBACtB,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;CAMtE;AAED,MAAM,WAAW,cAAe,SAAQ,WAAW;IACjD,sFAAsF;IACtF,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,8CAA8C;IAC9C,KAAK,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC5B,wFAAwF;IACxF,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC/B,UAAU,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,kBAAkB,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;CACnE;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,IAAI,EAAE,cAAc,CAAC;IACrB,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AASD,wBAAsB,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,aAAa,CAAC,CAyErG;AAqCD,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAG1D"}
|