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