v2.0.0
Loading...
Searching...
No Matches
butterflyview.cpp
Go to the documentation of this file.
1//=============================================================================================================
15
16//=============================================================================================================
17// INCLUDES
18//=============================================================================================================
19
20#include "butterflyview.h"
21
22#include "scalingview.h"
23
26
27//=============================================================================================================
28// QT INCLUDES
29//=============================================================================================================
30
31#include <QPainter>
32#include <QPainterPath>
33#include <QDebug>
34#include <QSvgGenerator>
35#include <QSurfaceFormat>
36#include <QSettings>
37
38//=============================================================================================================
39// USED NAMESPACES
40//=============================================================================================================
41
42using namespace DISPLIB;
43
44//=============================================================================================================
45// DEFINE MEMBER METHODS
46//=============================================================================================================
47
48ButterflyView::ButterflyView(const QString& sSettingsPath,
49 QWidget *parent,
50 Qt::WindowFlags f)
51:
52 QRhiWidget(parent)
53, m_sSettingsPath(sSettingsPath)
54, m_pEvokedSetModel(nullptr)
55, m_bIsInit(false)
56, m_bShowMAG(true)
57, m_bShowGRAD(true)
58, m_bShowEEG(true)
59, m_bShowEOG(true)
60, m_bShowMISC(true)
62, m_qMapAverageActivation(QSharedPointer<QMap<QString, bool> >::create())
63, m_qMapAverageColor(QSharedPointer<QMap<QString, QColor> >::create())
64{
65 m_sSettingsPath = sSettingsPath;
66
67#if defined(WASMBUILD) || defined(__EMSCRIPTEN__)
68 setApi(QRhiWidget::Api::OpenGL);
69#elif defined(Q_OS_MACOS) || defined(Q_OS_IOS)
70 setApi(QRhiWidget::Api::Metal);
71#elif defined(Q_OS_WIN)
72 setApi(QRhiWidget::Api::Direct3D11);
73#else
74 setApi(QRhiWidget::Api::OpenGL);
75#endif
76 setSampleCount(1);
77 setAttribute(Qt::WA_NativeWindow);
78
80}
81
82//=============================================================================================================
83
88
89//=============================================================================================================
90
92{
93 // No-op: ButterflyView now derives from QRhiWidget directly.
94}
95
96//=============================================================================================================
97
98void ButterflyView::setEvokedSetModel(QSharedPointer<EvokedSetModel> model)
99{
100 m_pEvokedSetModel = model;
101
102 connect(m_pEvokedSetModel.data(), &EvokedSetModel::dataChanged,
103 this, &ButterflyView::dataUpdate, Qt::UniqueConnection);
104}
105
106//=============================================================================================================
107
109{
110 if(!m_bIsInit && m_pEvokedSetModel->isInit()) {
111 m_bIsInit = true;
112 }
113
115
116 update();
117}
118
119//=============================================================================================================
120
121QMap<QString, bool> ButterflyView::getModalityMap()
122{
123 return m_modalityMap;
124}
125
126//=============================================================================================================
127
128void ButterflyView::setModalityMap(const QMap<QString, bool> &modalityMap)
129{
130 m_modalityMap = modalityMap;
131 update();
132}
133
134//=============================================================================================================
135
136void ButterflyView::setScaleMap(const QMap<qint32,float> &scaleMap)
137{
138 m_scaleMap = scaleMap;
139 update();
140}
141
142//=============================================================================================================
143
144void ButterflyView::setSelectedChannels(const QList<int> &selectedChannels)
145{
146 m_lSelectedChannels = selectedChannels;
147 update();
148}
149
150//=============================================================================================================
151
153{
154 update();
155}
156
157//=============================================================================================================
158
159void ButterflyView::setBackgroundColor(const QColor& backgroundColor)
160{
161 m_colCurrentBackgroundColor = backgroundColor;
162 update();
163}
164
165//=============================================================================================================
166
171
172//=============================================================================================================
173
174void ButterflyView::takeScreenshot(const QString& fileName)
175{
176 if(fileName.contains(".svg", Qt::CaseInsensitive)) {
177 // Generate screenshot
178 QSvgGenerator svgGen;
179 svgGen.setFileName(fileName);
180 svgGen.setSize(this->size());
181 svgGen.setViewBox(this->rect());
182
183 QWidget::render(&svgGen);
184 }
185
186 if(fileName.contains(".png", Qt::CaseInsensitive)) {
187 QImage image(this->size(), QImage::Format_ARGB32);
188 image.fill(Qt::transparent);
189
190 QPainter painter(&image);
191 QWidget::render(&painter);
192 image.save(fileName);
193 }
194}
195
196//=============================================================================================================
197
198QSharedPointer<QMap<QString, QColor> > ButterflyView::getAverageColor() const
199{
200 return m_qMapAverageColor;
201}
202
203//=============================================================================================================
204
205QSharedPointer<QMap<QString, bool> > ButterflyView::getAverageActivation() const
206{
208}
209
210//=============================================================================================================
211
212void ButterflyView::setAverageColor(const QSharedPointer<QMap<QString, QColor> > qMapAverageColor)
213{
214 m_qMapAverageColor = qMapAverageColor;
215 update();
216}
217
218//=============================================================================================================
219
220void ButterflyView::setSingleAverageColor(const QColor& avgColor)
221{
222 for (QString mapKey : m_qMapAverageColor->keys())
223 m_qMapAverageColor->insert(mapKey, avgColor);
224}
225
226//=============================================================================================================
227
228void ButterflyView::setAverageActivation(const QSharedPointer<QMap<QString, bool> > qMapAverageActivation)
229{
230 m_qMapAverageActivation = qMapAverageActivation;
231 update();
232}
233
234//=============================================================================================================
235
236void ButterflyView::setChannelInfoModel(QSharedPointer<ChannelInfoModel> &pChannelInfoModel)
237{
238 m_pChannelInfoModel = pChannelInfoModel;
239}
240
241//=============================================================================================================
242
243void ButterflyView::showSelectedChannelsOnly(const QStringList& selectedChannels)
244{
246 qDebug() << "ButterflyView::showSelectedChannelsOnly - m_pChannelInfoModel is NULL. Returning. ";
247 return;
248 }
249
250 QList<int> selectedChannelsIndexes;
251
252 for(int i = 0; i<selectedChannels.size(); i++)
253 selectedChannelsIndexes<<m_pChannelInfoModel->getIndexFromOrigChName(selectedChannels.at(i));
254
255 setSelectedChannels(selectedChannelsIndexes);
256}
257
258//=============================================================================================================
259
260void ButterflyView::showSelectedChannels(const QList<int> selectedChannelsIndexes)
261{
262 setSelectedChannels(selectedChannelsIndexes);
263}
264
265//=============================================================================================================
266
268{
269 if (m_pEvokedSetModel) {
270 QList<int> lAllChannels;
271 for(int i = 0; i < m_pEvokedSetModel->rowCount(); i++) {
272 lAllChannels.append(i);
273 }
274 setSelectedChannels(lAllChannels);
275 }
276}
277
278//=============================================================================================================
279
281{
282 if(m_sSettingsPath.isEmpty()) {
283 return;
284 }
285
286 // Save Settings
287 QSettings settings("MNECPP");
288}
289
290//=============================================================================================================
291
293{
294 if(m_sSettingsPath.isEmpty()) {
295 return;
296 }
297
298 // Load Settings
299 QSettings settings("MNECPP");
300}
301
302//=============================================================================================================
303
304void ButterflyView::paintEvent(QPaintEvent *event)
305{
306 QPainter painter(this);
307
308 painter.save();
309 painter.setBrush(QBrush(m_colCurrentBackgroundColor));
310 painter.drawRect(QRect(-1,-1,this->width()+2,this->height()+2));
311 painter.restore();
312
313 painter.setRenderHint(QPainter::Antialiasing, true);
314
316 {
317 //Draw baseline correction area
318 if(m_pEvokedSetModel->getBaselineInfo().first.toString() != "None" &&
319 m_pEvokedSetModel->getBaselineInfo().second.toString() != "None") {
320 float from = m_pEvokedSetModel->getBaselineInfo().first.toFloat();
321 float to = m_pEvokedSetModel->getBaselineInfo().second.toFloat();
322
323 painter.save();
324 painter.setPen(QPen(Qt::red, 1, Qt::DashLine));
325 painter.setBrush(Qt::red);
326 painter.setOpacity(0.1);
327
328 if(m_pEvokedSetModel->getNumSamples() == 0){
329 qDebug() << "Unable to get data. Returning early.";
330 return;
331 }
332
333 float fDx = (float)(this->width()) / ((float)m_pEvokedSetModel->getNumSamples());
334
335 float fromSamp = ((from)*m_pEvokedSetModel->getSamplingFrequency())+m_pEvokedSetModel->getNumPreStimSamples();
336 float posX = fDx*(fromSamp);
337 float toSamp = ((to)*m_pEvokedSetModel->getSamplingFrequency())+m_pEvokedSetModel->getNumPreStimSamples();
338 float width = fDx*(toSamp-fromSamp);
339
340 QRect rect(posX,0,width,this->height());
341
342 painter.drawRect(rect);
343
344 painter.restore();
345 }
346
347 //Stimulus bar
348 if(m_pEvokedSetModel->getNumSamples() > 0) {
349 painter.save();
350 painter.setPen(QPen(Qt::red, 1, Qt::DashLine));
351
352 float fDx = (float)(this->width()) / ((float)m_pEvokedSetModel->getNumSamples());
353 float posX = fDx * ((float)m_pEvokedSetModel->getNumPreStimSamples());
354 painter.drawLine(posX, 1, posX, this->height());
355
356 painter.drawText(QPointF(posX+5,this->rect().bottomRight().y()-5), QString("0ms / Stimulus"));
357
358 painter.restore();
359 }
360
361 //Vertical time spacers
362 if(m_pEvokedSetModel->getNumberOfTimeSpacers() > 0)
363 {
364 painter.save();
365 QColor colorTimeSpacer = Qt::black;
366 colorTimeSpacer.setAlphaF(0.5);
367 painter.setPen(QPen(colorTimeSpacer, 1, Qt::DashLine));
368
369 float yStart = this->rect().topLeft().y();
370 float yEnd = this->rect().bottomRight().y();
371
372 float fDx = 1;
373 if(m_pEvokedSetModel->getNumSamples() != 0) {
374 fDx = (float)(this->width()) / ((float)m_pEvokedSetModel->getNumSamples());
375 }
376
377 float sampleCounter = m_pEvokedSetModel->getNumPreStimSamples();
378 int counter = 1;
379 float timeDistanceMSec = 50.0;
380 float timeDistanceSamples = (timeDistanceMSec/1000.0)*m_pEvokedSetModel->getSamplingFrequency(); //time distance corresponding to sampling frequency
381
382 //spacers before stim
383 while(sampleCounter-timeDistanceSamples>0) {
384 sampleCounter-=timeDistanceSamples;
385 float x = fDx*sampleCounter;
386 painter.drawLine(x, yStart, x, yEnd);
387 painter.drawText(QPointF(x+5,yEnd-5), QString("-%1ms").arg(timeDistanceMSec*counter));
388 counter++;
389 }
390
391 //spacers after stim
392 counter = 1;
393 sampleCounter = m_pEvokedSetModel->getNumPreStimSamples();
394 while(sampleCounter+timeDistanceSamples<m_pEvokedSetModel->getNumSamples()) {
395 sampleCounter+=timeDistanceSamples;
396 float x = fDx*sampleCounter;
397 painter.drawLine(x, yStart, x, yEnd);
398 painter.drawText(QPointF(x+5,yEnd-5), QString("%1ms").arg(timeDistanceMSec*counter));
399 counter++;
400 }
401
402 painter.restore();
403 }
404
405 //Zero line
406 if(m_pEvokedSetModel->getNumSamples() > 0) {
407 painter.save();
408 painter.setPen(QPen(Qt::black, 1, Qt::DashLine));
409
410 painter.drawLine(0, this->height()/2, this->width(), this->height()/2);
411
412 painter.restore();
413 }
414
415 painter.translate(0,this->height()/2);
416
417 //Actual average data
418 for(qint32 r = 0; r < m_pEvokedSetModel->rowCount(); ++r) {
419 if(m_lSelectedChannels.contains(r)) {
420 qint32 kind = m_pEvokedSetModel->getKind(r);
421
422 //Display only selected kinds
423 switch(kind) {
424 case FIFFV_MEG_CH: {
425 qint32 unit = m_pEvokedSetModel->getUnit(r);
426 if(unit == FIFF_UNIT_T_M) {
427 if(m_modalityMap["GRAD"])
428 break;
429 else
430 continue;
431 }
432 else if(unit == FIFF_UNIT_T)
433 {
434 if(m_modalityMap["MAG"])
435 break;
436 else
437 continue;
438 }
439 continue;
440 }
441 case FIFFV_EEG_CH: {
442 if(m_modalityMap["EEG"])
443 break;
444 else
445 continue;
446 }
447 case FIFFV_EOG_CH: {
448 if(m_modalityMap["EOG"])
449 break;
450 else
451 continue;
452 }
453 case FIFFV_MISC_CH: {
454 if(m_modalityMap["MISC"])
455 break;
456 else
457 continue;
458 }
459 default:
460 continue;
461 }
462
463 painter.save();
464
465 createPlotPath(r, painter);
466
467 painter.restore();
468 }
469 }
470 }
471
472 return QRhiWidget::paintEvent(event);
473}
474
475//=============================================================================================================
476
477void ButterflyView::createPlotPath(qint32 row, QPainter& painter) const
478{
479 //get maximum range of respective channel type (range value in FiffChInfo does not seem to contain a reasonable value)
480 qint32 kind = m_pEvokedSetModel->getKind(row);
481 float fMaxValue = DISPLIB::getScalingValue(m_scaleMap, kind, m_pEvokedSetModel->getUnit(row));
482 bool bIsBad = m_pEvokedSetModel->getIsChannelBad(row);
483
484 if(bIsBad) {
485 painter.setOpacity(0.20);
486 } else {
487 painter.setOpacity(0.75);
488 }
489
490 float fValue;
491 float fScaleY = this->height()/(2*fMaxValue);
492
493 //restrictions for paint performance
494 float fWinMaxVal = ((float)this->height()-2)/2.0f;
495// qint32 iDownSampling = (m_pEvokedSetModel->getNumSamples() * 4 / (this->width()-2));
496// if(iDownSampling < 1) {
497// iDownSampling = 1;
498// }
499
500 QPointF qSamplePosition;
501
502 float fDx = (float)(this->width()-2) / ((float)m_pEvokedSetModel->getNumSamples()-1.0f);//((float)option.rect.width()) / m_pEvokedSetModel->getMaxSamples();
503
504 QList<DISPLIB::AvrTypeRowVector> rowVec = m_pEvokedSetModel->data(row,1).value<QList<DISPLIB::AvrTypeRowVector> >();
505
506 //Do for all average types
507 for(int j = 0; j < rowVec.size(); ++j) {
508 QString sAvrComment = rowVec.at(j).first;
509
510 // Select color for each average
511 if(m_pEvokedSetModel->isFreezed()) {
512 QColor freezeColor = m_qMapAverageColor->value(sAvrComment);
513 freezeColor.setAlphaF(0.5);
514 painter.setPen(QPen(freezeColor, 1));
515 } else {
516 painter.setPen(QPen(m_qMapAverageColor->value(sAvrComment)));
517 }
518
519 if(m_qMapAverageActivation->value(sAvrComment)) {
520 //Calculate downsampling factor of averaged data in respect to the items width
521 int dsFactor;
522 rowVec.at(j).second.cols() / this->width() < 1 ? dsFactor = 1 : dsFactor = rowVec.at(j).second.cols() / this->width();
523 if(dsFactor == 0) {
524 dsFactor = 1;
525 }
526
527 QPainterPath path(QPointF(1,0));
528 float y_base = path.currentPosition().y();
529
530 //Move to initial starting point
531 if(rowVec.at(j).second.cols() > 0)
532 {
533 float val = rowVec.at(j).second[0];
534 fValue = (val/*-rowVec.at(j)[m_pEvokedSetModel->getNumPreStimSamples()-1]*/)*fScaleY;//ToDo -> -2 PreStim is one too short
535
536 float newY = y_base+fValue;
537
538 qSamplePosition.setY(-newY);
539 qSamplePosition.setX(path.currentPosition().x());
540
541 path.moveTo(qSamplePosition);
542 }
543
544 //create lines from one to the next sample
545 qint32 i;
546 for(i = 1; i < rowVec.at(j).second.cols() && path.elementCount() <= this->width(); i += dsFactor) {
547 float val = /*rowVec.at(j)[m_pEvokedSetModel->getNumPreStimSamples()-1] - */rowVec.at(j).second[i]; //remove first sample data[0] as offset
548 fValue = val*fScaleY;
549
550 //Cut plotting if out of widget area
551 fValue = fValue > fWinMaxVal ? fWinMaxVal : fValue < -fWinMaxVal ? -fWinMaxVal : fValue;
552
553 float newY = y_base+fValue;
554
555 qSamplePosition.setY(-newY);
556
557 qSamplePosition.setX(path.currentPosition().x()+fDx);
558
559 path.lineTo(qSamplePosition);
560 }
561
562 // //create lines from one to the next sample for last path
563 // qint32 sample_offset = m_pEvokedSetModel->numVLines() + 1;
564 // qSamplePosition.setX(qSamplePosition.x() + fDx*sample_offset);
565 // lastPath.moveTo(qSamplePosition);
566
567 // for(i += sample_offset; i < lastData.size(); ++i) {
568 // float val = lastData[i] - lastData[0]; //remove first sample lastData[0] as offset
569 // fValue = val*fScaleY;
570
571 // float newY = y_base+fValue;
572
573 // qSamplePosition.setY(newY);
574 // qSamplePosition.setX(lastPath.currentPosition().x()+fDx);
575
576 // lastPath.lineTo(qSamplePosition);
577 // }
578
579 painter.drawPath(path);
580 }
581 }
582}
583
584//=============================================================================================================
585
586QSharedPointer<EvokedSetModel> ButterflyView::getEvokedSetModel()
587{
588 return m_pEvokedSetModel;
589}
590
591//=============================================================================================================
592
594{
595 setEvokedSetModel(Q_NULLPTR);
596}
Per-modality vertical-scale spinbox panel (one spin per channel type).
QAbstractTableModel exposing per-channel FIFF metadata (name, type, unit, position,...
QAbstractTableModel wrapping a FiffEvokedSet so views can render per-condition averages without copyi...
QRhi-accelerated butterfly plot overlaying every channel of one or more averaged conditions.
#define FIFFV_EOG_CH
#define FIFFV_EEG_CH
#define FIFFV_MISC_CH
#define FIFFV_MEG_CH
#define FIFF_UNIT_T
#define FIFF_UNIT_T_M
2-D display widgets and visualisation helpers (charts, topography, colour maps).
float getScalingValue(const QMap< qint32, float > &qMapChScaling, int iChannelKind, int iChannelUnit)
QSharedPointer< QMap< QString, bool > > m_qMapAverageActivation
virtual void paintEvent(QPaintEvent *event)
QSharedPointer< EvokedSetModel > m_pEvokedSetModel
QList< int > m_lSelectedChannels
void createPlotPath(qint32 row, QPainter &painter) const
void setChannelInfoModel(QSharedPointer< ChannelInfoModel > &pChannelInfoModel)
QSharedPointer< QMap< QString, QColor > > getAverageColor() const
void setModalityMap(const QMap< QString, bool > &modalityMap)
void takeScreenshot(const QString &fileName)
QMap< QString, bool > getModalityMap()
const QColor & getBackgroundColor()
void setAverageColor(const QSharedPointer< QMap< QString, QColor > > qMapAverageColor)
void showSelectedChannels(const QList< int > selectedChannelsIndexes)
void setSelectedChannels(const QList< int > &selectedChannels)
QSharedPointer< ChannelInfoModel > m_pChannelInfoModel
QSharedPointer< QMap< QString, QColor > > m_qMapAverageColor
QSharedPointer< QMap< QString, bool > > getAverageActivation() const
void setEvokedSetModel(QSharedPointer< EvokedSetModel > model)
void setSingleAverageColor(const QColor &avgColor)
void setBackgroundColor(const QColor &backgroundColor)
void showSelectedChannelsOnly(const QStringList &selectedChannels)
void setScaleMap(const QMap< qint32, float > &scaleMap)
QMap< qint32, float > m_scaleMap
ButterflyView(const QString &sSettingsPath="", QWidget *parent=0, Qt::WindowFlags f=Qt::Widget)
QSharedPointer< EvokedSetModel > getEvokedSetModel()
QMap< QString, bool > m_modalityMap
void setAverageActivation(const QSharedPointer< QMap< QString, bool > > qMapAverageActivation)