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