1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
|
//libxdf is a static C++ library to load XDF files
//Copyright (C) 2017 Yida Lin
//This program is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.
//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//GNU General Public License for more details.
//You should have received a copy of the GNU General Public License
//along with this program. If not, see <http://www.gnu.org/licenses/>.
//If you have questions, contact author at yida.lin@outlook.com
/*! \file xdf.h
* \brief The header file of Xdf class
*/
#ifndef XDF_H
#define XDF_H
#include <string>
#include <vector>
#include <map>
#include <set>
/*! \class Xdf
*
* Xdf class is designed to store the data of an entire XDF file.
* It comes with methods to read XDF files and containers to store
* the data, as well as some additional methods e.g. resampling etc.
*/
class Xdf
{
public:
//! Default constructor with no parameter.
Xdf();
//subclass for single streams
/*! \class Stream
*
* XDF files uses stream as the unit to store data. An XDF file usually
* contains multiple streams, while each of them may contain one or more
* channels.
* The Stream class provides a handy way to store all the meta-data,
* time-series, time-stamps and all other information of a single stream
* from an XDF file.
*/
struct Stream
{
//! A 2D vector which stores the time series of a stream. Each row represents a channel.
std::vector<std::vector<float> > time_series;
std::vector<double> time_stamps; /*!< A vector to store time stamps. */
std::string streamHeader; /*!< Raw XML of stream header chunk. */
std::string streamFooter; /*!< Raw XML of stream footer chunk. */
struct
{
int channel_count; /*!< Number of channels of the current stream */
double nominal_srate; /*!< The nominal sample rate of the current stream. */
std::string name; /*!< Name of the current stream. */
std::string type; /*!< Type of the current stream. */
std::string channel_format;/*!< Channel format of the current stream. */
std::vector<std::map<std::string, std::string> > channels;/*!< A vector to store the meta-data of the channels of the current stream. */
std::vector<std::pair<double, double> > clock_offsets; /*!< A vector to store clock offsets from the ClockOffset chunk. */
double first_timestamp; /*!< First time stamp of the stream. */
double last_timestamp; /*!< Last time stamp of the stream. */
int sample_count; /*!< Sample count of the stream. */
double measured_srate; /*!< Measured sample rate of the stream. */
double effective_sample_rate = 0;/*!< Effective sample rate. */
} info; /*!< Meta-data from the stream header of the current stream. */
double last_timestamp{ 0 }; /*!< For temporary use while loading the vector */
double sampling_interval; /*!< If srate > 0, sampling_interval = 1/srate; otherwise 0 */
std::vector<double> clock_times;/*!< Vector of clock times from clock offset chunk (Tag 4). */
std::vector<double> clock_values;/*!< Vector of clock values from clock offset chunk (Tag 4). */
};
//XDF properties=================================================================================
std::vector<Stream> streams; /*!< A vector to store all the streams of the current XDF file. */
float version; /*!< The version of XDF file */
uint64_t totalLen = 0; /*!< The total length is the product of the range between the smallest
*time stamp and the largest multiplied by the major sample rate. */
double minTS = 0; /*!< The smallest time stamp across all streams. */
double maxTS = 0; /*!< The largest time stamp across all streams. */
size_t totalCh = 0; /*!< The total number of channel count. */
int majSR = 0; /*!< The sample rate that has the most channels across all streams. */
int maxSR = 0; /*!< Highest sample rate across all streams. */
std::vector<double> effectiveSampleRateVector; /*!< Effective Sample Rate of each stream. */
double fileEffectiveSampleRate = 0; /*!< If effective sample rates in all the streams are the same, this is the value. */
std::vector<int> streamMap;/*!< A vector indexes which channels belong to which stream.
* The index is the same as channel number; the actual content is the stream Number */
/*!
* \brief Used as `std::vector<std::pair<std::pair<eventName, eventTimeStamp>, int> >`
* in eventMap.
* \sa eventMap
*/
typedef std::string eventName;
/*!
* \brief Used as `std::vector<std::pair<std::pair<eventName, eventTimeStamp>, int> >`
* in eventMap.
* \sa eventMap
*/
typedef double eventTimeStamp;
std::vector<std::pair<std::pair<eventName, eventTimeStamp>, int> > eventMap;/*!< The vector to store all the events across all streams.
* The format is <<events, timestamps>, streamNum>. */
std::vector<std::string> dictionary;/*!< The vector to store unique event types with no repetitions. \sa eventMap */
std::vector<uint16_t> eventType; /*!< The vector to store events by their index in the dictionary.\sa dictionary, eventMap */
std::vector<std::string> labels; /*!< The vector to store descriptive labels of each channel. */
std::set<double> sampleRateMap; /*!< The vector to store all sample rates across all the streams. */
std::vector<float> offsets; /*!< Offsets of each channel after using subtractMean() function */
std::string fileHeader; /*!< Raw XML of the file header. */
int userAddedStream { 0 }; /*!< For Sigviewer only: if user manually added events in Sigviewer,
* the events will be stored in a new stream after all current streams.
* The index will be userAddedStream. */
std::vector<std::pair<std::string, double> > userCreatedEvents;/*!< User created events in Sigviewer. */
//Public Functions==============================================================================================
/*!
* \brief Adjust `totalLen` to avoid possible deviation
*
* `totalLen` is calculated by multiplying the difference between max time
* stamp and minimal time stamp by the `majSR` (major sample rate).
* However, this can be inaccurate. In case any channel is longer than
* `totalLen`, this function will make `totalLen` match the length of
* that channel.
*
* \sa totalLen, majSR, calcTotalLength()
*/
void adjustTotalLength();
/*!
* \brief Calculate the globle length (in samples).
*
* This is calculated by multiplying the rage from the earliest
* time stamp to the last time stamp across all channels by the
* parameter sampleRate.
*
* \param sampleRate is the sample rate you wish to use to calculate the
* total length.
*/
void calcTotalLength(int sampleRate);
/*!
* \brief Create labels for each channel and store them in _labels_ vector.
* \sa labels, offsets
*/
void createLabels();
/*!
* \brief Subtract the entire channel by its mean.
*
* Sigviewer displays both the channel signals as well as the zero baseline.
* Thus when the mean of a channel is too high or too low it would be very
* hard to see the fluctuation. Subtract the entire channel by its mean
* will make the signal fluctuate around the zero baseline, and has better
* visual effect. The mean of each channel times `-1` will be stored in
* member vector `offsets`
*
* \sa offsets
*/
void detrend();
/*!
* \brief Delete the time stamps vectors when no longer needed to
* release some memory.
*
* Sigviewer doesn't demand time stamps to display signals except
* irregular sample rate channels, events, and the first time stamp
* of each channel (used to decide where does a channel start when
* putting all streams together). In this case we can delete the time
* stamps when no longer needed to free up some memory.
*/
void freeUpTimeStamps();
/*!
* \brief The main function of loading an XDF file.
* \param filename is the path to the file being loaded including the
* file name.
*/
int load_xdf(std::string filename);
/*!
* \brief Resample all streams and channel to a chosen sample rate
* \param userSrate is recommended to be between integer 1 and
* the highest sample rate of the current file.
*/
void resample(int userSrate);
/*!
* \brief syncTimeStamps
*/
void syncTimeStamps();
/*!
* \brief writeEventsToXDF
*
* If user added some markups and events in Sigviewer, this function can
* store those user created events back to the XDF file in a new stream
*/
int writeEventsToXDF(std::string file_path);
//Private Functions: Not intended to be used by external programs======================================
private:
/*!
* \brief calcEffectiveSrate
*/
void calcEffectiveSrate();
/*!
* \brief Calculate the total channel count and store the result
* in `totalCh`.
*
* Channels of both regular and irregular sample rates are included.
* The streams with the channel format `string` are excluded, and are
* stored in `eventMap` instead.
*
* \sa totalCh, eventMap
*/
void calcTotalChannel();
/*!
* \brief Find the sample rate that has the most channels.
*
* XDF format supports different sample rates across streams, but
* Sigviewer needs to display all channels in a unified sample rate.
* Thus if there are more than one sample rate in the file, some channels
* need to be resampled in order to be displayed.
*
* Libxdf uses third party _smarc_ library to do the resampling task.
* While _smarc_ library is powerful in the sense that it can resample
* signals to almost any sample rate, it's fairly slow, and for performance
* reason it's better to minimize the resampling process.
*
* findMajSR() will find which sample rate currently has the most channels
* in the file. If only those channels with a different sample rate are to
* be resampled, the resampling process will be finished in the shortest
* possible period.
*
* \sa majSR, resample(int userSrate)
*/
void findMajSR();
/*!
* \brief Find the minimal and maximal time stamps across all streams.
*
* The results will be stored in member variables `minTS` and `maxTS` respectively.
* \sa minTS, maxTS, calcTotalLength(int sampleRate);
*/
void findMinMax();
/*!
* \brief Get the highest sample rate of all streams and store
* it in `maxSR`.
*
* \sa maxSR
*/
void getHighestSampleRate();
/*!
* \brief Copy all unique types of events from _eventMap_ to
* _dictionary_ with no repeats.
* \sa dictionary, eventMap
*/
void loadDictionary();
/*!
* \brief Load every sample rate appeared in the current file into
* member variable `sampleRateMap`.
* \sa sampleRateMap
*/
void loadSampleRateMap();
/*!
* \brief This function will get the length of the upcoming chunk, or the number of samples.
*
* While loading XDF file there are 2 cases where this function will be
* needed. One is to get the length of each chunk, one is to get the
* number of samples when loading the time series (Chunk tag 3).
* \param file is the XDF file that is being loaded in the type of `std::ifstream`.
* \return The length of the upcoming chunk (in bytes).
*/
uint64_t readLength(std::ifstream &file);
};
#endif // XDF_H
|