47 QString sLabelUpper =
label.toUpper();
51 if(sLabelUpper.contains(
"ECOG"))
53 else if(sLabelUpper.contains(
"SEEG"))
55 else if(sLabelUpper.contains(
"EEG"))
57 else if(sLabelUpper.contains(
"MEG"))
59 else if(sLabelUpper.contains(
"ECG"))
61 else if(sLabelUpper.contains(
"EOG"))
63 else if(sLabelUpper.contains(
"EMG"))
70 if(sUnitUpper.endsWith(
"V") || sUnitUpper.endsWith(
"VOLT")) {
72 if(sUnitUpper.startsWith(
"U") || sUnitUpper.startsWith(
"MICRO"))
74 else if(sUnitUpper.startsWith(
"M") || sUnitUpper.startsWith(
"MILLI"))
76 else if(sUnitUpper.startsWith(
"N") || sUnitUpper.startsWith(
"NANO"))
97 : m_fScaleFactor(fScaleFactor)
105 if(m_file.isOpen()) {
114 m_sFilePath = sFilePath;
115 m_file.setFileName(sFilePath);
117 if(!m_file.open(QIODevice::ReadOnly)) {
118 qWarning() <<
"[EDFReader::open] Could not open file:" << sFilePath;
122 parseHeader(&m_file);
129void EDFReader::parseHeader(QIODevice* pDev)
131 if(pDev->pos() != 0) {
136 m_sVersionNo = QString::fromLatin1(pDev->read(EDF_VERSION)).trimmed();
137 m_sPatientId = QString::fromLatin1(pDev->read(LOCAL_PATIENT_INFO)).trimmed();
138 m_sRecordingId = QString::fromLatin1(pDev->read(LOCAL_RECORD_INFO)).trimmed();
139 m_startDateTime.setDate(QDate::fromString(QString::fromLatin1(pDev->read(STARTDATE)),
"dd.MM.yy"));
140 m_startDateTime = m_startDateTime.addYears(100);
141 m_startDateTime.setTime(QTime::fromString(QString::fromLatin1(pDev->read(STARTTIME)),
"hh.mm.ss"));
142 m_iNumBytesInHeader = QString::fromLatin1(pDev->read(NUM_BYTES_HEADER)).toInt();
143 pDev->read(HEADER_RESERVED);
144 m_iNumDataRecords = QString::fromLatin1(pDev->read(NUM_DATA_RECORDS)).toInt();
145 m_fDataRecordsDuration = QString::fromLatin1(pDev->read(DURATION_DATA_RECS)).toFloat();
146 m_iNumChannels = QString::fromLatin1(pDev->read(NUM_SIGNALS)).toInt();
149 QVector<QString> vLabels, vTransducers, vPhysDims, vPrefilterings;
150 QVector<float> vPhysMins, vPhysMaxs;
151 QVector<long> vDigMins, vDigMaxs, vSamplesPerRecord;
153 for(
int i = 0; i < m_iNumChannels; ++i)
154 vLabels.push_back(QString::fromLatin1(pDev->read(SIG_LABEL)).trimmed());
155 for(
int i = 0; i < m_iNumChannels; ++i)
156 vTransducers.push_back(QString::fromLatin1(pDev->read(SIG_TRANSDUCER)).trimmed());
157 for(
int i = 0; i < m_iNumChannels; ++i)
158 vPhysDims.push_back(QString::fromLatin1(pDev->read(SIG_PHYS_DIM)).trimmed());
159 for(
int i = 0; i < m_iNumChannels; ++i)
160 vPhysMins.push_back(QString::fromLatin1(pDev->read(SIG_PHYS_MIN)).toFloat());
161 for(
int i = 0; i < m_iNumChannels; ++i)
162 vPhysMaxs.push_back(QString::fromLatin1(pDev->read(SIG_PHYS_MAX)).toFloat());
163 for(
int i = 0; i < m_iNumChannels; ++i)
164 vDigMins.push_back(QString::fromLatin1(pDev->read(SIG_DIG_MIN)).toLong());
165 for(
int i = 0; i < m_iNumChannels; ++i)
166 vDigMaxs.push_back(QString::fromLatin1(pDev->read(SIG_DIG_MAX)).toLong());
167 for(
int i = 0; i < m_iNumChannels; ++i)
168 vPrefilterings.push_back(QString::fromLatin1(pDev->read(SIG_PREFILTERING)).trimmed());
169 for(
int i = 0; i < m_iNumChannels; ++i)
170 vSamplesPerRecord.push_back(QString::fromLatin1(pDev->read(SIG_NUM_SAMPLES)).toLong());
171 for(
int i = 0; i < m_iNumChannels; ++i)
172 pDev->read(SIG_RESERVED);
175 m_vAllChannels.clear();
176 for(
int i = 0; i < m_iNumChannels; ++i) {
179 ch.
label = vLabels[i];
188 ch.
sampleCount = vSamplesPerRecord[i] * m_iNumDataRecords;
189 ch.
frequency = (m_fDataRecordsDuration > 0.0f)
190 ? vSamplesPerRecord[i] / m_fDataRecordsDuration
193 m_vAllChannels.push_back(ch);
197 if(pDev->pos() != m_iNumBytesInHeader) {
198 qWarning() <<
"[EDFReader::parseHeader] Header byte count mismatch: read"
199 << pDev->pos() <<
"expected" << m_iNumBytesInHeader;
203 m_iNumBytesPerDataRecord = 0;
204 for(
const auto& ch : m_vAllChannels) {
209 long iMaxSamplesPerRecord = -1;
210 for(
const auto& ch : m_vAllChannels) {
216 m_vMeasChannels.clear();
217 for(
int i = 0; i < m_vAllChannels.size(); ++i) {
218 if(m_vAllChannels[i].samplesPerRecord == iMaxSamplesPerRecord) {
219 m_vAllChannels[i].isMeasurement =
true;
220 m_vMeasChannels.push_back(m_vAllChannels[i]);
230 info.
nchan = m_vMeasChannels.size();
232 for(
const auto& ch : m_vMeasChannels) {
234 info.
chs.append(fiffCh);
247 qWarning() <<
"[EDFReader::readRawSegment] File not open";
252 if(iStartSampleIdx < 0 || iStartSampleIdx >= totalSamples ||
253 iEndSampleIdx < 0 || iEndSampleIdx > totalSamples) {
254 qWarning() <<
"[EDFReader::readRawSegment] Index out of bounds:"
255 << iStartSampleIdx <<
"-" << iEndSampleIdx;
259 int iNumSamples = iEndSampleIdx - iStartSampleIdx;
260 if(iNumSamples <= 0) {
264 int iSamplesPerRecord = m_vMeasChannels.isEmpty() ? 0 : m_vMeasChannels[0].samplesPerRecord;
265 if(iSamplesPerRecord <= 0) {
270 int iFirstRecord = iStartSampleIdx / iSamplesPerRecord;
271 int iRelativeFirst = iStartSampleIdx % iSamplesPerRecord;
272 int iNumRecords =
static_cast<int>(
273 std::ceil(
static_cast<float>(iNumSamples + iRelativeFirst) / iSamplesPerRecord));
276 m_file.seek(m_iNumBytesInHeader +
static_cast<qint64
>(iFirstRecord) * m_iNumBytesPerDataRecord);
279 QVector<QByteArray> vRecords;
280 vRecords.reserve(iNumRecords);
281 for(
int i = 0; i < iNumRecords; ++i) {
282 vRecords.push_back(m_file.read(m_iNumBytesPerDataRecord));
286 QVector<QVector<int>> vRawPatches(m_vAllChannels.size());
287 for(
int iRec = 0; iRec < vRecords.size(); ++iRec) {
289 for(
int iCh = 0; iCh < m_vAllChannels.size(); ++iCh) {
290 int nSamp = m_vAllChannels[iCh].samplesPerRecord;
291 QVector<int> patch(nSamp);
292 for(
int s = 0; s < nSamp; ++s) {
293 int byteIdx = (iOffset + s) * 2;
295 patch[s] =
static_cast<int16_t
>(
296 (
static_cast<unsigned char>(vRecords[iRec].at(byteIdx + 1)) << 8) |
297 (
static_cast<unsigned char>(vRecords[iRec].at(byteIdx))));
300 vRawPatches[iCh] += patch;
305 QVector<QVector<int>> vMeasPatches;
306 vMeasPatches.reserve(m_vMeasChannels.size());
307 for(
int iCh = 0; iCh < m_vAllChannels.size(); ++iCh) {
308 if(m_vAllChannels[iCh].isMeasurement) {
309 vMeasPatches.push_back(vRawPatches[iCh]);
314 MatrixXf result(vMeasPatches.size(), iNumSamples);
316 for(
int iCh = 0; iCh < vMeasPatches.size(); ++iCh) {
321 for(
int s = 0; s < iNumSamples; ++s) {
322 int rawIdx = s + iRelativeFirst;
323 float physVal =
static_cast<float>(vMeasPatches[iCh][rawIdx] - ch.
digitalMin) / digRange
326 physVal /= m_fScaleFactor;
328 result(iCh, s) = physVal;
339 if(!m_vMeasChannels.isEmpty()) {
340 return m_vMeasChannels[0].sampleCount;
349 if(!m_vMeasChannels.isEmpty()) {
350 return m_vMeasChannels[0].frequency;
359 return m_vMeasChannels.size();
372 for(
int i = 0; i < raw.
info.
chs.size(); ++i) {
373 cals[i] =
static_cast<double>(raw.
info.
chs[i].cal);
384 return QStringLiteral(
"EDF");
391 QString ext = sExtension.toLower();
392 return (ext ==
".edf" || ext ==
".bdf");
399 return m_vAllChannels;
406 return m_vMeasChannels;
BIDSLIB::AbstractFormatReader implementation for European Data Format (EDF / EDF+) files.
Symbolic FIFF tag, block, value, unit and channel-type constants shared across FIFFLIB.
BIDS dataset reading, writing, path construction, and sidecar metadata handling for iEEG/EEG/MEG.
FIFF file I/O, in-memory data structures and high-level readers/writers.
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.
Per-channel FIFF descriptor: identifiers, kind, calibration, coil type, channel-frame coil position a...
Full FIFF measurement info: per-channel descriptors, sampling and filter setup, projectors,...