Difference between revisions of "AnyWave:DeveloperCorner"

From WikiMEG
Jump to: navigation, search
(Open and read the data)
 
(24 intermediate revisions by one user not shown)
Line 1: Line 1:
 +
__NOTOC__
 
=Welcome=
 
=Welcome=
 
This part of the Wiki is dedicated to developers who would like to implement their own plug-ins for AnyWave.<br />
 
This part of the Wiki is dedicated to developers who would like to implement their own plug-ins for AnyWave.<br />
AnyWave is written using the Qt Framework and the Qt plugin mechanism, so a good knowledge of the Qt Framework is required.
+
{| style="text-align: center; margin: auto;"
 
+
|+ Quick Navigation
=The SDK=
+
|-
If you have installed AnyWave on your system, you will find all the required files in the installation folder.<br />
+
| [[File:Menu_matlab_plugin.png|400px|link=AnyWave:MATLAB_Plugin|'''Write a MATLAB Plugin''']] || [[File:Menu_cpp_plugin.png|400px|link=AnyWave:Cpp_Plugin|'''Write a c++ Plugin''']]
==Linux==
+
|-
Build from sources following the instructions on our [https://gitlab.thevirtualbrain.org/anywave Gitlab].<br />
+
| [[File:Menu_matlab_batch.png|400px|link=AnyWave:Plugin_Batch|'''Make your plugin batchable''']] || [[File:Menu_matlab_batch_gui_compatible.png|400px|link=AnyWave:MATLAB_Batch_GUI|'''Make your plugin compatible with the batch GUI of AnyWave''']]
Considering the default installation path, the requires folders to build a plugin are:<br />
+
|}
* '''/usr/local/AnyWave/include'''
+
* '''/usr/local/AnyWave/lib'''
+
==Mac OS==
+
The required folders to build a plugin are:<br />
+
* '''/Applications/AnyWave.app/Contents/include'''
+
* '''/Applications/AnyWave.app/Contents/dylibs'''
+
 
+
==Windows==
+
The required folders to build a plugin are:<br />
+
* '''AnyWave\include'''
+
* '''AnyWave\lib'''
+
 
+
=Basic requirements to build a plugin=
+
We strongly recommend QtCreator as the tool to use which is available along with the Qt open source package you will need to build a plugin. <br/>
+
Download Qt and Qt Creator here: [https://www.qt.io/download Get Qt and Qt Creator] <br/>
+
'''Note about Windows:'''<br/>
+
The Windows version of AnyWave is built with Visual Studio 2017 though, so, if you plan to build a plugin for Windows, consider using Visual Studio along with the Qt VS Addin.<br/>
+
Technically speaking you can build a plugin with QtCreator using another compiler like '''gcc''' or '''clang'''.<br/>
+
You will need to put the runtime DLL files of the chosen compiler into the AnyWave folder after you copied the plugin into the Plugins subdirectory.
+
 
+
==Prepare a project==
+
Every Qt project starts with a .pro file which is the format used by qmake, the tool for building Qt projects.<br/>
+
Building an AnyWave plugin requires that the paths to AnyWave headers and libraries must be set.<br/>
+
A good practice is to set an environment variable called '''AW_ROOT''' that points to the root folder of your AnyWave installation.<br />
+
Example:<br/>
+
<syntaxhighlight lang="text">
+
CONFIG += release warn_off c++11 plugin
+
ROOT=$$(AW_ROOT)
+
# on Mac OS the required folders are located inside the application bundle.
+
macx{
+
ROOT = $$(AW_ROOT)/Contents
+
}
+
INCLUDEPATH += $$ROOT/include
+
LIBS += -L$$ROOT/lib
+
DESTDIR = $$ROOT/Plugins
+
 
+
# Mac OS application has a separated folder for Plugins.
+
macx {
+
DESTDIR = $$ROOT/../../Anywave_Plugins
+
# shared libs on Mac are located in the application bundle.
+
LIBS += -F/Library/Frameworks -L$$ROOT/dylibs
+
}
+
</syntaxhighlight>
+
You may use this file as .pri that you could include in every project you plan to develop:<br/>
+
[http://meg.univ-amu.fr/AnyWave/sdk/plugins.pri Get plugins.pri file]
+
 
+
=Build a reader plugin=
+
As a tutorial, we are going to develop here a reader plugin called "My Reader".<br/>
+
Each Qt project starts with a .pro file which qmake use to compile and link a Qt project.<br/>
+
A Reader plugin is a Qt shared library that should be placed in the Plugins directory of AnyWave.<br/>
+
==Step 1: Prepare the folder==
+
Let's create a folder in which we will put the files required to build our plugin:<br/>
+
<syntaxhighlight lang="bash">
+
mkdir MyReader && cd MyReader
+
</syntaxhighlight>
+
 
+
==Step 2: create the project file==
+
Now we can create the .pro file for our project:<br>
+
<syntaxhighlight lang="text">
+
# We assume that the plugins.pri file is also in our plugin directory.
+
include(plugins.pri)
+
# The name of our plugin
+
TARGET=MyReader
+
# Our project must be a library
+
TEMPLATE = lib
+
# Just to avoid unwanted libs, Qt GUI is not usefull for a reader plugin (no widgets/no graphics items).
+
QT -= gui
+
 
+
# We must link with some AnyWave libs
+
#  AwRW is the AnyWave ReadWrite lib.
+
unix{
+
LIBS += -lAwRW 
+
}
+
win32{
+
LIBS += AwReadWriteLib.lib
+
}
+
 
+
HEADERS += MyReader.h
+
SOURCES += MyReader.cpp
+
</syntaxhighlight>
+
 
+
==Step 3: Let's code==
+
The header file:<br/>
+
<syntaxhighlight lang="cpp">
+
#pragma once
+
#include <AwFileIO.h> // AnyWave base class header
+
#include <QFile>      // Qt QFile object
+
 
+
class Q_DECL_EXPORT MyReader : public AwFileIO
+
{
+
  Q_OBJECT
+
  Q_INTERFACES(AwFileIO)
+
public:
+
  explicit MyReader(const QString& fileName);
+
  ~MyReader();
+
 
+
  qint64 readDataFromChannels(float start, float duration, QList<AwChannel *>& channels) override;
+
  AwFileIO::FileStatus openFile(const QString &path) override;
+
  AwFileIO::FileStatus canRead(const QString &path) override;
+
  void cleanUpAndClose() override;
+
protected:
+
// keep a file object to read data
+
  QFile m_dataFile;
+
};
+
 
+
class Q_DECL_EXPORT MyReaderPlugin : public AwFileIOPlugin
+
{
+
  Q_OBJECT
+
  Q_INTERFACES(AwFileIOPlugin)
+
  Q_PLUGIN_METADATA(IID AwFileIOInterfacePlugin_IID)
+
public:
+
  MyReaderPlugin();
+
  MyReader *newInstance(const QString& fileName) { return new MyReader(fileName); }
+
};
+
 
+
</syntaxhighlight>
+
The header file declares two classes: One is the reader object itself and the other one is the plugin object.<br/>
+
The methods declared with the override keyword MUST be implemented otherwise the compiler will complain and your plugin won't be a valid AnyWave plugin.<br/>
+
===constructor/destructor (init our plugin)===
+
Let's have a closer look at methods we have to implement:<br/>
+
<syntaxhighlight lang="cpp">
+
explicit MyReader(const QString& fileName);
+
~MyReader();
+
</syntaxhighlight>
+
The constructor/destructor methods. This is where you initialise the member variables and handle the destruction by releasing allocated memory and open files.<br/>
+
Let's see the code for our constructor:<br/>
+
<syntaxhighlight lang="cpp">
+
#include "MyReader.h"
+
 
+
// constructor for reader object
+
MyReader::MyReader(const QString& fileName) : AwFileIO(fileName)
+
{
+
  // do some inits here.
+
}
+
 
+
// constructor of Plugin object
+
// Here we will define the name of our Plugin and it's properties/flags for AnyWave
+
MyReaderPlugin::MyReaderPlugin() : AwFileIOPlugin()
+
{
+
  // define the name (must be unique in AnyWave)
+
  name = QString("MyReader Plugin");
+
  // short description of what the plugin does.
+
  description = QString("Read My Format");
+
  // info about manufacturer/author
+
  manufacturer = QString("Myself");
+
  // version string
+
  version = QString("1.0");
+
  // the file extensions the reader can open.
+
  fileExtensions = { ".myr" };
+
  // Flags to define the behavior in AnyWave
+
  m_flags = Aw::HasExtension|Aw::CanRead; // Here we tell AnyWave that the plugin handle file extension (mostly the case)
+
  // and we also specify CanRead flag to tell AnyWave that this is a READER plugin.
+
}
+
</syntaxhighlight>
+
===Open and read the data===
+
Now we have to implement the three methods that will allow our plugin to open and read data from a file:<br/>
+
First, we'll code the canRead method:<br/>
+
<syntaxhighlight lang="cpp">
+
#include "MyReader.h"
+
#include <QFile>
+
 
+
///
+
/// canRead : use to open a file and check if the format is ok.
+
/// Returns a flag informing AnyWave that we can or can't read that file.
+
AwFileIO::FileStatus MyReader::canRead(const QString& fileName)
+
{
+
    // Here we won't really open the file, we are just going to check if we can.
+
 
+
  // Let's suppose or .myr format is a text header file where the first line is a string with the following keyword "MYFORMAT".
+
 
+
  QFile file(fileName);
+
  if (file.open(QIODevice::ReadOnly|QIODevice::Text) // try to open as a text file.
+
  {
+
      QString line = file.readLine(); // read the first line. 
+
      file.close();
+
      if (line == "MYFORMAT") {
+
          return AwFileIO::NoError;  // it's OUR format!
+
      }
+
  }
+
  return AwFileIO::WrongFormat;  // We don't know this file format.
+
}
+
</syntaxhighlight>
+
Now we must implement the openFile method. This is where we'll inform AnyWave about what is available in the file (channels, markers): <br/>
+
<syntaxhighlight lang="cpp">
+
#include "MyReader.h"
+
#include <QFile>
+
 
+
AwFileIO::FileStatus MyReader::openFile(const QString& fileName)
+
{
+
    // open the text header file and get informations about what is present in the data file.
+
    // We suppose that a binary data file is associated to the header file (like in the ADES format).
+
    QFile file(fileName);
+
    if (!file.open(QIODevice::ReadOnly|QIODevice::Text)  {
+
      // failed to open the header file: provide an error message and quit.
+
      m_error = QString("Failed to open the file.");
+
      return AwFileStatus::FileAccess;
+
    }
+
    // read first line (remember MYFORMAT tag)
+
  file.readLine();
+
  // the second line contains the global sampling rate of data
+
  // We store that information in a member variable for further use in readDataFromChannels.
+
  m_samplingRate = (float)file.readLine().toDouble();
+
  // the next line contains the total number of samples per channel.
+
  qint64 nSamples = file.readLine().toInt();
+
  // the next line contains the number of channels and their order in the data.
+
  int nChannels = file.readLine().toInt();
+
  // the following lines contains all the electrode labels of the corresponding channels.
+
  // Use a list to store them
+
  QStringList labels, types;
+
  for (auto i = 0; i < nChannels; i++) // now get all the channels label
+
      labels.append(file.readLine());
+
  // the following lines (nChannels) contains the types of the channels (EEG, SEEG, Other, ECG, ...)
+
  // We suppose the types are compatible with the ones AnyWave can handle.
+
  for (auto i = 0; i < nChannels; i++)
+
      types.append(file.readLine());
+
 
+
  // ok now we can add the channels to the info object of our plugin:
+
  for (auto i = 0; i < nChannels; i++) {
+
      AwChannel channel;
+
      channel.setName(labels.at(i));
+
      channel.setType(AwChannel::stringToType(types.at(i)));
+
      channel.setSamplingRate(samplingRate);
+
      // adjust the unit and default gain depending on type:
+
      if (channel.isEEG() || channel.isSEEG())
+
        channel.setUnit(QString::fromLatin1("µv"));
+
      if (channel.isMEG() || channel.isReference())
+
        channel.setUnit("pT");
+
 
+
      infos.addChannel(channel);
+
  }
+
  // A Block is the basic data set object AnyWave uses.
+
  AwBlock *block = infos.newBlock(); // instantiate a new data set using the infos object (a member of AwFileIO).
+
  // set the number of samples in the block
+
  block->setSamples(nSamples);
+
  // set also the duration in seconds
+
  block->setDuration(nSamples / samplingRate);
+
 
+
  // Done
+
  file.close();
+
  // we suppose the binary file has got he same filename but a .dat extension.
+
  // So open our binary file for further use (in the readDataFromChannels method).
+
  // replace .myr extension with .dat
+
  QString dataFileName = fileName;
+
  dataFileName.replace(QString(".myr"), QString(".dat");
+
 
+
  if (!m_dataFile.open(QIODevice::ReadOnly)) {
+
      m_error = "Could not open the binary file.";
+
      return AwFileIO::FileAcess
+
  }
+
 
+
  // we're ok the binary file is open. 
+
  return AwFileIO::NoError;
+
}
+
</syntaxhighlight>
+
 
+
Now the important part: reading the data.<br/>
+
<syntaxhighlight lang="cpp">
+
#include "MyReader.h"
+
 
+
///
+
/// readDataFromChannels expects a position in the file (start) and a duration in seconds (duration).
+
/// It will read the data for the requested channels (channels).
+
/// Returns the number of samples read for a channel.
+
/// Returns 0 if no data could be loaded.
+
qin64 MyReader::readDataFromChannels(float start, float duration, AwChannelList& channels)
+
{
+
  // basic checking
+
  if (channels.isEmpty()) 
+
      return;
+
 
+
  // number of samples to read
+
  qint64 nSamples = (qint64)floor(duration * m_samplingRate);
+
  // offset sample in channel
+
  qint64 nStart = (qint64)floor(start * m_samplingRate);
+
  // total number of channels in the file:
+
  auto nChannels = infos.channelsCount();
+
  // Starting sample in the file.
+
  qint64 startSample = nStart * nChannels;
+
 
+
  // check positions and samples
+
  if (nSamples <= 0 || nStart > infos.totalSamples())
+
      return 0;
+
 
+
  // check if the length requested goes beyond the file limit:
+
  if (nStart + nSamples > infos.totalSamples())
+
      nSamples = info.totalSamples() - nStart;  // reduce the number of samples to read to match the file limit.
+
 
+
  // total number of samples to read
+
  qint64 totalSize = nSamples * nChannels;
+
 
+
  // instantiate a buffer to read samples
+
  float *buf = new float[totalSize];
+
  // Set the file position
+
  m_dataFile.seek(startSample * sizeof(float));
+
  // read data
+
  qint64 = m_dataFile.read((char *)buf, totalSize * sizeof(float));
+
  if (read <= 0) {
+
      delete[] buf;
+
      return 0;
+
  }
+
 
+
  // compute the real number of samples successfully read.
+
  read /= sizeof(float);
+
  read /= nChannels;
+
   
+
  // ok now fill the data vector of the requested channels:
+
  for (auto c : channels) {
+
      int index = infos.indexOfChannel(c->name());
+
      float *data = c->newData(read); // replace the current data vector of the channel by a new one. The size of vector is the number of samples read.
+
      qint64 count = 0;
+
      while (count++ < read) // get the data from the buffer for the current channel.
+
        *data++ = (float)buf[index + count * nChannels];
+
  }
+
  // Done, free the buffer and finish.
+
  delete[] buf;
+
  return read;
+
}
+
 
+
</syntaxhighlight>
+
 
+
=Build a signal processing plug-in=
+
==[[AnyWave:WriteMatlabScripted|How to write a MATLAB plug-in]]==
+
===[[AnyWave:MATLAB_API|MATLAB API]]===
+
 
+
==[[AnyWave:WritePythonScripted|How to write a Python Scripted plug-in]]==
+

Latest revision as of 16:14, 21 April 2020

Welcome

This part of the Wiki is dedicated to developers who would like to implement their own plug-ins for AnyWave.

Quick Navigation
Write a MATLAB Plugin Write a c++ Plugin
Make your plugin batchable Make your plugin compatible with the batch GUI of AnyWave