v2.0.0
Loading...
Searching...
No Matches
channel_derivation.cpp
Go to the documentation of this file.
1//=============================================================================================================
12
13//=============================================================================================================
14// INCLUDES
15//=============================================================================================================
16
17#include "channel_derivation.h"
18
19#include <mne/mne_deriv_set.h>
20
21//=============================================================================================================
22// QT INCLUDES
23//=============================================================================================================
24
25#include <QFile>
26#include <QTextStream>
27#include <QDebug>
28
29#include <cmath>
30
31//=============================================================================================================
32// USED NAMESPACES
33//=============================================================================================================
34
35using namespace UTILSLIB;
36using namespace Eigen;
37using namespace MNELIB;
38
39//=============================================================================================================
40// STATIC HELPERS
41//=============================================================================================================
42
49static QString extractShaftPrefix(const QString& name)
50{
51 QString prefix;
52 for (int i = 0; i < name.size(); ++i) {
53 QChar ch = name[i];
54 if (ch.isDigit()) {
55 break;
56 }
57 prefix.append(ch);
58 }
59 return prefix;
60}
61
62//=============================================================================================================
63// DEFINE MEMBER METHODS
64//=============================================================================================================
65
66QVector<DerivationRule> ChannelDerivation::buildBipolar(const QStringList& channelNames)
67{
68 // Group channels by shaft prefix, maintaining original order within each group
69 QMap<QString, QStringList> groups;
70 QStringList groupOrder;
71
72 for (const QString& name : channelNames) {
73 QString prefix = extractShaftPrefix(name);
74 if (!groups.contains(prefix)) {
75 groupOrder.append(prefix);
76 }
77 groups[prefix].append(name);
78 }
79
80 // Build bipolar pairs within each group
81 QVector<DerivationRule> rules;
82 for (const QString& prefix : groupOrder) {
83 const QStringList& group = groups[prefix];
84 for (int i = 0; i < group.size() - 1; ++i) {
85 DerivationRule rule;
86 rule.outputName = group[i] + "-" + group[i + 1];
87 rule.inputWeights[group[i]] = 1.0;
88 rule.inputWeights[group[i + 1]] = -1.0;
89 rules.append(rule);
90 }
91 }
92
93 return rules;
94}
95
96//=============================================================================================================
97
98QVector<DerivationRule> ChannelDerivation::buildCommonAverage(const QStringList& channelNames)
99{
100 const int N = channelNames.size();
101 if (N == 0) {
102 return {};
103 }
104
105 const double invN = 1.0 / static_cast<double>(N);
106
107 QVector<DerivationRule> rules;
108 rules.reserve(N);
109
110 for (const QString& target : channelNames) {
111 DerivationRule rule;
112 rule.outputName = target;
113 for (const QString& ch : channelNames) {
114 rule.inputWeights[ch] = -invN;
115 }
116 // Override target channel: weight = 1.0 - 1/N
117 rule.inputWeights[target] = 1.0 - invN;
118 rules.append(rule);
119 }
120
121 return rules;
122}
123
124//=============================================================================================================
125
126QPair<MatrixXd, QStringList> ChannelDerivation::apply(
127 const MatrixXd& matData,
128 const QStringList& channelNames,
129 const QVector<DerivationRule>& rules)
130{
131 // Build channel name → row index lookup
132 QMap<QString, int> chIndex;
133 for (int i = 0; i < channelNames.size(); ++i) {
134 chIndex[channelNames[i]] = i;
135 }
136
137 const Eigen::Index nTimes = matData.cols();
138 MatrixXd matResult = MatrixXd::Zero(rules.size(), nTimes);
139 QStringList outputNames;
140 outputNames.reserve(rules.size());
141
142 for (int r = 0; r < rules.size(); ++r) {
143 const DerivationRule& rule = rules[r];
144 outputNames.append(rule.outputName);
145
146 for (auto it = rule.inputWeights.constBegin(); it != rule.inputWeights.constEnd(); ++it) {
147 auto idxIt = chIndex.constFind(it.key());
148 if (idxIt == chIndex.constEnd()) {
149 qWarning() << "ChannelDerivation::apply - channel not found:" << it.key()
150 << "in rule:" << rule.outputName;
151 continue;
152 }
153 matResult.row(r) += it.value() * matData.row(idxIt.value());
154 }
155 }
156
157 return qMakePair(matResult, outputNames);
158}
159
160//=============================================================================================================
161
162QVector<DerivationRule> ChannelDerivation::readDefinitionFile(const QString& path)
163{
164 const std::optional<MNEDerivSet> set = MNEDerivSet::readText(path);
165 if (!set) {
166 return {};
167 }
168 QVector<DerivationRule> rules;
169 for (const MNEDerivSet::Definition& definition : set->definitions()) {
170 rules.append({definition.first, definition.second});
171 }
172 return rules;
173}
174
175//=============================================================================================================
176
177bool ChannelDerivation::writeDefinitionFile(const QString& path, const QVector<DerivationRule>& rules)
178{
179 QFile file(path);
180 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
181 qWarning() << "ChannelDerivation::writeDefinitionFile - cannot open:" << path;
182 return false;
183 }
184 // MNE-C mne_make_derivations syntax; names are quoted because they may contain spaces.
185 QTextStream out(&file);
186 out << "# Channel derivations: \"name\" = weight * \"input\" + ...\n";
187 for (const DerivationRule& rule : rules) {
188 out << '"' << rule.outputName << "\" =";
189 for (auto it = rule.inputWeights.constBegin(); it != rule.inputWeights.constEnd(); ++it) {
190 out << ' ' << (it.value() < 0.0 ? '-' : '+') << ' ' << QString::number(std::fabs(it.value()), 'g', 17) << " * \"" << it.key() << '"';
191 }
192 out << '\n';
193 }
194 return true;
195}
Linear channel derivations — bipolar montages and arbitrary weighted recombinations.
Set of channel derivations (montages): MNE-C derivation files, text definitions and matching to recor...
Core MNE data structures (source spaces, source estimates, hemispheres).
Shared utilities (I/O helpers, spectral analysis, layout management, warp algorithms).
A single derivation rule mapping input channels (with weights) to one output channel.
QString outputName
Name of the derived output channel.
QMap< QString, double > inputWeights
Map of input channel name → weight.
static QPair< Eigen::MatrixXd, QStringList > apply(const Eigen::MatrixXd &matData, const QStringList &channelNames, const QVector< DerivationRule > &rules)
Apply derivation rules to a data matrix.
static QVector< DerivationRule > buildCommonAverage(const QStringList &channelNames)
Build common-average reference derivation rules.
static QVector< DerivationRule > buildBipolar(const QStringList &channelNames)
Build bipolar derivation rules from sequential electrode pairs.
static QVector< DerivationRule > readDefinitionFile(const QString &path)
Read derivation rules from an MNE-C derivation text file (MNELIB::MNEDerivSet::readText).
static bool writeDefinitionFile(const QString &path, const QVector< DerivationRule > &rules)
Write derivation rules in MNE-C derivation text syntax, readable by readDefinitionFile and mne_make_d...
QPair< QString, QMap< QString, double > > Definition
static std::optional< MNEDerivSet > readText(const QString &path)