Difference between revisions of "AnyWave:BuildReader"

From WikiMEG
Jump to: navigation, search
(Created page with "Even though college can be overwhelming, it can give you a great experience. This article has a number of tips to help you make the transition smoothly so you can be successfu...")
 
 
(32 intermediate revisions by one user not shown)
Line 1: Line 1:
Even though college can be overwhelming, it can give you a great experience. This article has a number of tips to help you make the transition smoothly so you can be successful. Use this advice and that of loved ones carefully. These decisions can affect your future.<br><br>Make sure you are up on all the different scholarships and grants you may be eligible for. A lot of people don't even know that they can get financial help because of a unique factor; even left handed people have their own scholarships in some places! Also check into forgivable loans and government grants.<br><br>If hitting an academic stride has been a challenge, check for any available study skills seminars. College courses are different from high school and require a transition. Study classes can provide excellent tips on how to study effectively so that you can achieve success in class.<br><br>Remember to watch your food intake. There's nothing fun about the freshman 15! Make sure to be mindful of your eating. The lure of fast food which is so available at college can be hard to resist. The foods may be low in cost initially, but these costs add up, and so do the pounds they create.<br><br>Think about a study skills class if you find yourself having trouble learning. Studying for college is much different than high school, which might impact your grades. A study skills class can set you on the path to success in your classes.<br><br>Your surroundings can make a huge difference when you are trying to study. You should go out to study; your room might not be the best place for it. Instead, seek out a quite location where you can avoid potential interruptions. If you loved this post and you would certainly such as to obtain even more information relating to online phlebotomy classes - [http://pisc.es/onlineclassesforphlebotomy http://pisc.es] - kindly browse through the web page. Usually, the best choice is the library. If you can't always go to the library, then consider buying some headphones that block out noise.<br><br>Take a bus to campus. Taking the bus to school is quick, easy and free in most college towns. You can cut out the time normally spent on searching for parking spots. If you want to drive your own car, expect to pay for a parking pass. It's environmentally friendly, too.<br><br>You are responsible for cooking and cleaning. Make sure to eat nutritious foods, get adequate sleep and clean up after yourself. Establish a schedule that allows you to get enough sleep, go to all your classes, study and spend some quality time with your friends. Bad habits and a bad diet will affect both your health and your grades.<br><br>Have a good relationship with professors. Professors represent an incredible resource and have lots of help to offer students. Also, ask questions if you are unsure of a certain topic. Creating a relationship with your professor will help you to get ahead in many ways, both during and after college.<br><br>Make time to study daily. Don't forget what college is really about! Promise yourself that you will study each and every day. While you may think you don't require studying one day, do it anyway. It can help you create a habit.<br><br>If coffee is your passion, try not to purchase it each and every day. The cost really adds up! Brew up your own coffee. You are going to save a lot of cash, even if it isn't as convenient as buying it. The amount of money you save will allow you to purchase a great coffee machine.<br><br>After reading this article, do you feel ready to go back to college? You are not alone. Many people struggle with making college decisions. Do not let fear impede your success. There are countless people who have done it successfully, and you can, too.
+
One important thing with AnyWave is to be able to read a data file.<br />
 +
Although that AnyWave is able to read some common '''EEG''' or '''MEG''' file formats, you might need to read a particular data file.<br />
 +
The only way to achieve that is to build a Reader plug-in for AnyWave.<br />
 +
This will require implementing a C++ plug-in using the SDK.<br />
 +
See the previous sections of the [[AnyWave:DeveloperCorner|Developer's corner]] to see how to build the SDK and use it to build a new plug-in.<br />
 +
 
 +
Let's start with the [http://meg.univ-amu.fr/AnyWave/tuto/CMakeLists.txt basic cmake project] that we will modify to suit our needs.
 +
 
 +
=ADES reader as example=
 +
AnyWave is able to read .ades file format which is a simple file format built upon a text header file and a binary data file.<br />
 +
This 'plug-in' is embedded within AnyWave but we are going to implement it as an external plug-in, for the sake of demonstration.
 +
 
 +
==Define the C++ classes==
 +
A C++ plug-in is defined by two classes:<br />
 +
* A class that describes the plug-in to AnyWave.
 +
* A class that describes the core mechanism of the plug-in.
 +
 
 +
That is true for all kind of plug-ins in AnyWave.
 +
 
 +
A good knowledge of the Qt Framework is required.<br />
 +
 
 +
It's a good start to read the [[AnyWave:AwObjects|Get Used with AnyWave C++ objects]] section.
 +
 
 +
===AwReaderPlugin and AwFileReader classes===
 +
As mentioned above, two C++ classes must be described and implemented to build a Reader plug-in.<br />
 +
Here are the two classes, described within one header file:
 +
<syntaxhighlight lang="cpp">
 +
#ifndef DESREADER_H
 +
#define DESREADER_H
 +
 
 +
#include <AwReaderInterface.h>
 +
#include <QtCore>
 +
#include <QDataStream>
 +
 
 +
class ADesReader : public AwFileReader // our class must derived from AwFileReader
 +
{
 +
Q_OBJECT                            // Q_OBJECT and Q_INTERFACES macro are specific to the Qt Framework.
 +
Q_INTERFACES(AwFileReader)          // They indicate that the object is also derived from QObject and implements the Qt signals and slots mechanism.
 +
public:
 +
ADesReader(const QString& filename);
 +
~ADesReader() { cleanUpAndClose(); }
 +
 
 +
// The three methods above need to be implemented. There are virtuals method defined in AwFileReader.
 +
long readDataFromChannels(float start, float duration, QList<AwChannel *> &channelList);    // This method will get data from the file and fill channels with them.
 +
FileStatus openFile(const QString &path);                                                    // This method will open the file and return a status.
 +
FileStatus canRead(const QString &path);                                                    // This method will check if the file format is correct and return a status.
 +
// The method above is optional but it is a good practice to implement it.
 +
        void cleanUpAndClose();              //  A cleanup method to clean memory and close the file.
 +
 
 +
 +
private:
 +
// Here we define variables and methods required by our plug-in.
 +
QFile m_headerFile;                  // A QFile to handle the text header file.
 +
QFile m_binFile;                    // A QFile to handle the binary data file.
 +
QTextStream m_headerStream;          // A stream object to read the content of the header file.
 +
QDataStream m_binStream;            // A stream object to read the content of the data file.
 +
float m_samplingRate;                // A variable to store the global sampling rate.
 +
int m_nSamples;                      // A variable to store the number of samples.
 +
QString m_binPath;                  // A variable to store the file path to the binary data file.
 +
 
 +
       
 +
};
 +
 
 +
class ADesReaderPlugin : public AwReaderPlugin  // The plugin class must derived from AwReaderPlugin
 +
{
 +
Q_OBJECT                            // Define the object as a QObject. The interface for the plugin must be AwReaderPlugin
 +
Q_INTERFACES(AwReaderPlugin)
 +
public:
 +
ADesReaderPlugin();                // The constructor
 +
ADesReader *newInstance(const QString& filename) { return new ADesReader(filename); }  // MANDATORY method that must instantiate the AwFileReader derived class for our plug-in.
 +
};
 +
 
 +
#endif // DESREADER_H
 +
</syntaxhighlight>
 +
 
 +
Now the implementation:<br />
 +
<syntaxhighlight lang="cpp">
 +
#include "ADesReader.h"
 +
 
 +
// Plugin constructor
 +
ADesReaderPlugin::ADesReaderPlugin() : AwReaderPlugin()
 +
{
 +
  name = "ADES Reader";                            // give a name to our plugin. The name must be unique.
 +
  description = QString(tr("Open .ades files"));  // give a description about what format the plug-in will load.
 +
  version = QString("1.0");                        // Version information, not used for now.
 +
  fileExtensions << "*.ades";                      // Add extension filter four our format.
 +
  m_flags = Aw::ReaderHasExtension;                // Set flags to tell AnyWave about some features of our plug-in. Here we just inform that the plugin can handle file extensions.
 +
           
 +
}
 +
 
 +
 
 +
// FileReader constructor
 +
ADesReader::ADesReader(const QString& path) : AwFileReader(path)  // This is where we can initialize our FileReader derived object.
 +
{
 +
m_binStream.setVersion(QDataStream::Qt_4_4);  // Qt specific: setting the version of Qt to 4.4 for stream objects.
 +
m_samplingRate = 0;  // init variables.
 +
m_nSamples = 0;
 +
}
 +
 
 +
// The cleanUp Method
 +
void ADesReader::cleanUpAndClose()
 +
{
 +
m_headerFile.close();  // very simple: we close the two open files (header and data)
 +
m_binFile.close();
 +
}
 +
 
 +
 
 +
// canRead()
 +
// This method must check if the file path given is a valid file.
 +
// Because some file formats are using the same file extensions (.eeg, for example), we must be sure that our plug-in will open a compatible file.
 +
ADesReader::FileStatus ADesReader::canRead(const QString &path)
 +
{
 +
cleanUpAndClose();  // Cleanup first (just in case)
 +
 +
m_headerFile.setFileName(path);
 +
if (!m_headerFile.open(QIODevice::ReadOnly | QIODevice::Text))  // Try to open the header file.
 +
return AwFileReader::FileAccess;  // If failed, return FileAcess error status.
 +
 
 +
m_headerStream.setDevice(&m_headerFile);  // Setting up stream object to read the header file content.
 +
QString line = m_headerStream.readLine(); // Read the first line of header file
 +
m_headerFile.close();
 +
if (line.toUpper().startsWith("#ADES"))  // ADES format expects the first line to be #ADES
 +
return AwFileReader::NoError;    //  if the line is correct, return the NoError status.
 +
 +
return AwFileReader::WrongFormat;  // The header file is invalid.
 +
}
 +
 
 +
 
 +
// openFile()
 +
// This method will open the file path given as parameter.
 +
// Normally the file had been checked before by canRead() so we are sure to open a ADES file.
 +
ADesReader::FileStatus ADesReader::openFile(const QString &path)
 +
{
 +
cleanUpAndClose();  // clean up before, just in case.
 +
 
 +
m_headerFile.setFileName(path);
 +
m_headerStream.setDevice(&m_headerFile);
 +
 
 +
if (!m_headerFile.open(QIODevice::ReadOnly))    // open header file
 +
return AwFileReader::FileAccess;  // should not happen
 +
 
 +
 
 +
QList<QPair<QString, int> > labels;    // build a list of pairs. A channel is identified by a name and a type. Name will be stored as QString as type will be stored as integer.
 +
while (!m_headerStream.atEnd())  // Read header file until its end.
 +
{
 +
QString line = m_headerStream.readLine();  // get a line
 +
QStringList tokens = line.split("=");      // split the line around "=" symbol
 +
if (!tokens.isEmpty() && !line.startsWith("#"))  // Skip the line if it is empty or begins with the "#" symbol.
 +
{
 +
QString key = tokens.at(0);  // Parsing lines
 +
QString val;
 +
if (key.trimmed().toUpper() == "SAMPLINGRATE")  // extract sampling rate keyword
 +
m_samplingRate = tokens.at(1).toDouble();
 +
else if (key.trimmed().toUpper() == "NUMBEROFSAMPLES") // extract number of samples keyword
 +
m_nSamples = tokens.at(1).toInt();
 +
else // it is a channel  // Extract channel informat (Remember that channels are described by: channelName = Type
 +
{
 +
QPair<QString, int> pair; 
 +
pair.first = tokens.at(0).trimmed();
 +
if (tokens.size() == 2)
 +
pair.second = AwChannel::stringToType(tokens.at(1).trimmed());
 +
else
 +
pair.second = AwChannel::EEG;
 +
labels << pair;  // add a new channel pair to the list.
 +
}
 +
}
 +
}
 +
 +
if (labels.isEmpty() || m_samplingRate == 0. || m_nSamples == 0)  // if key values are missing or invalid, returns error status.
 +
return AwFileReader::WrongFormat;
 +
 
 +
for (int i = 0; i < labels.size(); i++)    // Now parsing the channel pairs list.
 +
{
 +
AwChannel chan;
 +
QPair<QString, int> pair = labels.at(i);
 +
chan.setName(pair.first);
 +
AwChannel *inserted = infos.addChannel(chan);  // insert a new AwChannel object into infos.
 +
inserted->setType(pair.second);                // setting the type
 +
inserted->setSamplingRate(m_samplingRate);      // setting the sampling rate (AnyWave expects that all channels have a sampling rate).
 +
switch (pair.second)  // setting gain and unit depending on channel type.
 +
{
 +
case AwChannel::EEG:
 +
inserted->setGain(150);
 +
inserted->setUnit("µV");
 +
break;
 +
case AwChannel::SEEG:
 +
inserted->setGain(300);
 +
inserted->setUnit("µV");
 +
break;
 +
case AwChannel::MEG:
 +
inserted->setGain((float)1E-12);    // IMPORTANT: MEG channels are expected to be expressed in pT.
 +
inserted->setUnit("pT");
 +
break;
 +
}
 +
}
 +
 
 +
AwBlock *block = infos.newBlock();  // Now that we add all the channels to the infos class of our plug-in we must add a block of data.
 +
        // AnyWave handle data by blocks. For now, only continous data, (only 1 block) are visualized by AnyWave. Anywave, it is already possible to internally handle multiple blocks.
 +
        // Future versions of AnyWave will permit to visualize epoched data.
 +
 
 +
block->setDuration((float)m_nSamples / m_samplingRate);  // A block must have a duration in seconds and a number of samples.
 +
block->setSamples(m_nSamples);
 +
 
 +
m_headerFile.close();  // We have done reading the header file. Close it.
 +
m_binPath = path;      // set the path to the binary file: the extension must be .dat
 +
m_binPath.replace(QString(".ades"), QString(".dat"));
 +
 +
m_binFile.setFileName(m_binPath);
 +
m_binStream.setDevice(&m_binFile);
 +
if (!m_binFile.open(QIODevice::ReadOnly))  // trying to open binary file.
 +
return AwFileReader::FileAccess;
 +
 
 +
        // An optional marker file could be present
 +
QString markerPath = path;
 +
markerPath.replace(QString(".ades"), QString(".mrk"));  // Setting path name for marker file (extension must be .mrk)
 +
if (QFile::exists(markerPath))  // Does the .mrk file exist?
 +
{      // Yes, so read it.
 +
QFile markerFile(markerPath);
 +
QTextStream stream(&markerFile);  // .mrk file is text file with tab separated values describing markers.
 +
if (markerFile.open(QIODevice::ReadOnly | QIODevice::Text))
 +
{
 +
AwMarkerList markers;  // create a list of markers
 +
while (!stream.atEnd())  // read the .mrk file til the end.
 +
{
 +
QString line = stream.readLine();
 +
line = line.trimmed();
 +
 
 +
// processing line and skip line starting with //
 +
if (!line.startsWith("//"))
 +
{
 +
QString label = line.section('\t', 0, 0);
 +
if (label.isEmpty()) // no label => skip line
 +
continue;
 +
QString value = line.section('\t', 1, 1);
 +
if (value.isEmpty())
 +
continue;
 +
QString position = line.section('\t', 2, 2);
 +
if (position.isEmpty())
 +
continue;
 +
QString duration = line.section('\t', 3, 3);
 +
 
 +
AwMarker m;  // create marker object.
 +
 
 +
m.setLabel(label);  // set marker attribudes (label, value, position and duration).
 +
m.setValue((qint16)value.toInt());
 +
m.setStart(position.toFloat());
 +
if (!duration.isEmpty())
 +
m.setDuration(duration.toFloat());
 +
 
 +
block->addMarker(m);  // add the marker to the current block of data.
 +
}
 +
}
 +
markerFile.close();
 +
}
 +
}
 +
 +
        // done
 +
return AwFileReader::NoError;
 +
}
 +
 
 +
 
 +
 
 +
//
 +
// readDataFromChannels()
 +
// This is the main method AnyWave will call to read data from the file.
 +
// AnyWave expects data to be represented by AwChannel objects. Each channel contains its own vector of data.
 +
// Therefore, to read data we must handle a list of channels, a position in the file and the amount of data to read.
 +
// Position and duration are expressed as seconds, not samples.
 +
long ADesReader::readDataFromChannels(float start, float duration, AwChannelList &channelList)
 +
{
 +
if (channelList.isEmpty())  // Ask to read an empty list => do nothing
 +
return 0;
 +
 
 +
if (duration <= 0)  // Duration of data is invalid => do nothing.
 +
return 0;
 +
 
 +
// number of samples to read
 +
qint64 nSamples = (qint64)floor(duration * m_samplingRate);
 +
// starting sample in channel.
 +
qint64 nStart = (qint64)floor(start * m_samplingRate);
 +
// total number of channels in file.
 +
qint32 nbChannels = infos.channelsCount();
 +
// starting sample in file.
 +
qint64 startSample = nStart * nbChannels;
 +
 
 +
if (nSamples <= 0)
 +
return 0;
 +
 
 +
if (nStart > infos.totalSamples())  // infos.totalSamples() will return the total number of samples in the file for one channel.
 +
return 0;
 +
 
 +
        // The binary file of ADES format contains multiplexed data with one sample as a 32bit float.
 +
        // That means we must first read all the channels data into a temporary buffer and then de-multiplexed the data to retrieve the vector data for a channel.
 +
 
 +
        float *buf = NULL;  // Buffer variable used to read data.
 +
int totalSize = nSamples * nbChannels;  // compute the total number of bytes to read into the buffer.
 +
buf = new float[totalSize]; // allocate buffer
 +
m_binFile.seek(startSample * sizeof(float));
 +
int read = m_binStream.readRawData((char *)buf, totalSize * sizeof(float));  // Read data
 +
read /= sizeof(float);
 +
 
 +
if (read == 0)    // check if read function succeeded or not.
 +
{
 +
delete [] buf;
 +
return 0;
 +
}
 +
 
 +
read /= nbChannels;
 +
 
 +
        // Browse the channel list
 +
foreach (AwChannel *c, channelList)
 +
{
 +
int index = infos.indexOfChannel(c->name());  // infos.indexOfChannel() will give the index of channel in data based on its name.
 +
float *data = c->newData(read);              // create a new vector of data for the channel.
 +
int count = 0;
 +
while (count < read)  // copy data from buffer to the channel.
 +
{
 +
*data++ = (float)buf[index + count * nbChannels];
 +
count++;
 +
}
 +
}
 +
        // done demultiplexing data for channels, so delete allocated buffer.
 +
delete[] buf;
 +
 
 +
return read;  // returns the number of bytes read
 +
}
 +
 
 +
// IMPORTANT:
 +
Q_EXPORT_PLUGIN2(ADesReader, ADesReaderPlugin)  // Add this macro so the Qt Framework will correctly build a Qt Plugin.
 +
// Typically the parameters are the name of the FileReader and the name of the plugin object.
 +
// It is up to the developer to name them correctly.
 +
</syntaxhighlight>
 +
 
 +
You can download a zip file containing all the required files to build our plug-in by clicking [http://meg.univ-amu.fr/AnyWave/tuto/BuildReader/ades_reader.zip here]
 +
 
 +
==Editing cmake project to build our reader==
 +
Starting with the basic cmake project file, only few modifications are required to build our reader plug-in:
 +
 
 +
<syntaxhighlight lang="cmake">
 +
 
 +
SET(SRCS
 +
${CMAKE_SOURCE_DIR}/ades_reader.cpp)  # Add our implementation file
 +
 
 +
SET(MOCS
 +
${CMAKE_SOURCE_DIR}/ades_reader.h)    # Remember that objects are derived from QObject and thus require the MOC tool to parse them.
 +
 
 +
qt4_wrap_cpp(ADES_MOCS ${MOCS})        # cmake will call Qt MOC tool and wrap moc file into variable ADES_MOCS
 +
 
 +
 
 +
add_library(ADESReader SHARED ${SRCS} ${ADES_MOCS})    # Add sources to the make rule.
 +
target_link_libraries(ADESReader AwCoreLib AwReadWriteLib ${QT_LIBRARIES})  # Add libraries to linker
 +
 
 +
</syntaxhighlight>
 +
 
 +
If the SDK is correctly installed an configured, just type '''cmake .''' in the source folder and our plug-in should build and install into the SDK folder.

Latest revision as of 15:28, 24 March 2015

One important thing with AnyWave is to be able to read a data file.
Although that AnyWave is able to read some common EEG or MEG file formats, you might need to read a particular data file.
The only way to achieve that is to build a Reader plug-in for AnyWave.
This will require implementing a C++ plug-in using the SDK.
See the previous sections of the Developer's corner to see how to build the SDK and use it to build a new plug-in.

Let's start with the basic cmake project that we will modify to suit our needs.

ADES reader as example

AnyWave is able to read .ades file format which is a simple file format built upon a text header file and a binary data file.
This 'plug-in' is embedded within AnyWave but we are going to implement it as an external plug-in, for the sake of demonstration.

Define the C++ classes

A C++ plug-in is defined by two classes:

  • A class that describes the plug-in to AnyWave.
  • A class that describes the core mechanism of the plug-in.

That is true for all kind of plug-ins in AnyWave.

A good knowledge of the Qt Framework is required.

It's a good start to read the Get Used with AnyWave C++ objects section.

AwReaderPlugin and AwFileReader classes

As mentioned above, two C++ classes must be described and implemented to build a Reader plug-in.
Here are the two classes, described within one header file:

#ifndef DESREADER_H
#define DESREADER_H
 
#include <AwReaderInterface.h>
#include <QtCore>
#include <QDataStream>
 
class ADesReader : public AwFileReader // our class must derived from AwFileReader
{
	Q_OBJECT                            // Q_OBJECT and Q_INTERFACES macro are specific to the Qt Framework.
	Q_INTERFACES(AwFileReader)          // They indicate that the object is also derived from QObject and implements the Qt signals and slots mechanism.
public:
	ADesReader(const QString& filename);
	~ADesReader() { cleanUpAndClose(); }
 
// The three methods above need to be implemented. There are virtuals method defined in AwFileReader.
	long readDataFromChannels(float start, float duration, QList<AwChannel *> &channelList);     // This method will get data from the file and fill channels with them.
	FileStatus openFile(const QString &path);                                                    // This method will open the file and return a status.
	FileStatus canRead(const QString &path);                                                     // This method will check if the file format is correct and return a status.
// The method above is optional but it is a good practice to implement it.
        void cleanUpAndClose();              //  A cleanup method to clean memory and close the file.
 
 
private:
// Here we define variables and methods required by our plug-in.
	QFile m_headerFile;                  // A QFile to handle the text header file.
	QFile m_binFile;                     // A QFile to handle the binary data file.
	QTextStream m_headerStream;          // A stream object to read the content of the header file.
	QDataStream m_binStream;             // A stream object to read the content of the data file.
	float m_samplingRate;                // A variable to store the global sampling rate.
	int m_nSamples;                      // A variable to store the number of samples.
	QString m_binPath;                   // A variable to store the file path to the binary data file.
 
 
};
 
class ADesReaderPlugin : public AwReaderPlugin  // The plugin class must derived from AwReaderPlugin
{
	Q_OBJECT                             // Define the object as a QObject. The interface for the plugin must be AwReaderPlugin
	Q_INTERFACES(AwReaderPlugin)
public:
	ADesReaderPlugin();                 // The constructor
	ADesReader *newInstance(const QString& filename) { return new ADesReader(filename); }   // MANDATORY method that must instantiate the AwFileReader derived class for our plug-in.
};
 
#endif // DESREADER_H

Now the implementation:

#include "ADesReader.h"
 
// Plugin constructor
ADesReaderPlugin::ADesReaderPlugin() : AwReaderPlugin()
{
  name = "ADES Reader";                            // give a name to our plugin. The name must be unique.
  description = QString(tr("Open .ades files"));   // give a description about what format the plug-in will load.
  version = QString("1.0");                        // Version information, not used for now.
  fileExtensions << "*.ades";                      // Add extension filter four our format.
  m_flags = Aw::ReaderHasExtension;                // Set flags to tell AnyWave about some features of our plug-in. Here we just inform that the plugin can handle file extensions.
 
}
 
 
// FileReader constructor
ADesReader::ADesReader(const QString& path) : AwFileReader(path)  // This is where we can initialize our FileReader derived object.
{
	m_binStream.setVersion(QDataStream::Qt_4_4);  // Qt specific: setting the version of Qt to 4.4 for stream objects.
	m_samplingRate = 0;   // init variables.
	m_nSamples = 0;
}
 
// The cleanUp Method
void ADesReader::cleanUpAndClose()
{
	m_headerFile.close();   // very simple: we close the two open files (header and data)
	m_binFile.close();
}
 
 
// canRead()
// This method must check if the file path given is a valid file.
// Because some file formats are using the same file extensions (.eeg, for example), we must be sure that our plug-in will open a compatible file.
ADesReader::FileStatus ADesReader::canRead(const QString &path)
{
	cleanUpAndClose();  // Cleanup first (just in case)
 
	m_headerFile.setFileName(path);
	if (!m_headerFile.open(QIODevice::ReadOnly | QIODevice::Text))   // Try to open the header file.
		return AwFileReader::FileAccess;   // If failed, return FileAcess error status.
 
	m_headerStream.setDevice(&m_headerFile);  // Setting up stream object to read the header file content.
	QString line = m_headerStream.readLine(); // Read the first line of header file
	m_headerFile.close(); 
	if (line.toUpper().startsWith("#ADES"))   // ADES format expects the first line to be #ADES
		return AwFileReader::NoError;     //  if the line is correct, return the NoError status.
 
	return AwFileReader::WrongFormat;   // The header file is invalid.
}
 
 
// openFile()
// This method will open the file path given as parameter.
// Normally the file had been checked before by canRead() so we are sure to open a ADES file.
ADesReader::FileStatus ADesReader::openFile(const QString &path)
{
	cleanUpAndClose();  // clean up before, just in case.
 
	m_headerFile.setFileName(path);
	m_headerStream.setDevice(&m_headerFile);
 
	if (!m_headerFile.open(QIODevice::ReadOnly))    // open header file
		return AwFileReader::FileAccess;  // should not happen
 
 
	QList<QPair<QString, int> > labels;    // build a list of pairs. A channel is identified by a name and a type. Name will be stored as QString as type will be stored as integer.
	while (!m_headerStream.atEnd())  // Read header file until its end.
	{
		QString line = m_headerStream.readLine();   // get a line
		QStringList tokens = line.split("=");       // split the line around "=" symbol
		if (!tokens.isEmpty() && !line.startsWith("#"))  // Skip the line if it is empty or begins with the "#" symbol. 
		{
			QString key = tokens.at(0);  // Parsing lines 
			QString val;
			if (key.trimmed().toUpper() == "SAMPLINGRATE")  // extract sampling rate keyword
				m_samplingRate = tokens.at(1).toDouble();
			else if (key.trimmed().toUpper() == "NUMBEROFSAMPLES") // extract number of samples keyword
				m_nSamples = tokens.at(1).toInt();
			else // it is a channel  // Extract channel informat (Remember that channels are described by: channelName = Type
			{
				QPair<QString, int> pair;  
				pair.first = tokens.at(0).trimmed();
				if (tokens.size() == 2)
					pair.second = AwChannel::stringToType(tokens.at(1).trimmed());
				else 
					pair.second = AwChannel::EEG;
				labels << pair;  // add a new channel pair to the list. 
			}	
		}
	}
 
	if (labels.isEmpty() || m_samplingRate == 0. || m_nSamples == 0)  // if key values are missing or invalid, returns error status.
		return AwFileReader::WrongFormat;
 
	for (int i = 0; i < labels.size(); i++)    // Now parsing the channel pairs list.
	{
		AwChannel chan;
		QPair<QString, int> pair = labels.at(i);
		chan.setName(pair.first);
		AwChannel *inserted = infos.addChannel(chan);   // insert a new AwChannel object into infos.
		inserted->setType(pair.second);                 // setting the type
		inserted->setSamplingRate(m_samplingRate);      // setting the sampling rate (AnyWave expects that all channels have a sampling rate).
		switch (pair.second)  // setting gain and unit depending on channel type.
		{
		case AwChannel::EEG:
			inserted->setGain(150);
			inserted->setUnit("µV");
			break;
		case AwChannel::SEEG:
			inserted->setGain(300);
			inserted->setUnit("µV");
			break;
		case AwChannel::MEG:
			inserted->setGain((float)1E-12);    // IMPORTANT: MEG channels are expected to be expressed in pT. 
			inserted->setUnit("pT");
			break;
		}
	}
 
	AwBlock *block = infos.newBlock();  // Now that we add all the channels to the infos class of our plug-in we must add a block of data.
        // AnyWave handle data by blocks. For now, only continous data, (only 1 block) are visualized by AnyWave. Anywave, it is already possible to internally handle multiple blocks.
        // Future versions of AnyWave will permit to visualize epoched data.
 
	block->setDuration((float)m_nSamples / m_samplingRate);  // A block must have a duration in seconds and a number of samples.
	block->setSamples(m_nSamples);
 
	m_headerFile.close();   // We have done reading the header file. Close it.
	m_binPath = path;       // set the path to the binary file: the extension must be .dat
	m_binPath.replace(QString(".ades"), QString(".dat"));
 
	m_binFile.setFileName(m_binPath);
	m_binStream.setDevice(&m_binFile);
	if (!m_binFile.open(QIODevice::ReadOnly))   // trying to open binary file.
		return AwFileReader::FileAccess;
 
        // An optional marker file could be present
	QString markerPath = path;
	markerPath.replace(QString(".ades"), QString(".mrk"));  // Setting path name for marker file (extension must be .mrk)
	if (QFile::exists(markerPath))  // Does the .mrk file exist?
	{       // Yes, so read it.
		QFile markerFile(markerPath); 
		QTextStream stream(&markerFile);  // .mrk file is text file with tab separated values describing markers.
		if (markerFile.open(QIODevice::ReadOnly | QIODevice::Text))
		{
			AwMarkerList markers;  // create a list of markers
			while (!stream.atEnd())  // read the .mrk file til the end.
			{
				QString line = stream.readLine();
				line = line.trimmed();
 
				// processing line and skip line starting with //
				if (!line.startsWith("//"))
				{
					QString label = line.section('\t', 0, 0);
					if (label.isEmpty()) // no label => skip line
						continue;
					QString value = line.section('\t', 1, 1);
					if (value.isEmpty())
						continue;
					QString position = line.section('\t', 2, 2);
					if (position.isEmpty())
						continue;
					QString duration = line.section('\t', 3, 3);
 
					AwMarker m;  // create marker object.
 
					m.setLabel(label);   // set marker attribudes (label, value, position and duration).
					m.setValue((qint16)value.toInt());
					m.setStart(position.toFloat());
					if (!duration.isEmpty())
						m.setDuration(duration.toFloat());
 
					block->addMarker(m);  // add the marker to the current block of data.
				}
			}
			markerFile.close();
		}
	}
 
         // done
	return AwFileReader::NoError;
}
 
 
 
//
// readDataFromChannels()
// This is the main method AnyWave will call to read data from the file.
// AnyWave expects data to be represented by AwChannel objects. Each channel contains its own vector of data.
// Therefore, to read data we must handle a list of channels, a position in the file and the amount of data to read.
// Position and duration are expressed as seconds, not samples.
long ADesReader::readDataFromChannels(float start, float duration, AwChannelList &channelList)
{
	if (channelList.isEmpty())  // Ask to read an empty list => do nothing
		return 0;
 
	if (duration <= 0)   // Duration of data is invalid => do nothing.
		return 0;
 
	// number of samples to read
	qint64 nSamples = (qint64)floor(duration * m_samplingRate);
	// starting sample in channel.
	qint64 nStart = (qint64)floor(start * m_samplingRate);
	// total number of channels in file.
	qint32 nbChannels = infos.channelsCount();
	// starting sample in file.
	qint64 startSample = nStart * nbChannels;
 
	if (nSamples <= 0) 
		return 0;
 
	if (nStart > infos.totalSamples())  // infos.totalSamples() will return the total number of samples in the file for one channel.
		return 0;
 
        // The binary file of ADES format contains multiplexed data with one sample as a 32bit float.
        // That means we must first read all the channels data into a temporary buffer and then de-multiplexed the data to retrieve the vector data for a channel.
 
        float *buf = NULL;  // Buffer variable used to read data.
	int totalSize = nSamples * nbChannels;  // compute the total number of bytes to read into the buffer.
	buf = new float[totalSize]; // allocate buffer
	m_binFile.seek(startSample * sizeof(float));
	int read = m_binStream.readRawData((char *)buf, totalSize * sizeof(float));  // Read data
	read /= sizeof(float);
 
	if (read == 0)     // check if read function succeeded or not.
	{
		delete [] buf;
		return 0;
	}
 
	read /= nbChannels;
 
        // Browse the channel list
	foreach (AwChannel *c, channelList)
	{
		int index = infos.indexOfChannel(c->name());  // infos.indexOfChannel() will give the index of channel in data based on its name.
		float *data = c->newData(read);               // create a new vector of data for the channel.
		int count = 0; 
		while (count < read)  // copy data from buffer to the channel.
		{
			*data++ = (float)buf[index + count * nbChannels];
			count++;
		}
	}
        // done demultiplexing data for channels, so delete allocated buffer.
	delete[] buf;
 
	return read;  // returns the number of bytes read 
}
 
// IMPORTANT:
Q_EXPORT_PLUGIN2(ADesReader, ADesReaderPlugin)   // Add this macro so the Qt Framework will correctly build a Qt Plugin. 
// Typically the parameters are the name of the FileReader and the name of the plugin object.
// It is up to the developer to name them correctly.

You can download a zip file containing all the required files to build our plug-in by clicking here

Editing cmake project to build our reader

Starting with the basic cmake project file, only few modifications are required to build our reader plug-in:

SET(SRCS
${CMAKE_SOURCE_DIR}/ades_reader.cpp)   # Add our implementation file
 
SET(MOCS
${CMAKE_SOURCE_DIR}/ades_reader.h)     # Remember that objects are derived from QObject and thus require the MOC tool to parse them.
 
qt4_wrap_cpp(ADES_MOCS ${MOCS})        # cmake will call Qt MOC tool and wrap moc file into variable ADES_MOCS
 
 
add_library(ADESReader SHARED ${SRCS} ${ADES_MOCS})    # Add sources to the make rule.
target_link_libraries(ADESReader AwCoreLib AwReadWriteLib ${QT_LIBRARIES})   # Add libraries to linker

If the SDK is correctly installed an configured, just type cmake . in the source folder and our plug-in should build and install into the SDK folder.