edf2csv 0.9.17 → 0.9.19
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/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/convert/run.d.ts +10 -1
- package/dist/convert/run.js +78 -16
- package/dist/convert/run.js.map +1 -1
- package/dist/edf/reader.js +43 -12
- package/dist/edf/reader.js.map +1 -1
- package/package.json +1 -1
package/dist/convert/run.d.ts
CHANGED
|
@@ -219,6 +219,15 @@ export declare function stdoutRefusal(file: EdfFile, plan: ConversionPlan): Conv
|
|
|
219
219
|
* on is in the file and the plan, both of which `--info` already has.
|
|
220
220
|
*/
|
|
221
221
|
export declare function noSignalFile(file: EdfFile, plan: ConversionPlan): Diagnostic | null;
|
|
222
|
+
/**
|
|
223
|
+
* The channels whose samples this run actually writes.
|
|
224
|
+
*
|
|
225
|
+
* `plan.columnNames` names every channel of the recording, because the names are derived from
|
|
226
|
+
* the whole file rather than from the selection — so it is not the answer. The groups are:
|
|
227
|
+
* `--channels` builds them out of what was selected, and a channel with no samples per record
|
|
228
|
+
* has none to put in one either.
|
|
229
|
+
*/
|
|
230
|
+
export declare function convertedChannels(plan: ConversionPlan): Set<number>;
|
|
222
231
|
/**
|
|
223
232
|
* The signal table an `--annotations-only` run does not write, taken out of the hints about it.
|
|
224
233
|
*
|
|
@@ -241,7 +250,7 @@ export declare function noSignalFile(file: EdfFile, plan: ConversionPlan): Diagn
|
|
|
241
250
|
*
|
|
242
251
|
* `writesSignals` rather than the option, because the plan is what settles it.
|
|
243
252
|
*/
|
|
244
|
-
export declare function withSignalTableUnwritten(diagnostics: readonly Diagnostic[], writesSignals: boolean, gzip: boolean): Diagnostic[];
|
|
253
|
+
export declare function withSignalTableUnwritten(diagnostics: readonly Diagnostic[], writesSignals: boolean, gzip: boolean, converted?: ReadonlySet<number>): Diagnostic[];
|
|
245
254
|
/**
|
|
246
255
|
* The sidecar files a `--stdout` run does not write, taken out of the sentences about them.
|
|
247
256
|
*
|
package/dist/convert/run.js
CHANGED
|
@@ -107,7 +107,7 @@ export async function convert(inputPath, options = {}) {
|
|
|
107
107
|
diagnostics: withSidecarsNamed(withSignalTableUnwritten([
|
|
108
108
|
...withTimingPromiseKept(withoutFileRateWarning(file.diagnostics), timing.starts !== null),
|
|
109
109
|
...plan.diagnostics,
|
|
110
|
-
], plan.writeSignals, plan.gzip), { toStdout: true, gzip: plan.gzip }),
|
|
110
|
+
], plan.writeSignals, plan.gzip, convertedChannels(plan)), { toStdout: true, gzip: plan.gzip }),
|
|
111
111
|
plan,
|
|
112
112
|
file,
|
|
113
113
|
elapsedMs: Date.now() - startedAt,
|
|
@@ -209,7 +209,7 @@ export async function convert(inputPath, options = {}) {
|
|
|
209
209
|
...withTimingPromiseKept(withoutFileRateWarning(file.diagnostics), timing.starts !== null),
|
|
210
210
|
...plan.diagnostics,
|
|
211
211
|
...stale,
|
|
212
|
-
], plan.writeSignals, plan.gzip), { toStdout: false, gzip: plan.gzip }),
|
|
212
|
+
], plan.writeSignals, plan.gzip, convertedChannels(plan)), { toStdout: false, gzip: plan.gzip }),
|
|
213
213
|
plan,
|
|
214
214
|
file,
|
|
215
215
|
elapsedMs: Date.now() - startedAt,
|
|
@@ -1606,6 +1606,17 @@ export function noSignalFile(file, plan) {
|
|
|
1606
1606
|
`table --info prints, and in the ${outputCsvName('channels', plan.gzip)} a conversion writes.`,
|
|
1607
1607
|
};
|
|
1608
1608
|
}
|
|
1609
|
+
/**
|
|
1610
|
+
* The channels whose samples this run actually writes.
|
|
1611
|
+
*
|
|
1612
|
+
* `plan.columnNames` names every channel of the recording, because the names are derived from
|
|
1613
|
+
* the whole file rather than from the selection — so it is not the answer. The groups are:
|
|
1614
|
+
* `--channels` builds them out of what was selected, and a channel with no samples per record
|
|
1615
|
+
* has none to put in one either.
|
|
1616
|
+
*/
|
|
1617
|
+
export function convertedChannels(plan) {
|
|
1618
|
+
return new Set(plan.groups.flatMap((group) => group.channels.map((c) => c.signal.index)));
|
|
1619
|
+
}
|
|
1609
1620
|
/**
|
|
1610
1621
|
* The signal table an `--annotations-only` run does not write, taken out of the hints about it.
|
|
1611
1622
|
*
|
|
@@ -1628,8 +1639,34 @@ export function noSignalFile(file, plan) {
|
|
|
1628
1639
|
*
|
|
1629
1640
|
* `writesSignals` rather than the option, because the plan is what settles it.
|
|
1630
1641
|
*/
|
|
1631
|
-
export function withSignalTableUnwritten(diagnostics, writesSignals, gzip
|
|
1632
|
-
|
|
1642
|
+
export function withSignalTableUnwritten(diagnostics, writesSignals, gzip,
|
|
1643
|
+
/*
|
|
1644
|
+
And the channels this run leaves out, which is the same question one channel at a time.
|
|
1645
|
+
|
|
1646
|
+
`--annotations-only` writes no cells for any channel; `--channels` writes none for the
|
|
1647
|
+
ones it excludes, and the five sentences below are as untrue of those as they are of the
|
|
1648
|
+
whole run. `--info` prints `(not selected)` in the OUTPUT column for them and channels.csv
|
|
1649
|
+
records them with `converted: no`, and the warning three lines under the table said:
|
|
1650
|
+
|
|
1651
|
+
0 flat flat uV 4 Hz 0 to 0 (not selected)
|
|
1652
|
+
...
|
|
1653
|
+
warning: Signal 0 ("flat") has digital minimum equal to digital maximum (0), so its
|
|
1654
|
+
values cannot be scaled.
|
|
1655
|
+
Its cells are left empty rather than filled with a value the header cannot
|
|
1656
|
+
justify.
|
|
1657
|
+
|
|
1658
|
+
There are no cells. Under `--strict` that is a failed run over a channel this one does not
|
|
1659
|
+
touch. The rewrites are the same rewrites, so the set of them is the argument rather than
|
|
1660
|
+
a second copy: empty means no signal table at all, which is what `writesSignals` said.
|
|
1661
|
+
*/
|
|
1662
|
+
converted = new Set()) {
|
|
1663
|
+
const skipped = (diagnostic) => {
|
|
1664
|
+
if (!writesSignals)
|
|
1665
|
+
return true;
|
|
1666
|
+
const named = /^Signal (\d+)\b/u.exec(diagnostic.message);
|
|
1667
|
+
return named !== null && !converted.has(Number(named[1]));
|
|
1668
|
+
};
|
|
1669
|
+
if (writesSignals && diagnostics.every((d) => !skipped(d)))
|
|
1633
1670
|
return [...diagnostics];
|
|
1634
1671
|
/*
|
|
1635
1672
|
The sidecars named here are named as the run writes them.
|
|
@@ -1643,6 +1680,13 @@ export function withSignalTableUnwritten(diagnostics, writesSignals, gzip) {
|
|
|
1643
1680
|
const channelsFile = outputCsvName('channels', gzip);
|
|
1644
1681
|
const annotationsFile = outputCsvName('annotations', gzip);
|
|
1645
1682
|
return diagnostics.map((diagnostic) => {
|
|
1683
|
+
if (!skipped(diagnostic))
|
|
1684
|
+
return diagnostic;
|
|
1685
|
+
// The reason the cells are missing, which is the only part that differs between the two.
|
|
1686
|
+
const because = writesSignals ? 'for a channel --channels left out' : 'with --annotations-only';
|
|
1687
|
+
// The run-level sentences below are only reached when no signal table is written at all:
|
|
1688
|
+
// a selection leaves the rows alone, so nothing it excludes changes what the column says.
|
|
1689
|
+
const writes = '--annotations-only writes no signal rows';
|
|
1646
1690
|
/*
|
|
1647
1691
|
And the three calibration warnings, whose hints are about cells.
|
|
1648
1692
|
|
|
@@ -1689,19 +1733,37 @@ export function withSignalTableUnwritten(diagnostics, writesSignals, gzip) {
|
|
|
1689
1733
|
.replace(' in any conversion that writes one', ''),
|
|
1690
1734
|
};
|
|
1691
1735
|
}
|
|
1736
|
+
/*
|
|
1737
|
+
And the channel that lost its own name, whose sentence ends in a column.
|
|
1738
|
+
|
|
1739
|
+
plan.ts writes "so its column is `X`" for a run that writes a signal table and "so it is
|
|
1740
|
+
named `X` in channels.csv's column cell" for one that does not — decided by the run,
|
|
1741
|
+
because that is all it knows there. A selection decides it per channel: the rename still
|
|
1742
|
+
happens, since the names are derived from the whole file so that they agree with
|
|
1743
|
+
channels.csv and across runs, and the column it is named for is one this run has not got.
|
|
1744
|
+
*/
|
|
1745
|
+
if (diagnostic.code === 'DUPLICATE_LABEL' && / so its column is "/u.test(diagnostic.message)) {
|
|
1746
|
+
return {
|
|
1747
|
+
...diagnostic,
|
|
1748
|
+
message: diagnostic.message.replace(/ so its column is ("[^"]*")\.$/u, ` so it is named $1 in ${channelsFile}'s column cell.`),
|
|
1749
|
+
};
|
|
1750
|
+
}
|
|
1692
1751
|
if (diagnostic.code === 'DUPLICATE_LABEL' &&
|
|
1693
1752
|
diagnostic.hint?.startsWith('Their names are suffixed')) {
|
|
1694
1753
|
return {
|
|
1695
1754
|
...diagnostic,
|
|
1696
1755
|
hint: 'Their names are suffixed with the signal number so they stay distinguishable. ' +
|
|
1697
|
-
|
|
1698
|
-
|
|
1756
|
+
(writesSignals
|
|
1757
|
+
? `--channels left this channel out, so its suffixed name appears in ` +
|
|
1758
|
+
`${channelsFile}'s column cell and nowhere else.`
|
|
1759
|
+
: '--annotations-only writes no signal table, so the suffixed names appear only ' +
|
|
1760
|
+
`in ${channelsFile}'s column cells.`),
|
|
1699
1761
|
};
|
|
1700
1762
|
}
|
|
1701
1763
|
if (diagnostic.code === 'DEGENERATE_DIGITAL_RANGE') {
|
|
1702
1764
|
return {
|
|
1703
1765
|
...diagnostic,
|
|
1704
|
-
hint:
|
|
1766
|
+
hint: `No samples are converted ${because}, so there are no cells to leave ` +
|
|
1705
1767
|
`empty. ${channelsFile} still records the digital range the header gives.`,
|
|
1706
1768
|
};
|
|
1707
1769
|
}
|
|
@@ -1709,14 +1771,14 @@ export function withSignalTableUnwritten(diagnostics, writesSignals, gzip) {
|
|
|
1709
1771
|
return {
|
|
1710
1772
|
...diagnostic,
|
|
1711
1773
|
message: diagnostic.message.replace('so every sample converts to the same value.', 'so every sample would convert to the same value.'),
|
|
1712
|
-
hint: `No samples are converted
|
|
1774
|
+
hint: `No samples are converted ${because}. ${channelsFile} still records ` +
|
|
1713
1775
|
'the calibration, one point wide.',
|
|
1714
1776
|
};
|
|
1715
1777
|
}
|
|
1716
1778
|
if (diagnostic.code === 'INVERTED_PHYSICAL_RANGE') {
|
|
1717
1779
|
return {
|
|
1718
1780
|
...diagnostic,
|
|
1719
|
-
hint: `No samples are converted
|
|
1781
|
+
hint: `No samples are converted ${because}. ${channelsFile} records the ` +
|
|
1720
1782
|
'physical minimum and maximum in the order the header gives them, inversion included.',
|
|
1721
1783
|
};
|
|
1722
1784
|
}
|
|
@@ -1731,7 +1793,7 @@ export function withSignalTableUnwritten(diagnostics, writesSignals, gzip) {
|
|
|
1731
1793
|
const one = diagnostic.hint.startsWith('That record');
|
|
1732
1794
|
return {
|
|
1733
1795
|
...diagnostic,
|
|
1734
|
-
hint:
|
|
1796
|
+
hint: `${writes}, so nothing is timed from ${one ? 'that record' : 'those records'}. ` +
|
|
1735
1797
|
`The events in ${one ? 'it' : 'them'} carry their own onsets, and ` +
|
|
1736
1798
|
`${annotationsFile}'s record_index still names ${one ? 'it' : 'them'}.`,
|
|
1737
1799
|
};
|
|
@@ -1741,7 +1803,7 @@ export function withSignalTableUnwritten(diagnostics, writesSignals, gzip) {
|
|
|
1741
1803
|
if (diagnostic.hint.startsWith('Each row carries its true recording time')) {
|
|
1742
1804
|
return {
|
|
1743
1805
|
...diagnostic,
|
|
1744
|
-
hint:
|
|
1806
|
+
hint: `${writes}, so nothing here is timed from the ` +
|
|
1745
1807
|
`records. ${annotationsFile} carries each event's own onset, and the record it ` +
|
|
1746
1808
|
'came from in record_index.',
|
|
1747
1809
|
};
|
|
@@ -1749,14 +1811,14 @@ export function withSignalTableUnwritten(diagnostics, writesSignals, gzip) {
|
|
|
1749
1811
|
if (diagnostic.hint.startsWith('Rows are written in file order')) {
|
|
1750
1812
|
return {
|
|
1751
1813
|
...diagnostic,
|
|
1752
|
-
hint:
|
|
1814
|
+
hint: `${writes}, so no time column is affected. ` +
|
|
1753
1815
|
`${annotationsFile}'s record_index still names the record each event came from.`,
|
|
1754
1816
|
};
|
|
1755
1817
|
}
|
|
1756
1818
|
if (diagnostic.hint.startsWith('Sample times are written from zero')) {
|
|
1757
1819
|
return {
|
|
1758
1820
|
...diagnostic,
|
|
1759
|
-
hint:
|
|
1821
|
+
hint: `${writes}, so no sample times are written at all.`,
|
|
1760
1822
|
};
|
|
1761
1823
|
}
|
|
1762
1824
|
/*
|
|
@@ -1787,7 +1849,7 @@ export function withSignalTableUnwritten(diagnostics, writesSignals, gzip) {
|
|
|
1787
1849
|
if (diagnostic.hint.startsWith('Times are written as if the records were contiguous')) {
|
|
1788
1850
|
return {
|
|
1789
1851
|
...diagnostic,
|
|
1790
|
-
hint:
|
|
1852
|
+
hint: `${writes}, so no times are written from the ` +
|
|
1791
1853
|
`records at all. ${annotationsFile} carries each event's own onset, which the ` +
|
|
1792
1854
|
'records do not decide.',
|
|
1793
1855
|
};
|
|
@@ -1799,7 +1861,7 @@ export function withSignalTableUnwritten(diagnostics, writesSignals, gzip) {
|
|
|
1799
1861
|
return {
|
|
1800
1862
|
...diagnostic,
|
|
1801
1863
|
hint: 'Where its records sit in time is not recorded in this file, and ' +
|
|
1802
|
-
|
|
1864
|
+
`${writes} to time from them anyway — see the ` +
|
|
1803
1865
|
'warning below.',
|
|
1804
1866
|
};
|
|
1805
1867
|
}
|
|
@@ -2122,7 +2184,7 @@ bom) {
|
|
|
2122
2184
|
notes: withSidecarsNamed(withSignalTableUnwritten([
|
|
2123
2185
|
...withTimingPromiseKept(withoutFileRateWarning(file.diagnostics), timedFromRecords),
|
|
2124
2186
|
...plan.diagnostics,
|
|
2125
|
-
], plan.writeSignals, plan.gzip),
|
|
2187
|
+
], plan.writeSignals, plan.gzip, convertedChannels(plan)),
|
|
2126
2188
|
// metadata.json is only written into a directory, so this is never the stdout case.
|
|
2127
2189
|
{ toStdout: false, gzip: plan.gzip }).map((d) => ({
|
|
2128
2190
|
code: d.code,
|