1 // Copyright (c) 2016 The SigViewer Development Team
2 // Licensed under the GNU General Public License (GPL)
3 // https://www.gnu.org/licenses/gpl
4 
5 
6 
7 #include "xdf_reader.h"
8 #include "biosig_basic_header.h"
9 #include "file_handler_factory_registrator.h"
10 #include "gui/progress_bar.h"
11 #include "base/fixed_data_block.h"
12 #include "gui_impl/dialogs/resampling_dialog.h"
13 
14 #include <QTextStream>
15 #include <QTranslator>
16 #include <QMutexLocker>
17 #include <QTime>
18 #include <QMessageBox>
19 
20 #include <cmath>
21 #include <algorithm>
22 #include <time.h>       /* clock_t, clock, CLOCKS_PER_SEC */
23 
24 
25 namespace sigviewer
26 {
27 
28 //the object to store XDF data
29 QSharedPointer<Xdf> XDFdata = QSharedPointer<Xdf>(new Xdf);
30 
31 
32 //-----------------------------------------------------------------------------
33 
34 FILE_SIGNAL_READER_REGISTRATION(xdf, XDFReader);
35 
36 //-----------------------------------------------------------------------------
XDFReader()37 XDFReader::XDFReader() :
38     basic_header_ (0),
39     buffered_all_channels_ (false),
40     buffered_all_events_ (false)
41 {
42     qDebug () << "Constructed XDFReader";
43 }
44 
45 //-----------------------------------------------------------------------------
~XDFReader()46 XDFReader::~XDFReader()
47 {
48     qDebug() << "Destructed XDFReader.";
49 }
50 
51 //-----------------------------------------------------------------------------
createInstance(QString const & file_path)52 QPair<FileSignalReader*, QString> XDFReader::createInstance (QString const& file_path)
53 {
54     XDFReader* reader (new XDFReader);
55 
56     QString error = reader->open (file_path);
57     if (error.size() > 0)
58     {
59         qDebug () << error;
60         //QMessageBox::critical(0, QObject::tr("Error"), error);
61         return QPair<FileSignalReader*, QString> (0, error);
62     }
63     else
64         return QPair<FileSignalReader*, QString> (reader, "");
65 }
66 
67 //-----------------------------------------------------------------------------
getSignalData(ChannelID channel_id,size_t start_sample,size_t length) const68 QSharedPointer<DataBlock const> XDFReader::getSignalData (ChannelID channel_id,
69                                        size_t start_sample,
70                                        size_t length) const
71 {
72     QMutexLocker lock (&mutex_);
73 
74     if (!buffered_all_channels_)
75         bufferAllChannels();
76 
77     if (!channel_map_.contains(channel_id))
78         return QSharedPointer<DataBlock const> (0);
79 
80     if (length == basic_header_->getNumberOfSamples() &&
81         start_sample == 0)
82         return channel_map_[channel_id];
83     else
84         return channel_map_[channel_id]->createSubBlock (start_sample, length);
85 }
86 
87 //-----------------------------------------------------------------------------
getEvents() const88 QList<QSharedPointer<SignalEvent const> > XDFReader::getEvents () const
89 {
90     QMutexLocker lock (&mutex_);
91 
92     if (!buffered_all_events_)
93         bufferAllEvents();
94 
95     return events_;
96 }
97 
98 //-----------------------------------------------------------------------------
open(QString const & file_path)99 QString XDFReader::open (QString const& file_path)
100 {
101     QMutexLocker lock (&mutex_);
102     return loadFixedHeader (file_path);
103 }
104 
105 //-----------------------------------------------------------------------------
loadFixedHeader(const QString & file_path)106 QString XDFReader::loadFixedHeader(const QString& file_path)
107 {
108     QMutexLocker locker (&xdf_access_lock_);
109 
110     clock_t t = clock();
111     clock_t t2 = clock();
112 
113     if (QFile::exists(file_path)) //Double check whether file exists
114     {
115         if (XDFdata->load_xdf(file_path.toStdString()) == 0)
116         {
117             XDFdata->createLabels();
118 
119             sampleRateTypes sampleRateType = selectSampleRateType();
120 
121             switch (sampleRateType) {
122             case No_streams_found:
123             {
124                 QMessageBox msgBox;
125                 msgBox.setIcon(QMessageBox::Warning);
126                 msgBox.setText("No Stream Found");
127                 msgBox.setStandardButtons(QMessageBox::Ok);
128                 msgBox.exec();
129 
130                 return "non-exist";
131             }
132             case Zero_Hz_Only:
133             {
134                 ResamplingDialog prompt(XDFdata->majSR, XDFdata->maxSR);
135 
136                 if (prompt.exec() == QDialog::Accepted)
137                 {
138                     XDFdata->majSR = prompt.getUserSrate();
139                     XDFdata->resample(XDFdata->majSR);
140                 }
141                 else
142                 {
143                     Xdf empty;
144                     std::swap(*XDFdata, empty);
145                     return "Cancelled";
146                 }
147             }
148                 break;
149             case Mono_Sample_Rate:
150             {
151                 XDFdata->calcTotalLength(XDFdata->majSR);
152 
153                 XDFdata->adjustTotalLength();
154 
155                 if (XDFdata->effectiveSampleRateVector.size())
156                 {
157                     /* If and only if the file contains only one sample rate, we use effective
158                     sample rate to more accurately display events. Effective sample rates
159                     can be slightly different across streams, so we calculate the mean here */
160 
161                     double init = 0.0;
162                     XDFdata->fileEffectiveSampleRate =
163                             std::accumulate(XDFdata->effectiveSampleRateVector.begin(),
164                                             XDFdata->effectiveSampleRateVector.end(), init)
165                             / XDFdata->effectiveSampleRateVector.size();
166                 }
167             }
168                 break;
169             case Multi_Sample_Rate:
170             {
171                 ResamplingDialog prompt(XDFdata->majSR, XDFdata->maxSR);
172 
173                 if (prompt.exec() == QDialog::Accepted)
174                 {
175                     XDFdata->majSR = prompt.getUserSrate();
176                     XDFdata->resample(XDFdata->majSR);
177                 }
178                 else
179                 {
180                     Xdf empty;
181                     std::swap(*XDFdata, empty);
182                     return "Cancelled";
183                 }
184             }
185                 break;
186             default:
187                 qDebug() << "Unknown sample rate type.";
188                 break;
189             }
190 
191             XDFdata->freeUpTimeStamps(); //to save some memory
192 
193             setStreamColors();
194             setEventTypeColors();
195 
196             t = clock() - t;
197             qDebug() << "it took " << ((float)t) / CLOCKS_PER_SEC << " seconds reading data";
198 
199             t = clock() - t - t2;
200             qDebug() << "it took " << ((float)t) / CLOCKS_PER_SEC << " additional seconds loading XDF header";
201 
202             bool showWarning = false;
203 
204             for (auto const &stream : XDFdata->streams)
205             {
206                 if (std::abs(stream.info.effective_sample_rate - stream.info.nominal_srate) >
207                         stream.info.nominal_srate / 20)
208                 {
209                     showWarning = true;
210                 }
211             }
212 
213             if (showWarning)
214                 QMessageBox::warning(0, "SigViewer",
215                                      "The effective sampling rate of at least one stream is significantly different than the reported nominal sampling rate. Signal visualization might be inaccurate.", QMessageBox::Ok, QMessageBox::Ok);
216 
217 
218             basic_header_ = QSharedPointer<BasicHeader>
219                     (new BiosigBasicHeader ("XDF", file_path));
220 
221             basic_header_->setNumberEvents(XDFdata->eventType.size());
222 
223             if (XDFdata->fileEffectiveSampleRate)
224                 basic_header_->setEventSamplerate(XDFdata->fileEffectiveSampleRate);
225             else
226                 basic_header_->setEventSamplerate(XDFdata->majSR);
227 
228             return "";
229         }
230         else
231         {
232             QMessageBox msgBox;
233             msgBox.setIcon(QMessageBox::Warning);
234             msgBox.setText("Unable to open file.");
235             msgBox.setStandardButtons(QMessageBox::Ok);
236             msgBox.exec();
237 
238             return "non-exist";
239         }
240     }
241     else
242     {
243         QMessageBox msgBox;
244         msgBox.setIcon(QMessageBox::Warning);
245         msgBox.setText("File does not exist.");
246         msgBox.setStandardButtons(QMessageBox::Ok);
247         msgBox.exec();
248 
249         return "non-exist";
250     }
251 }
252 
253 //-----------------------------------------------------------------------------
getBasicHeader()254 QSharedPointer<BasicHeader> XDFReader::getBasicHeader ()
255 {
256     //QMutexLocker lock (&mutex_);
257     return basic_header_;
258 }
259 
260 //-----------------------------------------------------------------------------
setStreamColors()261 int XDFReader::setStreamColors()
262 {
263     // Display each stream in a distinct color
264     QSharedPointer<ColorManager> colorPicker = ApplicationContextImpl::getInstance()->color_manager_;
265     QVector<QColor> colorList = {"#0055ff", "#00aa00", "#aa00ff", "#00557f",
266                                  "#5555ff", "#ff55ff", "#00aaff", "#00aa7f"};
267 
268     //some streams contains text only and no channels, so we skip those streams
269     //because the colors I picked look the best when they are sorted in order
270     int colorChoice = -1;
271     int stream = -1;
272     for (size_t i = 0; i < XDFdata->totalCh; i++)
273     {
274         if (stream != XDFdata->streamMap[i])
275         {
276             stream = XDFdata->streamMap[i];
277             colorChoice++;
278             if (colorChoice == 8)   //we only have 8 colors
279                 colorChoice = 0;
280         }
281         colorPicker->setChannelColor(i, colorList[colorChoice]);
282     }
283 
284     colorPicker->saveSettings();
285 
286     return 0;
287 }
288 
289 //-----------------------------------------------------------------------------
selectSampleRateType()290 XDFReader::sampleRateTypes XDFReader::selectSampleRateType()
291 {
292     switch (XDFdata->sampleRateMap.size()) {
293     case 0:
294         return No_streams_found;
295     case 1:
296         if (XDFdata->sampleRateMap.count(0))
297             return Zero_Hz_Only;
298         else
299             return Mono_Sample_Rate;
300     case 2:
301         if (XDFdata->sampleRateMap.count(0))
302             return Mono_Sample_Rate;
303         else
304             return Multi_Sample_Rate;
305     default:
306         return Multi_Sample_Rate;
307     }
308 }
309 
310 //-----------------------------------------------------------------------------
bufferAllChannels() const311 void XDFReader::bufferAllChannels () const
312 {
313     size_t numberOfSamples = XDFdata->totalLen;
314 
315     QString progress_name = QObject::tr("Loading data...");
316 
317     //load all signals channel by channel
318     unsigned channel_id = 0;
319     for (auto &stream : XDFdata->streams)
320     {
321         if (stream.info.nominal_srate != 0 && stream.info.channel_format.compare("string")) // filter the string streams
322         {
323             int startingPosition = (stream.info.first_timestamp - XDFdata->minTS) * XDFdata->majSR;
324 
325             if (stream.time_series.front().size() > XDFdata->totalLen - startingPosition )
326                 startingPosition = XDFdata->totalLen - stream.time_series.front().size();
327 
328             for (auto &row : stream.time_series)
329             {
330                 ProgressBar::instance().increaseValue (1, progress_name);
331 
332                 QSharedPointer<QVector<float32> > raw_data(new QVector<float32> (numberOfSamples, NAN));
333 
334                 std::copy(row.begin(), row.end(), raw_data->begin() + startingPosition);
335                 QSharedPointer<DataBlock const> data_block(new FixedDataBlock(raw_data, XDFdata->majSR));
336 
337                 channel_map_[channel_id] = data_block;
338                 channel_id++;
339 
340                 std::vector<float> nothing;
341                 row.swap(nothing);
342             }
343             std::vector<double> nothing2;
344             stream.time_stamps.swap(nothing2);
345         }
346         //else if: irregualar samples
347         else if (stream.info.nominal_srate == 0 && !stream.time_series.empty())
348         {
349             for (auto &row : stream.time_series)
350             {
351                 ProgressBar::instance().increaseValue (1, progress_name);
352 
353                 QSharedPointer<QVector<float32> > raw_data(new QVector<float32> (numberOfSamples, NAN));
354 
355                 for (size_t i = 0; i < row.size(); i++)
356                 {
357                     //find out the position using the timestamp provided
358                     float* pt = raw_data->begin()  + (int)(round((stream.time_stamps[i]- XDFdata->minTS)* XDFdata->majSR));
359                     *pt = row[i];
360 
361                     //if i is not the last element of the irregular time series
362                     if (i != stream.time_stamps.size() - 1)
363                     {
364                         //using linear interpolation to fill in the space between every two signals
365                         int interval = round((stream.time_stamps[i+1]
366                                 - stream.time_stamps[i]) * XDFdata->majSR);
367                         for (int interpolation = 1; interpolation <= interval; interpolation++)
368                         {
369                             *(pt + interpolation) = row[i] + interpolation * ((row[i+1] - row[i])) / (interval + 1);
370                         }
371                     }
372                 }
373                 QSharedPointer<DataBlock const> data_block(new FixedDataBlock(raw_data, XDFdata->majSR));
374                 channel_map_[channel_id] = data_block;
375                 channel_id++;
376 
377                 std::vector<float> nothing;
378                 row.swap(nothing);
379             }
380             std::vector<double> nothing2;
381             stream.time_stamps.swap(nothing2);
382         }
383     }
384 
385     buffered_all_channels_ = true;
386 }
387 
388 //-------------------------------------------------------------------------
bufferAllEvents() const389 void XDFReader::bufferAllEvents () const
390 {
391     unsigned number_events = XDFdata->eventMap.size();
392 
393     double eventSampleRate = 0;
394 
395     if (XDFdata->fileEffectiveSampleRate)
396         eventSampleRate = XDFdata->fileEffectiveSampleRate;
397     else
398         eventSampleRate = XDFdata->majSR;
399 
400     for (unsigned index = 0; index < number_events; index++)
401     {
402         QSharedPointer<SignalEvent> event
403                 (new SignalEvent (round ((XDFdata->eventMap[index].first.second - XDFdata->minTS) * eventSampleRate),
404                                   XDFdata->eventType[index] + 1,//index+1 because in SigViewer and libbiosig
405                                   //0 is reserved for a special type of event. Thus we increment by 1
406                                   eventSampleRate, XDFdata->eventMap[index].second));
407 
408         event->setChannel (UNDEFINED_CHANNEL);
409         event->setDuration (0);
410         events_.append (event);
411     }
412 
413     buffered_all_events_ = true;
414 }
415 
416 }
417