71 QString sLabelUpper =
label.toUpper();
75 if(sLabelUpper.contains(
"ECOG"))
77 else if(sLabelUpper.contains(
"SEEG"))
79 else if(sLabelUpper.contains(
"EEG"))
81 else if(sLabelUpper.contains(
"MEG"))
83 else if(sLabelUpper.contains(
"ECG"))
85 else if(sLabelUpper.contains(
"EOG"))
87 else if(sLabelUpper.contains(
"EMG"))
94 if(sUnitUpper.endsWith(
"V") || sUnitUpper.endsWith(
"VOLT")) {
96 if(sUnitUpper.startsWith(
"U") || sUnitUpper.startsWith(
"MICRO"))
98 else if(sUnitUpper.startsWith(
"M") || sUnitUpper.startsWith(
"MILLI"))
100 else if(sUnitUpper.startsWith(
"N") || sUnitUpper.startsWith(
"NANO"))
121 : m_fScaleFactor(fScaleFactor)
129 if(m_file.isOpen()) {
138 m_sFilePath = sFilePath;
139 m_file.setFileName(sFilePath);
141 if(!m_file.open(QIODevice::ReadOnly)) {
142 qWarning() <<
"[EDFReader::open] Could not open file:" << sFilePath;
146 parseHeader(&m_file);
153void EDFReader::parseHeader(QIODevice* pDev)
155 if(pDev->pos() != 0) {
160 m_sVersionNo = QString::fromLatin1(pDev->read(EDF_VERSION)).trimmed();
161 m_sPatientId = QString::fromLatin1(pDev->read(LOCAL_PATIENT_INFO)).trimmed();
162 m_sRecordingId = QString::fromLatin1(pDev->read(LOCAL_RECORD_INFO)).trimmed();
163 m_startDateTime.setDate(QDate::fromString(QString::fromLatin1(pDev->read(STARTDATE)),
"dd.MM.yy"));
164 m_startDateTime = m_startDateTime.addYears(100);
165 m_startDateTime.setTime(QTime::fromString(QString::fromLatin1(pDev->read(STARTTIME)),
"hh.mm.ss"));
166 m_iNumBytesInHeader = QString::fromLatin1(pDev->read(NUM_BYTES_HEADER)).toInt();
167 pDev->read(HEADER_RESERVED);
168 m_iNumDataRecords = QString::fromLatin1(pDev->read(NUM_DATA_RECORDS)).toInt();
169 m_fDataRecordsDuration = QString::fromLatin1(pDev->read(DURATION_DATA_RECS)).toFloat();
170 m_iNumChannels = QString::fromLatin1(pDev->read(NUM_SIGNALS)).toInt();
173 QVector<QString> vLabels, vTransducers, vPhysDims, vPrefilterings;
174 QVector<float> vPhysMins, vPhysMaxs;
175 QVector<long> vDigMins, vDigMaxs, vSamplesPerRecord;
177 for(
int i = 0; i < m_iNumChannels; ++i)
178 vLabels.push_back(QString::fromLatin1(pDev->read(SIG_LABEL)).trimmed());
179 for(
int i = 0; i < m_iNumChannels; ++i)
180 vTransducers.push_back(QString::fromLatin1(pDev->read(SIG_TRANSDUCER)).trimmed());
181 for(
int i = 0; i < m_iNumChannels; ++i)
182 vPhysDims.push_back(QString::fromLatin1(pDev->read(SIG_PHYS_DIM)).trimmed());
183 for(
int i = 0; i < m_iNumChannels; ++i)
184 vPhysMins.push_back(QString::fromLatin1(pDev->read(SIG_PHYS_MIN)).toFloat());
185 for(
int i = 0; i < m_iNumChannels; ++i)
186 vPhysMaxs.push_back(QString::fromLatin1(pDev->read(SIG_PHYS_MAX)).toFloat());
187 for(
int i = 0; i < m_iNumChannels; ++i)
188 vDigMins.push_back(QString::fromLatin1(pDev->read(SIG_DIG_MIN)).toLong());
189 for(
int i = 0; i < m_iNumChannels; ++i)
190 vDigMaxs.push_back(QString::fromLatin1(pDev->read(SIG_DIG_MAX)).toLong());
191 for(
int i = 0; i < m_iNumChannels; ++i)
192 vPrefilterings.push_back(QString::fromLatin1(pDev->read(SIG_PREFILTERING)).trimmed());
193 for(
int i = 0; i < m_iNumChannels; ++i)
194 vSamplesPerRecord.push_back(QString::fromLatin1(pDev->read(SIG_NUM_SAMPLES)).toLong());
195 for(
int i = 0; i < m_iNumChannels; ++i)
196 pDev->read(SIG_RESERVED);
199 m_vAllChannels.clear();
200 for(
int i = 0; i < m_iNumChannels; ++i) {
203 ch.
label = vLabels[i];
212 ch.
sampleCount = vSamplesPerRecord[i] * m_iNumDataRecords;
213 ch.
frequency = (m_fDataRecordsDuration > 0.0f)
214 ? vSamplesPerRecord[i] / m_fDataRecordsDuration
217 m_vAllChannels.push_back(ch);
221 if(pDev->pos() != m_iNumBytesInHeader) {
222 qWarning() <<
"[EDFReader::parseHeader] Header byte count mismatch: read"
223 << pDev->pos() <<
"expected" << m_iNumBytesInHeader;
227 m_iNumBytesPerDataRecord = 0;
228 for(
const auto& ch : m_vAllChannels) {
233 long iMaxSamplesPerRecord = -1;
234 for(
const auto& ch : m_vAllChannels) {
240 m_vMeasChannels.clear();
241 for(
int i = 0; i < m_vAllChannels.size(); ++i) {
242 if(m_vAllChannels[i].samplesPerRecord == iMaxSamplesPerRecord) {
243 m_vAllChannels[i].isMeasurement =
true;
244 m_vMeasChannels.push_back(m_vAllChannels[i]);
254 info.
nchan = m_vMeasChannels.size();
256 for(
const auto& ch : m_vMeasChannels) {
258 info.
chs.append(fiffCh);
271 qWarning() <<
"[EDFReader::readRawSegment] File not open";
276 if(iStartSampleIdx < 0 || iStartSampleIdx >= totalSamples ||
277 iEndSampleIdx < 0 || iEndSampleIdx > totalSamples) {
278 qWarning() <<
"[EDFReader::readRawSegment] Index out of bounds:"
279 << iStartSampleIdx <<
"-" << iEndSampleIdx;
283 int iNumSamples = iEndSampleIdx - iStartSampleIdx;
284 if(iNumSamples <= 0) {
288 int iSamplesPerRecord = m_vMeasChannels.isEmpty() ? 0 : m_vMeasChannels[0].samplesPerRecord;
289 if(iSamplesPerRecord <= 0) {
294 int iFirstRecord = iStartSampleIdx / iSamplesPerRecord;
295 int iRelativeFirst = iStartSampleIdx % iSamplesPerRecord;
296 int iNumRecords =
static_cast<int>(
297 std::ceil(
static_cast<float>(iNumSamples + iRelativeFirst) / iSamplesPerRecord));
300 m_file.seek(m_iNumBytesInHeader +
static_cast<qint64
>(iFirstRecord) * m_iNumBytesPerDataRecord);
303 QVector<QByteArray> vRecords;
304 vRecords.reserve(iNumRecords);
305 for(
int i = 0; i < iNumRecords; ++i) {
306 vRecords.push_back(m_file.read(m_iNumBytesPerDataRecord));
310 QVector<QVector<int>> vRawPatches(m_vAllChannels.size());
311 for(
int iRec = 0; iRec < vRecords.size(); ++iRec) {
313 for(
int iCh = 0; iCh < m_vAllChannels.size(); ++iCh) {
314 int nSamp = m_vAllChannels[iCh].samplesPerRecord;
315 QVector<int> patch(nSamp);
316 for(
int s = 0; s < nSamp; ++s) {
317 int byteIdx = (iOffset + s) * 2;
319 patch[s] =
static_cast<int16_t
>(
320 (
static_cast<unsigned char>(vRecords[iRec].at(byteIdx + 1)) << 8) |
321 (
static_cast<unsigned char>(vRecords[iRec].at(byteIdx))));
324 vRawPatches[iCh] += patch;
329 QVector<QVector<int>> vMeasPatches;
330 vMeasPatches.reserve(m_vMeasChannels.size());
331 for(
int iCh = 0; iCh < m_vAllChannels.size(); ++iCh) {
332 if(m_vAllChannels[iCh].isMeasurement) {
333 vMeasPatches.push_back(vRawPatches[iCh]);
338 MatrixXf result(vMeasPatches.size(), iNumSamples);
340 for(
int iCh = 0; iCh < vMeasPatches.size(); ++iCh) {
345 for(
int s = 0; s < iNumSamples; ++s) {
346 int rawIdx = s + iRelativeFirst;
347 float physVal =
static_cast<float>(vMeasPatches[iCh][rawIdx] - ch.
digitalMin) / digRange
350 physVal /= m_fScaleFactor;
352 result(iCh, s) = physVal;
363 if(!m_vMeasChannels.isEmpty()) {
364 return m_vMeasChannels[0].sampleCount;
373 if(!m_vMeasChannels.isEmpty()) {
374 return m_vMeasChannels[0].frequency;
383 return m_vMeasChannels.size();
396 for(
int i = 0; i < raw.
info.
chs.size(); ++i) {
397 cals[i] =
static_cast<double>(raw.
info.
chs[i].cal);
408 return QStringLiteral(
"EDF");
415 QString ext = sExtension.toLower();
416 return (ext ==
".edf" || ext ==
".bdf");
423 return m_vAllChannels;
430 return m_vMeasChannels;
Contains the declaration of the EDFReader class. Refactored from the mne_edf2fiff tool into a reusabl...
BIDS dataset reading, writing, path construction, and sidecar metadata handling for iEEG/EEG/MEG.
FIFF file I/O and data structures (raw, epochs, evoked, covariance, forward).
Channel-level metadata from the EDF header.
QString physicalDimension
FIFFLIB::FiffChInfo toFiffChInfo() const
FIFFLIB::FiffInfo getInfo() const override
Return measurement metadata as FiffInfo.
EDFReader(float fScaleFactor=1e6)
EDFReader Default constructor.
Eigen::MatrixXf readRawSegment(int iStartSampleIdx, int iEndSampleIdx) const override
Read a segment of raw data.
bool open(const QString &sFilePath) override
Open and parse the file header. Must be called before reading data.
bool supportsExtension(const QString &sExtension) const override
Check whether this reader can handle the given file extension.
QVector< EDFChannelInfo > getAllChannelInfos() const
Return all channel infos (measurement + extra).
float getFrequency() const override
Return the sampling frequency in Hz.
FIFFLIB::FiffRawData toFiffRawData() const override
Convert the entire dataset to a FiffRawData structure.
QVector< EDFChannelInfo > getMeasurementChannelInfos() const
Return measurement channel infos only.
long getSampleCount() const override
Return total number of samples across the recording.
QString formatName() const override
Return a descriptive name for the format (e.g. "EDF", "BrainVision").
int getChannelCount() const override
Return the number of measurement channels.
FIFF measurement file information.
FIFF raw measurement data.