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