Skip to main content

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:

  1. In DataLoader::loadFilePath(), add support for your file extension so that the file dialog recognizes it.
  2. 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):

  1. In AnalyzeData.h, extend addModel() to handle your MODEL_TYPE.
  2. Specify the data type your model corresponds to — see bidsviewmodel.h for 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.