v2.0.0
Loading...
Searching...
No Matches
rtfiffrawviewmodel.cpp
Go to the documentation of this file.
1//=============================================================================================================
15
16//=============================================================================================================
17// INCLUDES
18//=============================================================================================================
19
20#include "rtfiffrawviewmodel.h"
21
22#include <fiff/fiff_types.h>
23#include <fiff/fiff_info.h>
24
25#include <utils/ioutils.h>
26
27#include <dsp/sphara.h>
29
30//=============================================================================================================
31// QT INCLUDES
32//=============================================================================================================
33
34#include <QBrush>
35#include <QCoreApplication>
36#include <QtConcurrent>
37#include <QFuture>
38#include <QDebug>
39
40//=============================================================================================================
41// EIGEN INCLUDES
42//=============================================================================================================
43
44//=============================================================================================================
45// STL INCLUDES
46//=============================================================================================================
47
48#include <iostream>
49
50//=============================================================================================================
51// USED NAMESPACES
52//=============================================================================================================
53
54using namespace DISPLIB;
55using namespace UTILSLIB;
56using namespace FIFFLIB;
57using namespace Eigen;
58using namespace RTPROCESSINGLIB;
59
60//=============================================================================================================
61// DEFINE GLOBAL METHODS
62//=============================================================================================================
63
64namespace
65{
66
68template<typename Fn>
69void forWrappedSpan(int iStart, int iLength, int iSize, Fn fn)
70{
71 iStart = ((iStart % iSize) + iSize) % iSize;
72 for (int iDone = 0; iDone < iLength;) {
73 const int iPiece = std::min(iLength - iDone, iSize - iStart);
74 fn(iStart, iDone, iPiece);
75 iDone += iPiece;
76 iStart = 0;
77 }
78}
79
80} // namespace
81
82//=============================================================================================================
83// DEFINE MEMBER METHODS
84//=============================================================================================================
85
87: QAbstractTableModel(parent)
88, m_bProjActivated(false)
89, m_bCompActivated(false)
90, m_bSpharaActivated(false)
91, m_bIsFreezed(false)
92, m_bDrawFilterFront(true)
93, m_bPerformFiltering(false)
94, m_bTriggerDetectionActive(false)
95, m_fSps(1024.0f)
96, m_dTriggerThreshold(0.01)
97, m_iT(10)
98, m_iDownsampling(10)
99, m_iMaxSamples(1024)
100, m_iCurrentSample(0)
101, m_iCurrentStartingSample(0)
102, m_iCurrentSampleFreeze(0)
103, m_iMaxFilterLength(128)
104, m_iCurrentBlockSize(0)
105, m_iCurrentTriggerChIndex(0)
106, m_iDistanceTimerSpacer(1000)
107, m_iDetectedTriggers(0)
108, m_sFilterChannelType("MEG")
109, m_pFiffInfo(FiffInfo::SPtr::create())
110, m_colBackground(Qt::white)
111{
112}
113
114//=============================================================================================================
115
119
120//=============================================================================================================
121//virtual functions
122int RtFiffRawViewModel::rowCount(const QModelIndex& /*parent*/) const
123{
124 if (!m_pFiffInfo->chs.isEmpty()) {
125 return m_pFiffInfo->chs.size();
126 } else {
127 return 0;
128 }
129}
130
131//=============================================================================================================
132
133int RtFiffRawViewModel::columnCount(const QModelIndex& /*parent*/) const
134{
135 return 3;
136}
137
138//=============================================================================================================
139
140QVariant RtFiffRawViewModel::data(const QModelIndex& index, int role) const
141{
142 if (role != Qt::DisplayRole && role != Qt::BackgroundRole) {
143 return QVariant();
144 }
145
146 if (role == Qt::BackgroundRole) {
147 return QVariant(QBrush(m_colBackground));
148 }
149
150 if (index.isValid()) {
151 qint32 row = m_qMapIdxRowSelection.value(index.row(), 0);
152
153 //******** first column (chname) ********
154 if (index.column() == 0 && role == Qt::DisplayRole)
155 return QVariant(m_pFiffInfo->ch_names[row]);
156
157 //******** second column (data plot) ********
158 if (index.column() == 1) {
159 QVariant v;
160 RowVectorPair rowVectorPair;
161
162 switch (role) {
163 case Qt::DisplayRole: {
164 if (m_bIsFreezed) {
165 // data freeze
166 if (!m_filterKernel.isEmpty() && m_bPerformFiltering) {
167 rowVectorPair.first = m_matDataFilteredFreeze.data() + row * m_matDataFilteredFreeze.cols();
168 rowVectorPair.second = m_matDataFilteredFreeze.cols();
169 v.setValue(rowVectorPair);
170 } else {
171 rowVectorPair.first = m_matDataRawFreeze.data() + row * m_matDataRawFreeze.cols();
172 rowVectorPair.second = m_matDataRawFreeze.cols();
173 v.setValue(rowVectorPair);
174 }
175 } else {
176 // data stream
177 if (!m_filterKernel.isEmpty() && m_bPerformFiltering) {
178 rowVectorPair.first = m_matDataFiltered.data() + row * m_matDataFiltered.cols();
179 rowVectorPair.second = m_matDataFiltered.cols();
180 v.setValue(rowVectorPair);
181 } else {
182 rowVectorPair.first = m_matDataRaw.data() + row * m_matDataRaw.cols();
183 rowVectorPair.second = m_matDataRaw.cols();
184 v.setValue(rowVectorPair);
185 }
186 }
187
188 return v;
189 }
190 } // end role switch
191 } // end column check
192
193 //******** third column (bad channel) ********
194 if (index.column() == 2 && role == Qt::DisplayRole) {
195 return QVariant(m_pFiffInfo->bads.contains(m_pFiffInfo->ch_names[row]));
196 } // end column check
197
198 } // end index.valid() check
199
200 return QVariant();
201}
202
203//=============================================================================================================
204
205QVariant RtFiffRawViewModel::headerData(int section, Qt::Orientation orientation, int role) const
206{
207 if (role != Qt::DisplayRole && role != Qt::TextAlignmentRole)
208 return QVariant();
209
210 if (orientation == Qt::Horizontal) {
211 switch (section) {
212 case 0: //chname column
213 return QVariant();
214 case 1: //data plot column
215 switch (role) {
216 case Qt::DisplayRole:
217 return QVariant("data plot");
218 case Qt::TextAlignmentRole:
219 return QVariant(Qt::AlignLeft);
220 }
221 return QVariant("data plot");
222 }
223 } else if (orientation == Qt::Vertical) {
224 QModelIndex chname = createIndex(section, 0);
225 switch (role) {
226 case Qt::DisplayRole:
227 return QVariant(data(chname).toString());
228 }
229 }
230
231 return QVariant();
232}
233
234//=============================================================================================================
235
236void RtFiffRawViewModel::initSphara()
237{
238 //Load SPHARA matrices for babymeg and vectorview
239 IOUtils::read_eigen_matrix(m_matSpharaVVGradLoaded, QCoreApplication::applicationDirPath() + QString("/../resources/mne_scan/plugins/noisereduction/SPHARA/Vectorview_SPHARA_InvEuclidean_Grad.txt"));
240 IOUtils::read_eigen_matrix(m_matSpharaVVMagLoaded, QCoreApplication::applicationDirPath() + QString("/../resources/mne_scan/plugins/noisereduction/SPHARA/Vectorview_SPHARA_InvEuclidean_Mag.txt"));
241
242 IOUtils::read_eigen_matrix(m_matSpharaBabyMEGInnerLoaded, QCoreApplication::applicationDirPath() + QString("/../resources/mne_scan/plugins/noisereduction/SPHARA/BabyMEG_SPHARA_InvEuclidean_Inner.txt"));
243 IOUtils::read_eigen_matrix(m_matSpharaBabyMEGOuterLoaded, QCoreApplication::applicationDirPath() + QString("/../resources/mne_scan/plugins/noisereduction/SPHARA/BabyMEG_SPHARA_InvEuclidean_Outer.txt"));
244
245 IOUtils::read_eigen_matrix(m_matSpharaEEGLoaded, QCoreApplication::applicationDirPath() + QString("/../resources/mne_scan/plugins/noisereduction/SPHARA/Current_SPHARA_EEG.txt"));
246
247 //Generate indices used to create the SPHARA operators for VectorView
248 m_vecIndicesFirstVV.resize(0);
249 m_vecIndicesSecondVV.resize(0);
250
251 for (int r = 0; r < m_pFiffInfo->chs.size(); ++r) {
252 //Find GRADIOMETERS
253 if (m_pFiffInfo->chs.at(r).chpos.coil_type == 3012) {
254 m_vecIndicesFirstVV.conservativeResize(m_vecIndicesFirstVV.rows() + 1);
255 m_vecIndicesFirstVV(m_vecIndicesFirstVV.rows() - 1) = r;
256 }
257
258 //Find Magnetometers
259 if (m_pFiffInfo->chs.at(r).chpos.coil_type == 3024) {
260 m_vecIndicesSecondVV.conservativeResize(m_vecIndicesSecondVV.rows() + 1);
261 m_vecIndicesSecondVV(m_vecIndicesSecondVV.rows() - 1) = r;
262 }
263 }
264
265 //Generate indices used to create the SPHARA operators for babyMEG
266 m_vecIndicesFirstBabyMEG.resize(0);
267 for (int r = 0; r < m_pFiffInfo->chs.size(); ++r) {
268 //Find INNER LAYER
269 if (m_pFiffInfo->chs.at(r).chpos.coil_type == 7002) {
270 m_vecIndicesFirstBabyMEG.conservativeResize(m_vecIndicesFirstBabyMEG.rows() + 1);
271 m_vecIndicesFirstBabyMEG(m_vecIndicesFirstBabyMEG.rows() - 1) = r;
272 }
273
274 //Find outer layer (FIFFV_COIL_BABY_REF_MAG)
275 if (m_pFiffInfo->chs.at(r).chpos.coil_type == 7003) {
276 m_vecIndicesSecondBabyMEG.conservativeResize(m_vecIndicesSecondBabyMEG.rows() + 1);
277 m_vecIndicesSecondBabyMEG(m_vecIndicesSecondBabyMEG.rows() - 1) = r;
278 }
279 }
280
281 //Generate indices used to create the SPHARA operators for EEG layouts
282 m_vecIndicesFirstEEG.resize(0);
283 for (int r = 0; r < m_pFiffInfo->chs.size(); ++r) {
284 //Find EEG
285 if (m_pFiffInfo->chs.at(r).kind == FIFFV_EEG_CH) {
286 m_vecIndicesFirstEEG.conservativeResize(m_vecIndicesFirstEEG.rows() + 1);
287 m_vecIndicesFirstEEG(m_vecIndicesFirstEEG.rows() - 1) = r;
288 }
289 }
290
291 //Create Sphara operator for the first time
292 updateSpharaOptions("BabyMEG", 270, 105);
293
294 qDebug() << "RtFiffRawViewModel::initSphara - Read VectorView mag matrix " << m_matSpharaVVMagLoaded.rows() << m_matSpharaVVMagLoaded.cols() << "and grad matrix" << m_matSpharaVVGradLoaded.rows() << m_matSpharaVVGradLoaded.cols();
295 qDebug() << "RtFiffRawViewModel::initSphara - Read BabyMEG inner layer matrix " << m_matSpharaBabyMEGInnerLoaded.rows() << m_matSpharaBabyMEGInnerLoaded.cols() << "and outer layer matrix" << m_matSpharaBabyMEGOuterLoaded.rows() << m_matSpharaBabyMEGOuterLoaded.cols();
296}
297
298//=============================================================================================================
299
300void RtFiffRawViewModel::setFiffInfo(QSharedPointer<FIFFLIB::FiffInfo>& p_pFiffInfo)
301{
302 if (p_pFiffInfo) {
303 RowVectorXi sel; // = RowVectorXi(0,0);
304 QStringList emptyExclude;
305
306 if (p_pFiffInfo->bads.size() > 0) {
307 sel = FiffInfoBase::pick_channels(p_pFiffInfo->ch_names, p_pFiffInfo->bads, emptyExclude);
308 }
309
310 m_vecBadIdcs = sel;
311
312 m_pFiffInfo = p_pFiffInfo;
313
315
316 //Resize data matrix without touching the stored values
317 m_matDataRaw.conservativeResize(m_pFiffInfo->chs.size(), m_iMaxSamples);
318 m_matDataRaw.setZero();
319
320 m_matDataFiltered.conservativeResize(m_pFiffInfo->chs.size(), m_iMaxSamples);
321 m_matDataFiltered.setZero();
322
323 m_vecLastBlockFirstValuesFiltered.conservativeResize(m_pFiffInfo->chs.size());
324 m_vecLastBlockFirstValuesFiltered.setZero();
325
326 m_vecLastBlockFirstValuesRaw.conservativeResize(m_pFiffInfo->chs.size());
327 m_vecLastBlockFirstValuesRaw.setZero();
328
329 m_matOverlap.conservativeResize(m_pFiffInfo->chs.size(), m_iMaxFilterLength);
330
331 m_matSparseProjMult = SparseMatrix<double>(m_pFiffInfo->chs.size(), m_pFiffInfo->chs.size());
332 m_matSparseCompMult = SparseMatrix<double>(m_pFiffInfo->chs.size(), m_pFiffInfo->chs.size());
333 m_matSparseSpharaMult = SparseMatrix<double>(m_pFiffInfo->chs.size(), m_pFiffInfo->chs.size());
334 m_matSparseProjCompMult = SparseMatrix<double>(m_pFiffInfo->chs.size(), m_pFiffInfo->chs.size());
335
336 m_matSparseProjMult.setIdentity();
337 m_matSparseCompMult.setIdentity();
338 m_matSparseSpharaMult.setIdentity();
339 m_matSparseProjCompMult.setIdentity();
340
341 //Create the initial Compensator projector
343
344 setFilterChannelType(m_sFilterChannelType);
345
346 //Init the sphara operators
347 initSphara();
348 } else {
349 m_vecBadIdcs = RowVectorXi(0, 0);
350 m_matProj = MatrixXd(0, 0);
351 m_matComp = MatrixXd(0, 0);
352 }
353}
354
355//=============================================================================================================
356
357void RtFiffRawViewModel::setSamplingInfo(float sps, int T, bool bSetZero)
358{
359 beginResetModel();
360
361 m_iT = T;
362
363 m_iMaxSamples = static_cast<qint32>(ceil(static_cast<double>(sps) * T));
364
365 //Resize data matrix without touching the stored values
366 m_matDataRaw.conservativeResize(m_pFiffInfo->chs.size(), m_iMaxSamples);
367 m_matDataFiltered.conservativeResize(m_pFiffInfo->chs.size(), m_iMaxSamples);
368 m_vecLastBlockFirstValuesRaw.conservativeResize(m_pFiffInfo->chs.size());
369 m_vecLastBlockFirstValuesFiltered.conservativeResize(m_pFiffInfo->chs.size());
370
371 if (bSetZero) {
372 m_matDataRaw.setZero();
373 m_matDataFiltered.setZero();
374 m_vecLastBlockFirstValuesRaw.setZero();
375 m_vecLastBlockFirstValuesFiltered.setZero();
376 }
377
378 if (m_iCurrentSample > m_iMaxSamples) {
379 m_iCurrentStartingSample += m_iCurrentSample;
380 m_iCurrentSample = 0;
381 }
382
383 endResetModel();
384}
385
386//=============================================================================================================
387
389{
390 const MatrixXdR& matData = (!m_filterKernel.isEmpty() && m_bPerformFiltering) ? m_matDataFiltered : m_matDataRaw;
391
392 MatrixXd matBlock(matData.rows(), m_iCurrentBlockSize);
393 if (m_iCurrentBlockSize > 0) {
394 forWrappedSpan(m_iCurrentSample - m_iCurrentBlockSize, m_iCurrentBlockSize, static_cast<int>(matData.cols()), [&](int iCol, int iOffset, int iLength) {
395 matBlock.middleCols(iOffset, iLength) = matData.middleCols(iCol, iLength);
396 });
397 }
398
399 return matBlock;
400}
401
402//=============================================================================================================
403
404void RtFiffRawViewModel::addData(const QList<MatrixXd>& data)
405{
406 //SSP
407 bool doProj = m_bProjActivated && m_matDataRaw.cols() > 0 && m_matDataRaw.rows() == m_matProj.cols() ? true : false;
408
409 //Compensator
410 bool doComp = m_bCompActivated && m_matDataRaw.cols() > 0 && m_matDataRaw.rows() == m_matComp.cols() ? true : false;
411
412 //SPHARA
413 bool doSphara = m_bSpharaActivated && m_matSparseSpharaMult.cols() > 0 && m_matDataRaw.rows() == m_matSparseSpharaMult.cols() ? true : false;
414
415 const int iMaxCols = static_cast<int>(m_matDataRaw.cols());
416 if (iMaxCols == 0) {
417 return;
418 }
419
420 //Copy new data into the ring buffer: stream sample s goes to column s mod iMaxCols
421 for (qint32 b = 0; b < data.size(); ++b) {
422 int nRow = data.at(b).rows();
423
424 if (nRow != m_matDataRaw.rows()) {
425 qDebug() << "incoming data does not match internal data row size. Returning...";
426 return;
427 }
428
429 const MatrixXd& matBlock = data.at(b);
430 const int nCol = static_cast<int>(matBlock.cols());
431
432 MatrixXd matCorrected;
433 if (doComp) {
434 matCorrected = doProj ? MatrixXd(m_matSparseProjCompMult * matBlock) : MatrixXd(m_matSparseCompMult * matBlock);
435 } else {
436 matCorrected = doProj ? MatrixXd(m_matSparseProjMult * matBlock) : matBlock;
437 }
438
439 QList<QPair<int, double>> lTriggers;
440 if (m_bTriggerDetectionActive) {
441 lTriggers = RTPROCESSINGLIB::detectTriggerFlanksMax(matBlock, m_iCurrentTriggerChIndex, 0, m_dTriggerThreshold, true, 500);
442 }
443
444 forWrappedSpan(m_iCurrentSample, nCol, iMaxCols, [&](int iCol, int iOffset, int iLength) {
445 if (iCol == 0 && m_iCurrentSample == iMaxCols) {
446 startNewSweep();
447 }
448
449 m_matDataRaw.middleCols(iCol, iLength) = matCorrected.middleCols(iOffset, iLength);
450 if (m_filterKernel.isEmpty() || !m_bPerformFiltering) {
451 m_matDataFiltered.middleCols(iCol, iLength).setZero();
452 if (doSphara) {
453 m_matDataRaw.middleCols(iCol, iLength) = m_matSparseSpharaMult * m_matDataRaw.middleCols(iCol, iLength);
454 }
455 }
456
457 for (const QPair<int, double>& trigger : std::as_const(lTriggers)) {
458 if (trigger.first >= iOffset && trigger.first < iOffset + iLength) {
459 m_qMapDetectedTrigger[m_iCurrentTriggerChIndex].append({iCol + trigger.first - iOffset, trigger.second});
460 }
461 }
462
463 m_iCurrentSample = iCol + iLength;
464 });
465
466 if (!lTriggers.isEmpty()) {
467 m_iDetectedTriggers += lTriggers.size();
468 emit triggerDetected(m_iDetectedTriggers, m_qMapDetectedTrigger);
469 }
470
471 //Filter the whole block at once so the overlap-add stays continuous across the wrap
472 if (!m_filterKernel.isEmpty() && m_bPerformFiltering) {
473 const int iBlockStart = m_iCurrentSample - nCol;
474 filterDataBlock(matCorrected, iBlockStart);
475
476 //Perform SPHARA on filtered data after actual filtering - SPHARA should be applied on the best possible data
477 if (doSphara) {
478 forWrappedSpan(iBlockStart - m_iMaxFilterLength / 2, nCol, iMaxCols, [&](int iCol, int, int iLength) {
479 m_matDataFiltered.middleCols(iCol, iLength) = m_matSparseSpharaMult * m_matDataFiltered.middleCols(iCol, iLength);
480 });
481 }
482 }
483
484 m_iCurrentBlockSize = std::min(nCol, iMaxCols);
485 }
486
487 //Update data content
488 QModelIndex topLeft = this->index(0, 1);
489 QModelIndex bottomRight = this->index(m_pFiffInfo->ch_names.size() - 1, 1);
490 QVector<int> roles;
491 roles << Qt::DisplayRole;
492
493 emit dataChanged(topLeft, bottomRight, roles);
494}
495
496//=============================================================================================================
497
498void RtFiffRawViewModel::startNewSweep()
499{
500 m_iCurrentStartingSample += m_iCurrentSample;
501 m_iCurrentSample = 0;
502
503 if (!m_bIsFreezed) {
504 m_vecLastBlockFirstValuesFiltered = m_matDataFiltered.col(0);
505 m_vecLastBlockFirstValuesRaw = m_matDataRaw.col(0);
506 }
507
508 //Store old detected triggers
509 m_qMapDetectedTriggerOld = m_qMapDetectedTrigger;
510
511 //Clear detected triggers
512 if (m_bTriggerDetectionActive) {
513 QMutableMapIterator<int, QList<QPair<int, double>>> i(m_qMapDetectedTrigger);
514 while (i.hasNext()) {
515 i.next();
516 i.value().clear();
517 }
518 }
519}
520
521//=============================================================================================================
522
524{
525 if (row < m_qMapIdxRowSelection.size()) {
526 qint32 chRow = m_qMapIdxRowSelection[row];
527 return m_pFiffInfo->chs.at(chRow).kind;
528 }
529
530 return 0;
531}
532
533//=============================================================================================================
534
536{
537 if (row < m_qMapIdxRowSelection.size()) {
538 qint32 chRow = m_qMapIdxRowSelection[row];
539 return m_pFiffInfo->chs.at(chRow).unit;
540 }
541
542 return FIFF_UNIT_NONE;
543}
544
545//=============================================================================================================
546
548{
549 if (row < m_qMapIdxRowSelection.size()) {
550 qint32 chRow = m_qMapIdxRowSelection[row];
551 return m_pFiffInfo->chs.at(chRow).chpos.coil_type;
552 }
553
554 return FIFFV_COIL_NONE;
555}
556
557//=============================================================================================================
558
559void RtFiffRawViewModel::selectRows(const QList<qint32>& selection)
560{
561 beginResetModel();
562
563 m_qMapIdxRowSelection.clear();
564
565 qint32 count = 0;
566 for (qint32 i = 0; i < selection.size(); ++i) {
567 if (selection[i] < m_pFiffInfo->chs.size()) {
568 m_qMapIdxRowSelection.insert(count, selection[i]);
569 ++count;
570 }
571 }
572
573 emit newSelection(selection);
574
575 endResetModel();
576}
577
578//=============================================================================================================
579
580void RtFiffRawViewModel::hideRows(const QList<qint32>& selection)
581{
582 beginResetModel();
583
584 for (qint32 i = 0; i < selection.size(); ++i) {
585 if (m_qMapIdxRowSelection.contains(selection.at(i))) {
586 m_qMapIdxRowSelection.remove(selection.at(i));
587 }
588 }
589
590 emit newSelection(selection);
591
592 endResetModel();
593}
594
595//=============================================================================================================
596
598{
599 beginResetModel();
600
601 m_qMapIdxRowSelection.clear();
602
603 for (qint32 i = 0; i < m_pFiffInfo->chs.size(); ++i) {
604 m_qMapIdxRowSelection.insert(i, i);
605 }
606
607 endResetModel();
608}
609
610//=============================================================================================================
611
612void RtFiffRawViewModel::toggleFreeze(const QModelIndex&)
613{
614 m_bIsFreezed = !m_bIsFreezed;
615
616 if (m_bIsFreezed) {
617 m_matDataRawFreeze = m_matDataRaw;
618 m_matDataFilteredFreeze = m_matDataFiltered;
619 m_qMapDetectedTriggerFreeze = m_qMapDetectedTrigger;
620 m_qMapDetectedTriggerOldFreeze = m_qMapDetectedTriggerOld;
621
622 m_iCurrentSampleFreeze = m_iCurrentSample;
623 }
624
625 //Update data content
626 QModelIndex topLeft = this->index(0, 1);
627 QModelIndex bottomRight = this->index(m_pFiffInfo->chs.size() - 1, 1);
628 QVector<int> roles;
629 roles << Qt::DisplayRole;
630
631 emit dataChanged(topLeft, bottomRight, roles);
632}
633
634//=============================================================================================================
635
636void RtFiffRawViewModel::setScaling(const QMap<qint32, float>& p_qMapChScaling)
637{
638 beginResetModel();
639 m_qMapChScaling = p_qMapChScaling;
640 endResetModel();
641}
642
643//=============================================================================================================
644
645void RtFiffRawViewModel::updateProjection(const QList<FIFFLIB::FiffProj>& projs)
646{
647 // Update the SSP projector
648 if (m_pFiffInfo) {
649 //If a minimum of one projector is active set m_bProjActivated to true so that this model applies the ssp to the incoming data
650 m_bProjActivated = false;
651 m_pFiffInfo->projs = projs;
652
653 for (qint32 i = 0; i < this->m_pFiffInfo->projs.size(); ++i) {
654 if (this->m_pFiffInfo->projs[i].active) {
655 m_bProjActivated = true;
656 break;
657 }
658 }
659
660 this->m_pFiffInfo->make_projector(m_matProj);
661
662 qDebug() << "RtFiffRawViewModel::updateProjection - New projection calculated.";
663
664 //set columns of matrix to zero depending on bad channels indexes
665 for (qint32 j = 0; j < m_vecBadIdcs.cols(); ++j) {
666 m_matProj.col(m_vecBadIdcs[j]).setZero();
667 }
668
669 m_matSparseProjMult = m_matProj.sparseView();
670
671 //Create full multiplication matrix
672 m_matSparseProjCompMult = m_matSparseProjMult * m_matSparseCompMult;
673 }
674}
675
676//=============================================================================================================
677
679{
680 // Update the compensator
681 if (m_pFiffInfo) {
682 if (to == 0) {
683 m_bCompActivated = false;
684 } else {
685 m_bCompActivated = true;
686 }
687
688 // qDebug()<<"to"<<to;
689 // qDebug()<<"from"<<from;
690 // qDebug()<<"m_bCompActivated"<<m_bCompActivated;
691
692 FiffCtfComp newComp;
693 this->m_pFiffInfo->make_compensator(0, to, newComp); //Do this always from 0 since we always read new raw data, we never actually perform a multiplication on already existing data
694
695 //We do not need to call this->m_pFiffInfo->set_current_comp(to);
696 //Because we will set the compensators to the coil in the same FiffInfo which is already used to write to file.
697 //Note that the data is written in raw form not in compensated form.
698 m_matComp = newComp.data->data;
699
700 m_matSparseCompMult = m_matComp.sparseView();
701
702 //Create full multiplication matrix
703 m_matSparseProjCompMult = m_matSparseProjMult * m_matSparseCompMult;
704 }
705}
706
707//=============================================================================================================
708
710{
711 m_bSpharaActivated = state;
712}
713
714//=============================================================================================================
715
716void RtFiffRawViewModel::updateSpharaOptions(const QString& sSytemType, int nBaseFctsFirst, int nBaseFctsSecond)
717{
718 if (m_pFiffInfo) {
719 qDebug() << "RtFiffRawViewModel::updateSpharaOptions - Creating SPHARA operator for" << sSytemType;
720
721 MatrixXd matSpharaMultFirst = MatrixXd::Identity(m_pFiffInfo->chs.size(), m_pFiffInfo->chs.size());
722 MatrixXd matSpharaMultSecond = MatrixXd::Identity(m_pFiffInfo->chs.size(), m_pFiffInfo->chs.size());
723
724 if (sSytemType == "VectorView" && m_matSpharaVVGradLoaded.size() != 0 && m_matSpharaVVMagLoaded.size() != 0) {
725 matSpharaMultFirst = UTILSLIB::makeSpharaProjector(m_matSpharaVVGradLoaded, m_vecIndicesFirstVV, m_pFiffInfo->nchan, nBaseFctsFirst, 1); //GRADIOMETERS
726 matSpharaMultSecond = UTILSLIB::makeSpharaProjector(m_matSpharaVVMagLoaded, m_vecIndicesSecondVV, m_pFiffInfo->nchan, nBaseFctsSecond, 0); //Magnetometers
727 }
728
729 if (sSytemType == "BabyMEG" && m_matSpharaBabyMEGInnerLoaded.size() != 0) {
730 matSpharaMultFirst = UTILSLIB::makeSpharaProjector(m_matSpharaBabyMEGInnerLoaded, m_vecIndicesFirstBabyMEG, m_pFiffInfo->nchan, nBaseFctsFirst, 0); //InnerLayer
731 }
732
733 if (sSytemType == "EEG" && m_matSpharaEEGLoaded.size() != 0) {
734 matSpharaMultFirst = UTILSLIB::makeSpharaProjector(m_matSpharaEEGLoaded, m_vecIndicesFirstEEG, m_pFiffInfo->nchan, nBaseFctsFirst, 0); //InnerLayer
735 }
736
737
738 //Create full multiplication matrix
739 const SparseMatrix<double> matSparseSpharaMultFirst = matSpharaMultFirst.sparseView();
740 const SparseMatrix<double> matSparseSpharaMultSecond = matSpharaMultSecond.sparseView();
741 m_matSparseSpharaMult = matSparseSpharaMultFirst * matSparseSpharaMultSecond;
742 }
743}
744
745//=============================================================================================================
746
748{
749 m_filterKernel = filterData;
750
751 m_iMaxFilterLength = 1;
752 for (int i = 0; i < filterData.size(); ++i) {
753 if (m_iMaxFilterLength < filterData.at(i).getFilterOrder()) {
754 m_iMaxFilterLength = filterData.at(i).getFilterOrder();
755 }
756 }
757
758 m_matOverlap.conservativeResize(m_pFiffInfo->chs.size(), m_iMaxFilterLength);
759 m_matOverlap.setZero();
760
761 m_bDrawFilterFront = false;
762}
763
764//=============================================================================================================
765
767{
768 m_bPerformFiltering = state;
769}
770
771//=============================================================================================================
772
774{
775 m_colBackground = color;
776}
777
778//=============================================================================================================
779
780void RtFiffRawViewModel::setFilterChannelType(const QString& channelType)
781{
782 m_sFilterChannelType = channelType;
783 m_filterChannelList.clear();
784
785 for (const FiffChInfo& ch : std::as_const(m_pFiffInfo->chs)) {
786 if (ch.kind == FIFFV_MEG_CH || ch.kind == FIFFV_EEG_CH || ch.kind == FIFFV_EOG_CH ||
787 ch.kind == FIFFV_ECG_CH || ch.kind == FIFFV_EMG_CH) {
788 if (m_sFilterChannelType == "All" || ch.ch_name.contains(m_sFilterChannelType)) {
789 m_filterChannelList << ch.ch_name;
790 }
791 }
792 }
793}
794
795//=============================================================================================================
796
797void RtFiffRawViewModel::markChBad(QModelIndex ch, bool status)
798{
799 QList<FiffChInfo> chInfolist = m_pFiffInfo->chs;
800
801 if (status) {
802 if (!m_pFiffInfo->bads.contains(chInfolist[ch.row()].ch_name))
803 m_pFiffInfo->bads.append(chInfolist[ch.row()].ch_name);
804 qDebug() << "RawModel:" << chInfolist[ch.row()].ch_name << "marked as bad.";
805 } else if (m_pFiffInfo->bads.contains(chInfolist[ch.row()].ch_name)) {
806 int index = m_pFiffInfo->bads.indexOf(chInfolist[ch.row()].ch_name);
807 m_pFiffInfo->bads.removeAt(index);
808 qDebug() << "RawModel:" << chInfolist[ch.row()].ch_name << "marked as good.";
809 }
810
811 //Update indeices of bad channels (this vector is needed when creating new ssp operators)
812 QStringList emptyExclude;
813 m_vecBadIdcs = FiffInfoBase::pick_channels(m_pFiffInfo->ch_names, m_pFiffInfo->bads, emptyExclude);
814
815 emit dataChanged(ch, ch);
816}
817
818//=============================================================================================================
819
820void RtFiffRawViewModel::triggerInfoChanged(const QMap<double, QColor>& colorMap, bool active, QString triggerCh, double threshold)
821{
822 m_qMapTriggerColor = colorMap;
823 m_bTriggerDetectionActive = active;
824 m_dTriggerThreshold = threshold;
825
826 //Find channel index and initialise detected trigger map if channel name changed
827 if (m_sCurrentTriggerCh != triggerCh) {
828 m_sCurrentTriggerCh = triggerCh;
829
830 QList<QPair<int, double>> temp;
831 m_qMapDetectedTrigger.clear();
832
833 for (int i = 0; i < m_pFiffInfo->chs.size(); ++i) {
834 if (m_pFiffInfo->chs[i].ch_name == m_sCurrentTriggerCh) {
835 m_iCurrentTriggerChIndex = i;
836 m_qMapDetectedTrigger.insert(i, temp);
837 break;
838 }
839 }
840 }
841
842 m_sCurrentTriggerCh = triggerCh;
843}
844
845//=============================================================================================================
846
848{
849 if (value <= 0) {
850 m_iDistanceTimerSpacer = 1000;
851 } else {
852 m_iDistanceTimerSpacer = value;
853 }
854}
855
856//=============================================================================================================
857
859{
860 m_iDetectedTriggers = 0;
861}
862
863//=============================================================================================================
864
865void RtFiffRawViewModel::markChBad(QModelIndexList chlist, bool status)
866{
867 QList<FiffChInfo> chInfolist = m_pFiffInfo->chs;
868
869 for (int i = 0; i < chlist.size(); ++i) {
870 if (status) {
871 if (!m_pFiffInfo->bads.contains(chInfolist[chlist[i].row()].ch_name))
872 m_pFiffInfo->bads.append(chInfolist[chlist[i].row()].ch_name);
873 } else {
874 if (m_pFiffInfo->bads.contains(chInfolist[chlist[i].row()].ch_name)) {
875 int index = m_pFiffInfo->bads.indexOf(chInfolist[chlist[i].row()].ch_name);
876 m_pFiffInfo->bads.removeAt(index);
877 }
878 }
879
880 emit dataChanged(chlist[i], chlist[i]);
881 }
882
883 //Update indeices of bad channels (this vector is needed when creating new ssp operators)
884 QStringList emptyExclude;
885 m_vecBadIdcs = FiffInfoBase::pick_channels(m_pFiffInfo->ch_names, m_pFiffInfo->bads, emptyExclude);
886}
887
888//=============================================================================================================
889
890void RtFiffRawViewModel::doFilterPerChannelRTMSA(QPair<QList<FilterKernel>, QPair<int, RowVectorXd>>& channelDataTime)
891{
892 for (int i = 0; i < channelDataTime.first.size(); ++i) {
893 channelDataTime.first[i].applyFftFilter(channelDataTime.second.second, true); //FFT Convolution for rt is not suitable. FFT make the signal filtering non causal.
894 }
895}
896
897//=============================================================================================================
898
899void RtFiffRawViewModel::filterDataBlock(const MatrixXd& data, int iDataIndex)
900{
901 if (data.cols() < m_iMaxFilterLength) {
902 return;
903 }
904
905 //Generate QList structure which can be handled by the QConcurrent framework
906 QList<QPair<QList<FilterKernel>, QPair<int, RowVectorXd>>> timeData;
907 QList<int> notFilterChannelIndex;
908
909 for (qint32 i = 0; i < data.rows(); ++i) {
910 if (m_filterChannelList.contains(m_pFiffInfo->chs.at(i).ch_name)) {
911 timeData.append(QPair<QList<FilterKernel>, QPair<int, RowVectorXd>>(m_filterKernel, QPair<int, RowVectorXd>(i, data.row(i))));
912 } else {
913 notFilterChannelIndex.append(i);
914 }
915 }
916
917 const int iMaxCols = static_cast<int>(m_matDataFiltered.cols());
918 const int nCol = static_cast<int>(data.cols());
919 const int iFilterDelay = m_iMaxFilterLength / 2;
920
921 //Do the concurrent filtering
922 if (!timeData.isEmpty()) {
923 QFuture<void> future = QtConcurrent::map(timeData,
924 doFilterPerChannelRTMSA);
925
926 future.waitForFinished();
927
928 //Overlap-add: output sample k of this block is convolution sample iDataIndex + k, drawn iFilterDelay earlier
929 for (int r = 0; r < timeData.size(); ++r) {
930 const int iRow = timeData.at(r).second.first;
931 RowVectorXd tempData = timeData.at(r).second.second;
932
933 if (m_bDrawFilterFront) {
934 tempData.head(m_iMaxFilterLength) += m_matOverlap.row(iRow);
935 forWrappedSpan(iDataIndex - iFilterDelay, nCol, iMaxCols, [&](int iCol, int iOffset, int iLength) {
936 m_matDataFiltered.row(iRow).segment(iCol, iLength) = tempData.segment(iOffset, iLength);
937 });
938 } else {
939 //The overlap belongs to the previous filter, so skip the start-up transient of the new one
940 forWrappedSpan(iDataIndex - iFilterDelay, m_iMaxFilterLength, iMaxCols, [&](int iCol, int, int iLength) {
941 m_matDataFiltered.row(iRow).segment(iCol, iLength).setZero();
942 });
943 forWrappedSpan(iDataIndex - iFilterDelay + m_iMaxFilterLength, nCol - m_iMaxFilterLength, iMaxCols, [&](int iCol, int iOffset, int iLength) {
944 m_matDataFiltered.row(iRow).segment(iCol, iLength) = tempData.segment(m_iMaxFilterLength + iOffset, iLength);
945 });
946 }
947
948 m_matOverlap.row(iRow) = tempData.tail(m_iMaxFilterLength);
949 }
950 }
951
952 m_bDrawFilterFront = true;
953
954 //Fill filtered data with raw data if the channel was not filtered
955 forWrappedSpan(iDataIndex, nCol, iMaxCols, [&](int iCol, int iOffset, int iLength) {
956 for (int i : std::as_const(notFilterChannelIndex)) {
957 m_matDataFiltered.row(i).segment(iCol, iLength) = data.row(i).segment(iOffset, iLength);
958 }
959 });
960}
961
962//=============================================================================================================
963
964void RtFiffRawViewModel::clearModel()
965{
966 beginResetModel();
967
968 m_matDataRaw.setZero();
969 m_matDataFiltered.setZero();
970 m_matDataRawFreeze.setZero();
971 m_matDataFilteredFreeze.setZero();
972 m_vecLastBlockFirstValuesFiltered.setZero();
973 m_vecLastBlockFirstValuesRaw.setZero();
974 m_matOverlap.setZero();
975
976 endResetModel();
977}
978
979//=============================================================================================================
980
982{
983 double dMaxValue;
984 qint32 kind = getKind(row);
985
986 switch (kind) {
987 case FIFFV_MEG_CH: {
988 dMaxValue = 1e-11f;
989 qint32 unit = getUnit(row);
990 if (unit == FIFF_UNIT_T_M) { //gradiometers
991 dMaxValue = 1e-10f;
992 if (getScaling().contains(FIFF_UNIT_T_M))
993 dMaxValue = getScaling()[FIFF_UNIT_T_M];
994 } else if (unit == FIFF_UNIT_T) //magnetometers
995 {
996 dMaxValue = 1e-11f;
997 if (getScaling().contains(FIFF_UNIT_T))
998 dMaxValue = getScaling()[FIFF_UNIT_T];
999 }
1000 break;
1001 }
1002
1003 case FIFFV_REF_MEG_CH: {
1004 dMaxValue = 1e-11f;
1005 if (getScaling().contains(FIFF_UNIT_T))
1006 dMaxValue = getScaling()[FIFF_UNIT_T];
1007 break;
1008 }
1009 case FIFFV_EEG_CH: {
1010 dMaxValue = 1e-4f;
1011 if (getScaling().contains(FIFFV_EEG_CH))
1012 dMaxValue = getScaling()[FIFFV_EEG_CH];
1013 break;
1014 }
1015 case FIFFV_EOG_CH: {
1016 dMaxValue = 1e-3f;
1017 if (getScaling().contains(FIFFV_EOG_CH))
1018 dMaxValue = getScaling()[FIFFV_EOG_CH];
1019 break;
1020 }
1021 case FIFFV_STIM_CH: {
1022 dMaxValue = 5;
1023 if (getScaling().contains(FIFFV_STIM_CH))
1024 dMaxValue = getScaling()[FIFFV_STIM_CH];
1025 break;
1026 }
1027 case FIFFV_MISC_CH: {
1028 dMaxValue = 1e-3f;
1029 if (getScaling().contains(FIFFV_MISC_CH))
1030 dMaxValue = getScaling()[FIFFV_MISC_CH];
1031 break;
1032 }
1033 default:
1034 dMaxValue = 1e-9f;
1035 break;
1036 }
1037
1038 return dMaxValue;
1039}
1040
1041//=============================================================================================================
1042
1044{
1045 if (m_fnAddEvent) {
1046 m_fnAddEvent(iSample);
1047 }
1048}
1049
1050//=============================================================================================================
1051
1053{
1054 if (iColumn < 0 || iColumn >= m_iMaxSamples) {
1055 return -1;
1056 }
1057
1058 // Left of the write position is the current sweep, from it on the previous one
1059 const int iWritePosition = m_bIsFreezed ? m_iCurrentSampleFreeze : m_iCurrentSample;
1060 const int iSample = getFirstSampleOffset() + iColumn - (iColumn < iWritePosition ? 0 : m_iMaxSamples);
1061
1062 return iSample >= 0 ? iSample : -1;
1063}
1064
1065//=============================================================================================================
1066
1067double RtFiffRawViewModel::getValueAtColumn(int iRow, int iColumn) const
1068{
1069 const RowVectorPair rowData = data(index(iRow, 1)).value<RowVectorPair>();
1070 if (!rowData.first || iColumn < 0 || iColumn >= rowData.second) {
1071 return 0.0;
1072 }
1073
1074 return rowData.first[iColumn];
1075}
1076
1077//=============================================================================================================
1078
1079std::vector<int> RtFiffRawViewModel::getEventsToDisplay(int iBegin, int iEnd) const
1080{
1081 if (m_fnGetEventSamples) {
1082 return m_fnGetEventSamples(iBegin, iEnd);
1083 }
1084 return {};
1085}
1086
1087//=============================================================================================================
1088
1089void RtFiffRawViewModel::setEventCallbacks(std::function<void(int)> addFn,
1090 std::function<std::vector<int>(int, int)> getFn)
1091{
1092 m_fnAddEvent = std::move(addFn);
1093 m_fnGetEventSamples = std::move(getFn);
1094}
constexpr auto FIFF_UNIT_T_M
constexpr auto FIFFV_ECG_CH
#define FIFFV_EEG_CH
constexpr auto FIFFV_MISC_CH
#define FIFFV_MEG_CH
constexpr auto FIFFV_COIL_NONE
constexpr auto FIFFV_STIM_CH
constexpr auto FIFF_UNIT_T
constexpr auto FIFFV_EMG_CH
constexpr auto FIFF_UNIT_NONE
constexpr auto FIFFV_EOG_CH
constexpr auto FIFFV_REF_MEG_CH
Full FIFF measurement metadata: everything from FIFFB_MEAS / FIFFB_MEAS_INFO needed to interpret a re...
Primitive scalar typedefs and forward-compatible aliases backing the FIFF type system.
Header-only Eigen matrix text I/O — round-trips dense matrices to whitespace-separated ASCII for cros...
Circular-buffer QAbstractTableModel feeding the real-time FIFF raw browser.
SPatial HARmonic Analysis (SPHARA) spatial-filter projector assembly.
Threshold and edge-based trigger detection on streaming stim channels.
FIFF file I/O, in-memory data structures and high-level readers/writers.
qint32 fiff_int_t
Definition fiff_types.h:86
2-D display widgets and visualisation helpers (charts, topography, colour maps).
Eigen::Matrix< double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor > MatrixXdR
QPair< const double *, qint32 > RowVectorPair
DSPSHARED_EXPORT QMap< int, QList< QPair< int, double > > > detectTriggerFlanksMax(const Eigen::MatrixXd &data, const QList< int > &lTriggerChannels, int iOffsetIndex, double dThreshold, bool bRemoveOffset, int iBurstLengthSamp=100)
DSPSHARED_EXPORT Eigen::MatrixXd filterData(const Eigen::MatrixXd &matData, int type, double dCenterfreq, double dBandwidth, double dTransition, double dSFreq, int iOrder=1024, int designMethod=UTILSLIB::FilterKernel::m_designMethods.indexOf(UTILSLIB::FilterParameter("Cosine")), const Eigen::RowVectorXi &vecPicks=Eigen::RowVectorXi(), bool bUseThreads=true, bool bKeepOverhead=false)
Shared utilities (I/O helpers, spectral analysis, layout management, warp algorithms).
DSPSHARED_EXPORT Eigen::MatrixXd makeSpharaProjector(const Eigen::MatrixXd &matBaseFct, const Eigen::VectorXi &vecIndices, int iOperatorDim, int iNBaseFct, int iSkip=0)
const QMap< qint32, float > & getScaling() const
void setBackgroundColor(const QColor &color)
void setFilter(QList< UTILSLIB::FilterKernel > filterData)
void selectRows(const QList< qint32 > &selection)
void newSelection(const QList< qint32 > &selection)
FIFFLIB::fiff_int_t getKind(qint32 row) const
QSharedPointer< RtFiffRawViewModel > SPtr
void setSamplingInfo(float sps, int T, bool bSetZero=false)
double getValueAtColumn(int iRow, int iColumn) const
void markChBad(QModelIndex ch, bool status)
RtFiffRawViewModel(QObject *parent=0)
void setFilterChannelType(const QString &channelType)
void setFiffInfo(QSharedPointer< FIFFLIB::FiffInfo > &p_pFiffInfo)
virtual int rowCount(const QModelIndex &parent=QModelIndex()) const
void toggleFreeze(const QModelIndex &index)
void hideRows(const QList< qint32 > &selection)
void setEventCallbacks(std::function< void(int)> addFn, std::function< std::vector< int >(int, int)> getFn)
void updateSpharaOptions(const QString &sSytemType, int nBaseFctsFirst, int nBaseFctsSecond)
void setScaling(const QMap< qint32, float > &p_qMapChScaling)
virtual int columnCount(const QModelIndex &parent=QModelIndex()) const
FIFFLIB::fiff_int_t getCoil(qint32 row) const
virtual QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const
void triggerDetected(int numberDetectedTriggers, const QMap< int, QList< QPair< int, double > > > &mapDetectedTriggers)
FIFFLIB::fiff_int_t getUnit(qint32 row) const
int getSampleAtColumn(int iColumn) const
std::vector< int > getEventsToDisplay(int iBegin, int iEnd) const
void triggerInfoChanged(const QMap< double, QColor > &colorMap, bool active, QString triggerCh, double threshold)
void updateProjection(const QList< FIFFLIB::FiffProj > &projs)
virtual QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const
void addData(const QList< Eigen::MatrixXd > &data)
double getMaxValueFromRawViewModel(int row) const
Per-channel FIFF descriptor: identifiers, kind, calibration, coil type, channel-frame coil position a...
One CTF software-gradient compensation matrix: grade kind, calibration flag and the gradiometer × ref...
FiffNamedMatrix::SDPtr data
Full FIFF measurement info: per-channel descriptors, sampling and filter setup, projectors,...
Definition fiff_info.h:90
static Eigen::RowVectorXi pick_channels(const QStringList &ch_names, const QStringList &include=defaultQStringList, const QStringList &exclude=defaultQStringList)
static bool read_eigen_matrix(Eigen::Matrix< T, Eigen::Dynamic, Eigen::Dynamic > &out, const QString &path)
Definition ioutils.h:283