Creating a New Data Model
MNE Analyze uses the Model/View architecture from Qt to separate data storage from presentation. All data — FIFF files, annotations, averages — is represented as a model that views can display. This guide explains how to create a new data model for a custom data type.
Architecture Overview
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Data Loader │────►│ Data Manager │────►│ Views │
│ (loads files) │ │ (stores models) │ │ (display models) │
└──────────────────┘ └──────────────────┘ └──────────────────┘
│
┌──────▼──────┐
│ AbstractModel│
│ (your new │
│ subclass) │
└─────────────┘
All data models inherit from AbstractModel, which itself extends QAbstractItemModel. The DataLoader reads files from disk and creates model instances, while the DataManager (via AnalyzeData) stores and manages them. Plugins access models through the event system.
Subclassing AbstractModel
AbstractModel is located at applications/mne_analyze/libs/anShared/Model/abstractmodel.h. Since it extends QAbstractItemModel, you must implement all pure virtual functions from both classes.
Pure Virtual Functions
getType()
Returns the type identifier for your model. Add a new entry to the MODEL_TYPE enum in applications/mne_analyze/libs/anShared/Utils/types.h:
enum MODEL_TYPE {
ANSHAREDLIB_FIFFRAW_MODEL,
ANSHAREDLIB_ANNOTATION_MODEL,
ANSHAREDLIB_AVERAGING_MODEL,
ANSHAREDLIB_MYDATA_MODEL, // ← add your type here
};
Then implement:
MODEL_TYPE MyDataModel::getType() const
{
return MODEL_TYPE::ANSHAREDLIB_MYDATA_MODEL;
}
data()
Returns data for a given model index and role. This is the primary function that views call to retrieve displayable content:
QVariant MyDataModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
return QVariant();
if (role == Qt::DisplayRole) {
// Return data based on row and column
return m_data.at(index.row()).at(index.column());
}
return QVariant();
}
flags()
Returns item flags (selectable, editable, enabled, etc.):
Qt::ItemFlags MyDataModel::flags(const QModelIndex& index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
index()
Creates and returns a QModelIndex for the given row, column, and parent:
QModelIndex MyDataModel::index(int row, int column, const QModelIndex& parent) const
{
Q_UNUSED(parent)
if (row < 0 || row >= rowCount() || column < 0 || column >= columnCount())
return QModelIndex();
return createIndex(row, column);
}
parent()
Returns the parent index. For flat (non-hierarchical) models, return an empty index:
QModelIndex MyDataModel::parent(const QModelIndex& index) const
{
Q_UNUSED(index)
return QModelIndex();
}
rowCount()
Returns the number of rows. The meaning depends on your data — for example, the FiffRawViewModel returns the number of channels:
int MyDataModel::rowCount(const QModelIndex& parent) const
{
Q_UNUSED(parent)
return m_data.size();
}
columnCount()
Returns the number of columns:
int MyDataModel::columnCount(const QModelIndex& parent) const
{
Q_UNUSED(parent)
return m_iNumColumns;
}
Optional: saveToFile()
If your model supports saving data back to disk, override saveToFile() from AbstractModel:
bool MyDataModel::saveToFile(const QString& sPath)
{
QFile file(sPath);
if (!file.open(QIODevice::WriteOnly)) {
qWarning() << "[MyDataModel::saveToFile] Could not open" << sPath;
return false;
}
// Write your data format
// ...
return true;
}
Registering with the Data Loader and Data Manager
Loading from Files
To allow users to load your data type from a file:
- In
DataLoader::loadFilePath(), add support for your file extension so that the file dialog recognizes it. - In the load handler, create a new instance of your model and pass it to the data manager.
Adding Models Programmatically
To create a model from within a plugin (e.g., computed results rather than files):
- In
AnalyzeData.h, extendaddModel()to handle yourMODEL_TYPE. - Specify the data type your model corresponds to — see
bidsviewmodel.hfor the available types, or add a new one.
Saving to Files
To support saving, ensure your file extension is included in DataLoader::onSaveFilePressed(), and implement saveToFile() in your model.
Complete Minimal Example
// mydatamodel.h
#pragma once
#include "../Utils/types.h"
#include "abstractmodel.h"
class MyDataModel : public ANSHAREDLIB::AbstractModel
{
Q_OBJECT
public:
MyDataModel(const QList<QStringList>& data, QObject* parent = nullptr);
// AbstractModel
MODEL_TYPE getType() const override;
// QAbstractItemModel
QVariant data(const QModelIndex& index, int role) const override;
Qt::ItemFlags flags(const QModelIndex& index) const override;
QModelIndex index(int row, int col, const QModelIndex& parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex& index) const override;
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
private:
QList<QStringList> m_data;
};
// mydatamodel.cpp
#include "mydatamodel.h"
MyDataModel::MyDataModel(const QList<QStringList>& data, QObject* parent)
: AbstractModel(parent)
, m_data(data)
{
}
MODEL_TYPE MyDataModel::getType() const
{
return MODEL_TYPE::ANSHAREDLIB_MYDATA_MODEL;
}
QVariant MyDataModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid() || role != Qt::DisplayRole)
return QVariant();
return m_data.at(index.row()).at(index.column());
}
Qt::ItemFlags MyDataModel::flags(const QModelIndex& index) const
{
return index.isValid() ? (Qt::ItemIsEnabled | Qt::ItemIsSelectable) : Qt::NoItemFlags;
}
QModelIndex MyDataModel::index(int row, int col, const QModelIndex& parent) const
{
Q_UNUSED(parent)
return createIndex(row, col);
}
QModelIndex MyDataModel::parent(const QModelIndex& index) const
{
Q_UNUSED(index)
return QModelIndex();
}
int MyDataModel::rowCount(const QModelIndex& parent) const
{
Q_UNUSED(parent)
return m_data.size();
}
int MyDataModel::columnCount(const QModelIndex& parent) const
{
Q_UNUSED(parent)
return m_data.isEmpty() ? 0 : m_data.first().size();
}
Using Your Model in a Plugin
Once registered, your plugin can create and display the model:
// In your plugin's handleEvent() or a load function:
QList<QStringList> data = parseMyFile(sFilePath);
auto pModel = QSharedPointer<MyDataModel>::create(data);
// Notify MNE Analyze about the new model via the event system
QVariant varModel = QVariant::fromValue(pModel);
m_pCommu->publishEvent(EVENT_TYPE::NEW_MODEL_AVAILABLE, varModel);
Views that are subscribed to NEW_MODEL_AVAILABLE will then receive and display the model.