v2.0.0
Loading...
Searching...
No Matches
channel_derivation.cpp
Go to the documentation of this file.
1//=============================================================================================================
34
35//=============================================================================================================
36// INCLUDES
37//=============================================================================================================
38
39#include "channel_derivation.h"
40
41//=============================================================================================================
42// QT INCLUDES
43//=============================================================================================================
44
45#include <QFile>
46#include <QTextStream>
47#include <QRegularExpression>
48#include <QDebug>
49
50//=============================================================================================================
51// USED NAMESPACES
52//=============================================================================================================
53
54using namespace UTILSLIB;
55using namespace Eigen;
56
57//=============================================================================================================
58// STATIC HELPERS
59//=============================================================================================================
60
67static QString extractShaftPrefix(const QString& name)
68{
69 QString prefix;
70 for (int i = 0; i < name.size(); ++i) {
71 QChar ch = name[i];
72 if (ch.isDigit()) {
73 break;
74 }
75 prefix.append(ch);
76 }
77 return prefix;
78}
79
80//=============================================================================================================
81// DEFINE MEMBER METHODS
82//=============================================================================================================
83
84QVector<DerivationRule> ChannelDerivation::buildBipolar(const QStringList& channelNames)
85{
86 // Group channels by shaft prefix, maintaining original order within each group
87 QMap<QString, QStringList> groups;
88 QStringList groupOrder;
89
90 for (const QString& name : channelNames) {
91 QString prefix = extractShaftPrefix(name);
92 if (!groups.contains(prefix)) {
93 groupOrder.append(prefix);
94 }
95 groups[prefix].append(name);
96 }
97
98 // Build bipolar pairs within each group
99 QVector<DerivationRule> rules;
100 for (const QString& prefix : groupOrder) {
101 const QStringList& group = groups[prefix];
102 for (int i = 0; i < group.size() - 1; ++i) {
103 DerivationRule rule;
104 rule.outputName = group[i] + "-" + group[i + 1];
105 rule.inputWeights[group[i]] = 1.0;
106 rule.inputWeights[group[i + 1]] = -1.0;
107 rules.append(rule);
108 }
109 }
110
111 return rules;
112}
113
114//=============================================================================================================
115
116QVector<DerivationRule> ChannelDerivation::buildCommonAverage(const QStringList& channelNames)
117{
118 const int N = channelNames.size();
119 if (N == 0) {
120 return {};
121 }
122
123 const double invN = 1.0 / static_cast<double>(N);
124
125 QVector<DerivationRule> rules;
126 rules.reserve(N);
127
128 for (const QString& target : channelNames) {
129 DerivationRule rule;
130 rule.outputName = target;
131 for (const QString& ch : channelNames) {
132 rule.inputWeights[ch] = -invN;
133 }
134 // Override target channel: weight = 1.0 - 1/N
135 rule.inputWeights[target] = 1.0 - invN;
136 rules.append(rule);
137 }
138
139 return rules;
140}
141
142//=============================================================================================================
143
144QPair<MatrixXd, QStringList> ChannelDerivation::apply(
145 const MatrixXd& matData,
146 const QStringList& channelNames,
147 const QVector<DerivationRule>& rules)
148{
149 // Build channel name → row index lookup
150 QMap<QString, int> chIndex;
151 for (int i = 0; i < channelNames.size(); ++i) {
152 chIndex[channelNames[i]] = i;
153 }
154
155 const Eigen::Index nTimes = matData.cols();
156 MatrixXd matResult = MatrixXd::Zero(rules.size(), nTimes);
157 QStringList outputNames;
158 outputNames.reserve(rules.size());
159
160 for (int r = 0; r < rules.size(); ++r) {
161 const DerivationRule& rule = rules[r];
162 outputNames.append(rule.outputName);
163
164 for (auto it = rule.inputWeights.constBegin(); it != rule.inputWeights.constEnd(); ++it) {
165 auto idxIt = chIndex.constFind(it.key());
166 if (idxIt == chIndex.constEnd()) {
167 qWarning() << "ChannelDerivation::apply - channel not found:" << it.key()
168 << "in rule:" << rule.outputName;
169 continue;
170 }
171 matResult.row(r) += it.value() * matData.row(idxIt.value());
172 }
173 }
174
175 return qMakePair(matResult, outputNames);
176}
177
178//=============================================================================================================
179
180QVector<DerivationRule> ChannelDerivation::readDefinitionFile(const QString& path)
181{
182 QFile file(path);
183 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
184 qWarning() << "ChannelDerivation::readDefinitionFile - cannot open:" << path;
185 return {};
186 }
187
188 QVector<DerivationRule> rules;
189 QTextStream in(&file);
190
191 // Pattern: output_name = weight1 * input1 + weight2 * input2 + ...
192 static const QRegularExpression reTerms(
193 R"(([+-]?\s*[\d.]+(?:[eE][+-]?\d+)?)\s*\*\s*(\S+))");
194
195 while (!in.atEnd()) {
196 QString line = in.readLine().trimmed();
197 if (line.isEmpty() || line.startsWith('#')) {
198 continue;
199 }
200
201 int eqPos = line.indexOf('=');
202 if (eqPos < 0) {
203 qWarning() << "ChannelDerivation::readDefinitionFile - malformed line:" << line;
204 continue;
205 }
206
207 DerivationRule rule;
208 rule.outputName = line.left(eqPos).trimmed();
209 QString rhs = line.mid(eqPos + 1);
210
211 QRegularExpressionMatchIterator matchIt = reTerms.globalMatch(rhs);
212 while (matchIt.hasNext()) {
213 QRegularExpressionMatch m = matchIt.next();
214 QString weightStr = m.captured(1).remove(' ');
215 bool ok = false;
216 double weight = weightStr.toDouble(&ok);
217 if (ok) {
218 rule.inputWeights[m.captured(2)] = weight;
219 } else {
220 qWarning() << "ChannelDerivation::readDefinitionFile - bad weight:" << weightStr;
221 }
222 }
223
224 if (!rule.inputWeights.isEmpty()) {
225 rules.append(rule);
226 }
227 }
228
229 file.close();
230 return rules;
231}
232
233//=============================================================================================================
234
235bool ChannelDerivation::writeDefinitionFile(const QString& path, const QVector<DerivationRule>& rules)
236{
237 QFile file(path);
238 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
239 qWarning() << "ChannelDerivation::writeDefinitionFile - cannot open:" << path;
240 return false;
241 }
242
243 QTextStream out(&file);
244 out << "# Channel derivation file\n";
245 out << "# Format: output_name = weight1 * input1 + weight2 * input2 + ...\n";
246
247 for (const DerivationRule& rule : rules) {
248 out << rule.outputName << " = ";
249 bool first = true;
250 for (auto it = rule.inputWeights.constBegin(); it != rule.inputWeights.constEnd(); ++it) {
251 if (!first) {
252 out << " + ";
253 }
254 out << it.value() << " * " << it.key();
255 first = false;
256 }
257 out << "\n";
258 }
259
260 file.close();
261 return true;
262}
ChannelDerivation class declaration — channel derivation and bipolar re-referencing.
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 a text definition file.
static bool writeDefinitionFile(const QString &path, const QVector< DerivationRule > &rules)
Write derivation rules to a text definition file.