27#include <QWriteLocker>
45constexpr float kScaleMEGGrad = 400e-13f;
46constexpr float kScaleMEGMag = 1.2e-12f;
47constexpr float kScaleEEG = 30e-6f;
48constexpr float kScaleEOG = 150e-6f;
49constexpr float kScaleEMG = 1e-3f;
50constexpr float kScaleECG = 1e-3f;
51constexpr float kScaleSTIM = 5.0f;
52constexpr float kScaleMISC = 1.0f;
53constexpr float kScaleFallback = 1.0f;
56constexpr qint32 kMEGGradKind = -1;
57constexpr qint32 kMEGMagKind = -2;
81 QWriteLocker lk(&m_lock);
82 m_pFiffInfo = pFiffInfo;
84 int nCh = (pFiffInfo ? pFiffInfo->nchan : 0) + m_virtualDisplayInfo.size();
85 m_channelData.resize(nCh);
86 for (
auto& ch : m_channelData)
99 if (data.rows() == 0 || data.cols() == 0)
102 QWriteLocker lk(&m_lock);
103 int nCh =
static_cast<int>(data.rows());
104 m_channelData.resize(nCh);
105 for (
int ch = 0; ch < nCh; ++ch) {
106 m_channelData[ch].resize(
static_cast<int>(data.cols()));
107 for (
int s = 0; s < static_cast<int>(data.cols()); ++s)
108 m_channelData[ch][s] =
static_cast<float>(data(ch, s));
120 if (data.rows() == 0 || data.cols() == 0)
123 QWriteLocker lk(&m_lock);
124 int nCh =
static_cast<int>(data.rows());
125 int nNew =
static_cast<int>(data.cols());
127 if (m_channelData.size() != nCh)
128 m_channelData.resize(nCh);
130 for (
int ch = 0; ch < nCh; ++ch) {
131 auto& buf = m_channelData[ch];
132 int oldSize = buf.size();
133 buf.resize(oldSize + nNew);
134 for (
int s = 0; s < nNew; ++s)
135 buf[oldSize + s] =
static_cast<float>(data(ch, s));
138 if (m_maxStoredSamples > 0 && buf.size() > m_maxStoredSamples) {
139 int drop = buf.size() - m_maxStoredSamples;
142 m_firstSample += drop;
155 QWriteLocker lk(&m_lock);
156 for (
auto& ch : m_channelData)
168 QWriteLocker lk(&m_lock);
169 m_scaleMap = scaleMap;
171 rebuildDisplayInfo();
179 QMap<qint32, float> intMap;
180 if (scaleMap.contains(QStringLiteral(
"MEG_grad")))
181 intMap[kMEGGradKind] =
static_cast<float>(scaleMap.value(QStringLiteral(
"MEG_grad")));
182 if (scaleMap.contains(QStringLiteral(
"MEG_mag")))
183 intMap[kMEGMagKind] =
static_cast<float>(scaleMap.value(QStringLiteral(
"MEG_mag")));
184 if (scaleMap.contains(QStringLiteral(
"MEG_EEG")))
185 intMap[
FIFFV_EEG_CH] =
static_cast<float>(scaleMap.value(QStringLiteral(
"MEG_EEG")));
186 if (scaleMap.contains(QStringLiteral(
"MEG_EOG")))
187 intMap[
FIFFV_EOG_CH] =
static_cast<float>(scaleMap.value(QStringLiteral(
"MEG_EOG")));
188 if (scaleMap.contains(QStringLiteral(
"MEG_EMG")))
189 intMap[
FIFFV_EMG_CH] =
static_cast<float>(scaleMap.value(QStringLiteral(
"MEG_EMG")));
190 if (scaleMap.contains(QStringLiteral(
"MEG_ECG")))
191 intMap[
FIFFV_ECG_CH] =
static_cast<float>(scaleMap.value(QStringLiteral(
"MEG_ECG")));
192 if (scaleMap.contains(QStringLiteral(
"MEG_MISC")))
193 intMap[
FIFFV_MISC_CH] =
static_cast<float>(scaleMap.value(QStringLiteral(
"MEG_MISC")));
194 if (scaleMap.contains(QStringLiteral(
"MEG_STIM")))
195 intMap[
FIFFV_STIM_CH] =
static_cast<float>(scaleMap.value(QStringLiteral(
"MEG_STIM")));
204 QWriteLocker lk(&m_lock);
205 m_virtualDisplayInfo = virtualChannels;
207 const int realChannelCount = m_pFiffInfo ? m_pFiffInfo->nchan : 0;
208 const int totalChannelCount = realChannelCount + m_virtualDisplayInfo.size();
209 m_channelData.resize(totalChannelCount);
212 rebuildDisplayInfo();
222 QWriteLocker lk(&m_lock);
223 m_signalColor = color;
225 rebuildDisplayInfo();
233 QWriteLocker lk(&m_lock);
234 m_maxStoredSamples = (n > 0) ? n : 0;
249 QWriteLocker lk(&m_lock);
250 m_detrendMode = mode;
259 QWriteLocker lk(&m_lock);
260 if (channelIdx >= 0 && channelIdx < m_displayInfo.size())
261 m_displayInfo[channelIdx].bad = bad;
263 if (m_pFiffInfo && channelIdx >= 0 && channelIdx < m_pFiffInfo->nchan) {
264 const QString channelName = m_pFiffInfo->ch_names.value(channelIdx);
265 const int badIndex = m_pFiffInfo->bads.indexOf(channelName);
269 m_pFiffInfo->bads.append(channelName);
270 }
else if (badIndex >= 0) {
271 m_pFiffInfo->bads.removeAt(badIndex);
283 QReadLocker lk(&m_lock);
284 const int realChannelCount = m_pFiffInfo ? m_pFiffInfo->nchan : 0;
285 return qMax(m_channelData.size(), realChannelCount + m_virtualDisplayInfo.size());
292 QReadLocker lk(&m_lock);
293 return m_firstSample;
300 QReadLocker lk(&m_lock);
301 return m_channelData.isEmpty() ? 0 : m_channelData[0].size();
308 QReadLocker lk(&m_lock);
309 if (channelIdx >= 0 && channelIdx < m_displayInfo.size())
310 return m_displayInfo[channelIdx];
318 QReadLocker lk(&m_lock);
319 if (channelIdx < 0 || channelIdx >= m_channelData.size())
321 const QVector<float>& src = m_channelData[channelIdx];
322 int bufFirst = qBound(0, first - m_firstSample, src.size());
323 int bufLast = qBound(0, last - m_firstSample, src.size());
324 if (bufLast <= bufFirst)
327 constexpr int kMax = 1000;
328 if (bufLast - bufFirst > kMax)
329 bufFirst = bufLast - kMax;
331 for (
int i = bufFirst; i < bufLast; ++i)
332 sum +=
static_cast<double>(src[i]) * src[i];
333 return static_cast<float>(qSqrt(sum / (bufLast - bufFirst)));
340 QReadLocker lk(&m_lock);
341 if (channelIdx < 0 || channelIdx >= m_channelData.size())
343 const QVector<float>& src = m_channelData[channelIdx];
344 int bufIdx = sample - m_firstSample;
345 if (bufIdx < 0 || bufIdx >= src.size())
356 int& vboFirstSample)
const
358 QReadLocker lk(&m_lock);
362 if (channelIdx < 0 || channelIdx >= m_channelData.size() || pixelWidth <= 0 || firstSample >= lastSample) {
366 const QVector<float>& src = m_channelData[channelIdx];
369 int bufLast = lastSample - m_firstSample;
372 bufFirst = qBound(0, bufFirst, src.size());
373 bufLast = qBound(0, bufLast, src.size());
379 vboFirstSample = m_firstSample + bufFirst;
381 if (bufLast <= bufFirst)
384 int nSamples = bufLast - bufFirst;
387 float dcOffset = 0.f;
388 float linearSlope = 0.f;
389 float linearIntercept = 0.f;
395 for (
int i = bufFirst; i < bufLast; ++i)
397 dcOffset =
static_cast<float>(sum / nSamples);
398 }
else if (useLinear) {
400 double sumX = 0.0, sumY = 0.0, sumXX = 0.0, sumXY = 0.0;
401 for (
int i = 0; i < nSamples; ++i) {
402 double x =
static_cast<double>(i);
403 double y =
static_cast<double>(src[bufFirst + i]);
409 double denom = nSamples * sumXX - sumX * sumX;
410 if (qAbs(denom) > 1e-30) {
411 linearSlope =
static_cast<float>((nSamples * sumXY - sumX * sumY) / denom);
412 linearIntercept =
static_cast<float>((sumY - linearSlope * sumX) / nSamples);
416 if (nSamples <= pixelWidth * 2) {
418 QVector<float> result;
419 result.reserve(nSamples * 2);
420 for (
int i = 0; i < nSamples; ++i) {
421 float trend = useMean ? dcOffset
422 : useLinear ? (linearSlope * i + linearIntercept)
424 result.append(
static_cast<float>(i));
425 result.append(src[bufFirst + i] - trend);
435 QVector<float> result;
436 result.reserve(pixelWidth * 4);
438 float spp =
static_cast<float>(nSamples) / pixelWidth;
440 for (
int px = 0; px < pixelWidth; ++px) {
441 int sBegin = bufFirst +
static_cast<int>(px * spp);
442 int sEnd = bufFirst +
static_cast<int>((px + 1) * spp);
443 sEnd = qMin(sEnd, bufLast);
445 sBegin = qMax(sEnd - 1, bufFirst);
447 float minV = src[sBegin];
448 float maxV = src[sBegin];
449 for (
int s = sBegin + 1; s < sEnd; ++s) {
457 float tCenter =
static_cast<float>(sBegin - bufFirst) + (sEnd - sBegin) * 0.5f;
458 float trend = useMean ? dcOffset
459 : useLinear ? (linearSlope * tCenter + linearIntercept)
464 float xOffset = px * spp;
467 result.append(xOffset);
469 result.append(xOffset);
480void ChannelDataModel::rebuildDisplayInfo()
482 QWriteLocker lk(&m_lock);
483 const int realChannelCount = m_pFiffInfo ? m_pFiffInfo->nchan : 0;
484 const int nCh = qMax(m_channelData.size(), realChannelCount + m_virtualDisplayInfo.size());
485 m_displayInfo.resize(nCh);
486 for (
int ch = 0; ch < nCh; ++ch) {
487 if (ch >= realChannelCount && ch - realChannelCount < m_virtualDisplayInfo.size()) {
489 if (info.
name.isEmpty())
490 info.
name = QString(
"Virtual %1").arg(ch - realChannelCount + 1);
493 if (!info.
color.isValid())
494 info.
color = m_signalColor;
497 m_displayInfo[ch] = info;
498 m_displayInfo[ch].isVirtualChannel =
true;
502 m_displayInfo[ch].amplitudeMax = amplitudeMaxForChannel(ch);
503 m_displayInfo[ch].color = colorForChannel(ch);
504 if (m_pFiffInfo && ch < realChannelCount)
505 m_displayInfo[ch].name = m_pFiffInfo->ch_names[ch];
507 m_displayInfo[ch].name = QString(
"CH %1").arg(ch + 1);
508 m_displayInfo[ch].typeLabel = typeLabelForChannel(ch);
509 m_displayInfo[ch].bad = (m_pFiffInfo && ch < realChannelCount)
510 ? m_pFiffInfo->bads.contains(m_displayInfo[ch].name)
512 m_displayInfo[ch].isVirtualChannel =
false;
518float ChannelDataModel::amplitudeMaxForChannel(
int ch)
const
520 if (!m_pFiffInfo || ch >= m_pFiffInfo->nchan)
521 return kScaleFallback;
523 const auto& info = m_pFiffInfo->chs[ch];
524 qint32 kind = info.kind;
529 return m_scaleMap.value(kMEGGradKind,
532 return m_scaleMap.value(kMEGMagKind,
535 if (m_scaleMap.contains(kind))
536 return m_scaleMap.value(kind);
537 return kScaleFallback;
542QColor ChannelDataModel::colorForChannel(
int ch)
const
544 if (!m_pFiffInfo || ch >= m_pFiffInfo->nchan)
545 return m_signalColor;
548 switch (m_pFiffInfo->chs[ch].kind) {
550 return QColor(20, 90, 180);
552 return QColor(170, 55, 10);
554 return QColor(130, 0, 130);
556 return QColor(190, 15, 45);
558 return QColor(20, 110, 20);
560 return QColor(180, 100, 0);
562 return m_signalColor;
568QString ChannelDataModel::typeLabelForChannel(
int ch)
const
570 if (!m_pFiffInfo || ch >= m_pFiffInfo->nchan)
571 return QStringLiteral(
"MISC");
572 switch (m_pFiffInfo->chs[ch].kind) {
575 return QStringLiteral(
"MEG grad");
576 return QStringLiteral(
"MEG mag");
578 return QStringLiteral(
"EEG");
580 return QStringLiteral(
"EOG");
582 return QStringLiteral(
"ECG");
584 return QStringLiteral(
"EMG");
586 return QStringLiteral(
"STIM");
588 return QStringLiteral(
"MISC");
596 QReadLocker lk(&m_lock);
597 return m_pFiffInfo ?
static_cast<float>(m_pFiffInfo->sfreq) : 0.f;
Symbolic FIFF tag, block, value, unit and channel-type constants shared across FIFFLIB.
Full FIFF measurement metadata: everything from FIFFB_MEAS / FIFFB_MEAS_INFO needed to interpret a re...
Circular-buffer Qt model exposing a rolling window of the live FIFF stream as a table.
FIFF file I/O, in-memory data structures and high-level readers/writers.
2-D display widgets and visualisation helpers (charts, topography, colour maps).
DetrendMode
Channel display metadata (read-only from the renderer's perspective).
Channel display metadata (read-only from the renderer's perspective).
void appendData(const Eigen::MatrixXd &data)
void setDetrendMode(DetrendMode mode)
void setScaleMap(const QMap< qint32, float > &scaleMap)
float channelRms(int channelIdx, int firstSample, int lastSample) const
float sampleValueAt(int channelIdx, int sample) const
void setScaleMapFromStrings(const QMap< QString, double > &scaleMap)
void setVirtualChannels(const QVector< ChannelDisplayInfo > &virtualChannels)
void setChannelBad(int channelIdx, bool bad)
void setRemoveDC(bool remove)
void init(QSharedPointer< FIFFLIB::FiffInfo > pFiffInfo)
void setSignalColor(const QColor &color)
void setData(const Eigen::MatrixXd &data, int firstSample=0)
ChannelDisplayInfo channelInfo(int channelIdx) const
void setMaxStoredSamples(int n)
QVector< float > decimatedVertices(int channelIdx, int firstSample, int lastSample, int pixelWidth, int &vboFirstSample) const
ChannelDataModel(QObject *parent=nullptr)