-
Notifications
You must be signed in to change notification settings - Fork 2
/
edfWriter.js
181 lines (153 loc) · 5.6 KB
/
edfWriter.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
// @flow
/*
* Some unfinished example code of writing a .edf file in JavaScript
* WARNING: this code isn't finished yet and doesn't really work
*/
import * as fs from "fs";
import * as _ from "lodash";
const replace = require("replace-in-file");
// Types from eeg-pipes
type eegChunk = {
data: Array<Array<channels>>,
info: {
startTime: number,
samplingRate: number
}
};
type eegSample = {
data: Array<channels>,
timestamp: number
};
export const writeEDFHeader = (
samplingRate: number,
writeStream: fs.WriteStream,
channelsObject: Array<channels>,
notchFilterInfo: Object,
bandFilterInfo: Object,
chunkLength: number
) => {
// Declare header
const version = "0".padEnd(8);
// - the code by which the patient is known in the hospital administration.
// - sex (English, so F or M).
// - birthdate in dd-MMM-yyyy format using the English 3-character abbreviations of the month in capitals. 02-AUG-1951 is OK, while 2-AUG-1951 is not.
// - the patients name.
const patientID = "X X X X".padEnd(80);
// - The text 'Startdate'.
// - The startdate itself in dd-MMM-yyyy format using the English 3-character abbreviations of the month in capitals.
// - The hospital administration code of the investigation, i.e. EEG number or PSG number.
// - A code specifying the responsible investigator or technician.
// - A code specifying the used equipment.
const recordingID = "X X X X X".padEnd(80);
const startDate = formatDate(new Date()).padEnd(8);
const startTime = formatTime(new Date()).padEnd(8);
const reserved = "".padEnd(44);
const nbRecords = "-1".padEnd(8); // -1 while unknown
const recordDuration = ("" + chunkLength / SAMPLING_RATE).padEnd(8); // record size in seconds
const nbSignals = ("" + channelsObject.length).padEnd(4);
const signalLabels = new Array(channelsObject.length)
.fill("")
.map((_, index) => channelNames[channelsObject[index]].padEnd(16));
const signalTypes = new Array(channelsObject.length)
.fill("")
.map((_, index) => {
return "Gold spring electrode".padEnd(80);
});
const signalDimensions = new Array(channelsObject.length)
.fill("")
.map((_, index) => "uV".padEnd(8));
// TODO: What is our min?
const signalPhysMins = new Array(channelsObject.length)
.fill("")
.map((_, index) => "-32768".padEnd(8));
// TODO: What is our max?
const signalPhysMaxs = new Array(channelsObject.length)
.fill("")
.map((_, index) => "32767".padEnd(8));
// TODO: What is our min?
const signalDigMins = new Array(channelsObject.length)
.fill("")
.map((_, index) => "-32768".padEnd(8));
// TODO: What is our max?
const signalDigMaxs = new Array(channelsObject.length)
.fill("")
.map((_, index) => "32767".padEnd(8));
// HP:0.1Hz LP:75Hz N:50Hz
const signalPrefiltering = new Array(channelsObject.length)
.fill("HP:0.5Hz ")
.map((val, index) => {
if (bandFilterInfo.type != NONE) {
if (bandFilterInfo.type === BANDPASS) {
val = val + "BP:" + bandFilterInfo.cutoffFrequency + "Hz ";
}
if (bandFilterInfo.type === HIGHPASS) {
val = val + "HP:" + bandFilterInfo.cutoffFrequency + "Hz ";
}
if (bandFilterInfo.type === LOWPASS) {
val = val + "LP:" + bandFilterInfo.cutoffFrequency + "Hz ";
}
}
if (notchFilterInfo.type != NONE) {
val = val + "N:" + notchFilterInfo.cutoffFrequency + "Hz ";
}
return val.padEnd(80);
});
const signalNbSamples = new Array(channelsObject.length)
.fill("")
.map((_, index) => ("" + chunkLength).padEnd(8));
const signalReserved = new Array(channelsObject.length).fill("".padEnd(32));
// Write header
writeStream.write(version);
writeStream.write(patientID);
writeStream.write(recordingID);
writeStream.write(startDate);
writeStream.write(startTime);
writeStream.write(("" + 256 * (channelsObject.length + 1)).padEnd(8)); // nbBytes in Header
writeStream.write(reserved);
writeStream.write(nbRecords);
writeStream.write(recordDuration);
writeStream.write(nbSignals);
signalLabels.forEach(x => writeStream.write(x));
signalTypes.forEach(x => writeStream.write(x));
signalDimensions.forEach(x => writeStream.write(x));
signalPhysMins.forEach(x => writeStream.write(x));
signalPhysMaxs.forEach(x => writeStream.write(x));
signalDigMins.forEach(x => writeStream.write(x));
signalDigMaxs.forEach(x => writeStream.write(x));
signalPrefiltering.forEach(x => writeStream.write(x));
signalNbSamples.forEach(x => writeStream.write(x));
signalReserved.forEach(x => writeStream.write(x));
};
export const writeChunkToEDF = (
chunk: eegChunk,
writeStream: fs.WriteStream
) => {
// Data stays in channels x samples configuration
// 2 Byte signed int litte-endian, 2's complement
let buffer = Buffer.alloc(2 * NB_CHANNELS * chunk.data[0].length, "base64");
let offset = 0;
for (let i = 0; i < chunk.data.length; i++) {
// For each channel
for (let j = 0; j < chunk.data[i].length; j++) {
// For each sample
buffer.writeInt16LE(parseInt(chunk.data[i][j] * 32768 / 188000), offset);
offset += 2;
}
}
writeStream.write(buffer, "base64");
};
// Replaces the nbRecords entry in the EDF+ header with the number of records that were collected
export const closeEDFFile = (filePath: string, nbRecords: number) => {
const options = {
files: filePath,
from: "-1".padEnd(8),
to: ("" + nbRecords).padEnd(8)
};
replace(options)
.then(changes => {
console.log("Modified files:", changes.join(", "));
})
.catch(error => {
console.error("Error occurred:", error);
});
};