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