v2.0.0
Loading...
Searching...
No Matches
mne_deriv_set.cpp
Go to the documentation of this file.
1//=============================================================================================================
12
13//=============================================================================================================
14// INCLUDES
15//=============================================================================================================
16
17#include "mne_deriv_set.h"
18
19#include <fiff/fiff_constants.h>
20#include <fiff/fiff_dir_node.h>
21#include <fiff/fiff_stream.h>
22
23//=============================================================================================================
24// QT INCLUDES
25//=============================================================================================================
26
27#include <QFile>
28#include <QTextStream>
29
30//=============================================================================================================
31// STL INCLUDES
32//=============================================================================================================
33
34#include <cmath>
35
36//=============================================================================================================
37// USED NAMESPACES
38//=============================================================================================================
39
40using namespace MNELIB;
41using namespace FIFFLIB;
42
43//=============================================================================================================
44// DEFINE STATIC METHODS
45//=============================================================================================================
46
47namespace
48{
49
50// Coefficients smaller than this are zero (MNE-C ZERO_THRESH and the mne_make_derivations default).
51constexpr double kZero = 1e-6;
52
53//=============================================================================================================
58QStringList tokenize(QTextStream& in)
59{
60 QStringList tokens;
61 while (!in.atEnd()) {
62 const QString line = in.readLine();
63 if (line.trimmed().startsWith('#')) {
64 continue;
65 }
66 int k = 0;
67 while (k < line.size()) {
68 if (line[k].isSpace()) {
69 ++k;
70 continue;
71 }
72 int end = k + 1;
73 if (line[k] == '"') {
74 end = line.indexOf('"', k + 1);
75 end = end < 0 ? line.size() : end;
76 tokens << line.mid(k + 1, end - k - 1);
77 k = end + 1;
78 continue;
79 }
80 while (end < line.size() && !line[end].isSpace()) {
81 ++end;
82 }
83 tokens << line.mid(k, end - k);
84 k = end;
85 }
86 }
87 return tokens;
88}
89
90//=============================================================================================================
91
92std::unique_ptr<MNEDeriv> makeDeriv(const QList<MNEDerivSet::Definition>& definitions, const QString& filename, const QString& shortname)
93{
94 QStringList inputs;
95 QStringList outputs;
96 std::vector<Eigen::Triplet<float>> triplets;
97 for (const MNEDerivSet::Definition& definition : definitions) {
98 QList<QPair<int, double>> row;
99 for (auto it = definition.second.cbegin(); it != definition.second.cend(); ++it) {
100 if (std::fabs(it.value()) <= kZero) {
101 continue;
102 }
103 if (!inputs.contains(it.key())) {
104 inputs << it.key();
105 }
106 row.append({static_cast<int>(inputs.indexOf(it.key())), it.value()});
107 }
108 if (row.isEmpty()) {
109 qInfo("MNEDerivSet - Empty derivation \"%s\" omitted", qPrintable(definition.first));
110 continue;
111 }
112 for (const auto& [column, weight] : row) {
113 triplets.emplace_back(static_cast<int>(outputs.size()), column, static_cast<float>(weight));
114 }
115 outputs << definition.first;
116 }
117 Eigen::SparseMatrix<float> data(outputs.size(), inputs.size());
118 data.setFromTriplets(triplets.begin(), triplets.end());
119 auto deriv = std::make_unique<MNEDeriv>();
120 deriv->filename = filename;
121 deriv->shortname = shortname;
122 deriv->deriv_data = std::make_unique<MNESparseNamedMatrix>();
123 deriv->deriv_data->nrow = static_cast<int>(outputs.size());
124 deriv->deriv_data->ncol = static_cast<int>(inputs.size());
125 deriv->deriv_data->rowlist = outputs;
126 deriv->deriv_data->collist = inputs;
127 deriv->deriv_data->data = std::make_unique<FiffSparseMatrix>(std::move(data));
128 return deriv;
129}
130
131} // namespace
132
133//=============================================================================================================
134// DEFINE MEMBER METHODS
135//=============================================================================================================
136
137std::optional<MNEDerivSet> MNEDerivSet::read(const QString& path)
138{
139 QFile file(path);
140 FiffStream::SPtr stream(new FiffStream(&file));
141 if (!stream->open()) {
142 return std::nullopt;
143 }
144 MNEDerivSet set;
145 for (const FiffDirNode::SPtr& block : stream->dirtree()->dir_tree_find(FIFFB_MNE_DERIVATIONS)) {
146 for (const FiffDirNode::SPtr& matrix : block->dir_tree_find(FIFFB_MNE_NAMED_MATRIX)) {
147 auto data = MNESparseNamedMatrix::read(stream, matrix, FIFF_MNE_DERIVATION_DATA);
148 if (!data) {
149 qWarning("MNEDerivSet::read - Bad derivation data in %s", qPrintable(path));
150 return std::nullopt;
151 }
152 auto deriv = std::make_unique<MNEDeriv>();
153 deriv->filename = path;
154 deriv->deriv_data = std::move(data);
155 set.derivs.push_back(std::move(deriv));
156 }
157 }
158 return set;
159}
160
161//=============================================================================================================
162
163std::optional<MNEDerivSet> MNEDerivSet::readText(const QString& path)
164{
165 QFile file(path);
166 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
167 qWarning("MNEDerivSet::readText - Cannot open %s", qPrintable(path));
168 return std::nullopt;
169 }
170 QTextStream in(&file);
171 const QStringList tokens = tokenize(in);
172
173 // A channel token right after another channel token (or first) names a new derivation;
174 // otherwise it is an input whose weight comes from the preceding =, +, - or number.
175 enum class Kind
176 {
177 Channel,
178 Equal,
179 Plus,
180 Minus,
181 Mult,
182 Number
183 };
184 QList<Definition> definitions;
185 Kind prev = Kind::Channel;
186 bool havePrev = false;
187 double number = 0.0;
188 for (const QString& token : tokens) {
189 bool isNumber = false;
190 const double value = token.toDouble(&isNumber);
191 const Kind kind = token == "=" ? Kind::Equal : token == "+" ? Kind::Plus
192 : token == "-" ? Kind::Minus
193 : token == "*" ? Kind::Mult
194 : isNumber ? Kind::Number
195 : Kind::Channel;
196 if (kind == Kind::Mult) {
197 continue;
198 }
199 if (kind == Kind::Channel && (!havePrev || prev == Kind::Channel)) {
200 definitions.append({token, {}});
201 } else if (kind == Kind::Equal) {
202 if (definitions.isEmpty() || !definitions.last().second.isEmpty()) {
203 qWarning("MNEDerivSet::readText - Misplaced equal sign in %s", qPrintable(path));
204 return std::nullopt;
205 }
206 } else if (kind == Kind::Channel) {
207 const double weight = prev == Kind::Minus ? -1.0 : prev == Kind::Number ? number
208 : 1.0;
209 if (definitions.isEmpty()) {
210 qWarning("MNEDerivSet::readText - Misplaced channel name in %s", qPrintable(path));
211 return std::nullopt;
212 }
213 definitions.last().second[token] += weight;
214 } else if (kind == Kind::Number) {
215 number = prev == Kind::Minus ? -value : value;
216 }
217 prev = kind;
218 havePrev = true;
219 }
220 if (definitions.isEmpty()) {
221 qWarning("MNEDerivSet::readText - No derivations in %s", qPrintable(path));
222 return std::nullopt;
223 }
224 MNEDerivSet set;
225 set.derivs.push_back(makeDeriv(definitions, path, QString()));
226 if (set.derivs.front()->deriv_data->nrow == 0) {
227 return std::nullopt;
228 }
229 return set;
230}
231
232//=============================================================================================================
233
234MNEDerivSet MNEDerivSet::fromDefinitions(const QList<Definition>& definitions, const QString& name)
235{
236 MNEDerivSet set;
237 set.derivs.push_back(makeDeriv(definitions, QString(), name));
238 return set;
239}
240
241//=============================================================================================================
242
243bool MNEDerivSet::write(const QString& path) const
244{
245 if (derivs.empty()) {
246 qWarning("MNEDerivSet::write - No derivations to write");
247 return false;
248 }
249 QFile file(path);
251 if (!stream) {
252 return false;
253 }
254 stream->start_block(FIFFB_MNE_DERIVATIONS);
255 for (const auto& deriv : derivs) {
256 deriv->deriv_data->write(*stream, FIFF_MNE_DERIVATION_DATA);
257 }
258 stream->end_block(FIFFB_MNE_DERIVATIONS);
259 stream->end_file();
260 return true;
261}
262
263//=============================================================================================================
264
265QList<MNEDerivSet::Definition> MNEDerivSet::definitions() const
266{
267 QList<Definition> result;
268 for (const auto& deriv : derivs) {
269 const MNESparseNamedMatrix& m = *deriv->deriv_data;
270 const Eigen::SparseMatrix<float, Eigen::RowMajor> rows = m.data->eigen();
271 for (int j = 0; j < m.nrow; ++j) {
272 Definition definition{m.rowlist.value(j), {}};
273 for (Eigen::SparseMatrix<float, Eigen::RowMajor>::InnerIterator it(rows, j); it; ++it) {
274 definition.second.insert(m.collist.value(static_cast<int>(it.col())), it.value());
275 }
276 result.append(definition);
277 }
278 }
279 return result;
280}
281
282//=============================================================================================================
283
285{
286 for (const auto& deriv : other.derivs) {
287 derivs.push_back(std::make_unique<MNEDeriv>(*deriv));
288 }
289}
290
291//=============================================================================================================
292
293std::unique_ptr<MNEDeriv> MNEDerivSet::match(const QStringList& chNames) const
294{
295 std::vector<Eigen::Triplet<float>> triplets;
296 QStringList outputs;
297 int ntot = 0;
298 for (const Definition& definition : definitions()) {
299 ++ntot;
300 bool available = true;
301 for (auto it = definition.second.cbegin(); it != definition.second.cend() && available; ++it) {
302 available = std::fabs(it.value()) <= kZero || chNames.contains(it.key());
303 }
304 if (!available) {
305 continue;
306 }
307 for (auto it = definition.second.cbegin(); it != definition.second.cend(); ++it) {
308 if (std::fabs(it.value()) > kZero) {
309 triplets.emplace_back(static_cast<int>(outputs.size()), static_cast<int>(chNames.indexOf(it.key())), static_cast<float>(it.value()));
310 }
311 }
312 outputs << definition.first;
313 }
314 if (outputs.isEmpty()) {
315 return nullptr;
316 }
317 qInfo("MNEDerivSet::match - %lld of %d derivations were matched.", static_cast<long long>(outputs.size()), ntot);
318 Eigen::SparseMatrix<float> data(outputs.size(), chNames.size());
319 data.setFromTriplets(triplets.begin(), triplets.end());
320
321 auto matched = std::make_unique<MNEDeriv>();
322 matched->shortname = QStringLiteral("Matched derivations");
323 // in_use counts, per recorded channel, the derived channels it enters (MNE-C convention).
324 matched->in_use = Eigen::VectorXi::Zero(chNames.size());
325 for (int k = 0; k < data.outerSize(); ++k) {
326 for (Eigen::SparseMatrix<float>::InnerIterator it(data, k); it; ++it) {
327 ++matched->in_use[it.col()];
328 }
329 }
330 matched->deriv_data = std::make_unique<MNESparseNamedMatrix>();
331 matched->deriv_data->nrow = static_cast<int>(outputs.size());
332 matched->deriv_data->ncol = static_cast<int>(chNames.size());
333 matched->deriv_data->rowlist = outputs;
334 matched->deriv_data->collist = chNames;
335 matched->deriv_data->data = std::make_unique<FiffSparseMatrix>(std::move(data));
336 return matched;
337}
338
339//=============================================================================================================
340
342{
343 int total = 0;
344 for (const auto& deriv : derivs) {
345 total += deriv->deriv_data ? deriv->deriv_data->nrow : 0;
346 }
347 return total;
348}
Symbolic FIFF tag, block, value, unit and channel-type constants shared across FIFFLIB.
#define FIFF_MNE_DERIVATION_DATA
#define FIFFB_MNE_DERIVATIONS
#define FIFFB_MNE_NAMED_MATRIX
FIFF binary tag-stream layer: wraps a QIODevice to read and write FIFF tags, directories,...
Recursive node of the parsed FIFF block tree (FIFFB_* hierarchy with directory entries and children).
Set of channel derivations (montages): MNE-C derivation files, text definitions and matching to recor...
Core MNE data structures (source spaces, source estimates, hemispheres).
FIFF file I/O, in-memory data structures and high-level readers/writers.
std::vector< InvToken > tokenize(const InvSourceEstimate &estimate, const InvTokenizeOptions &options)
Serialise an InvSourceEstimate into a flat token sequence.
QSharedPointer< FiffDirNode > SPtr
FIFF tag-stream reader/writer: wraps a QIODevice and exposes typed read_* / write_* methods for every...
QSharedPointer< FiffStream > SPtr
static FiffStream::SPtr start_file(QIODevice &p_IODevice)
static MNEDerivSet fromDefinitions(const QList< Definition > &definitions, const QString &name=QString())
QPair< QString, QMap< QString, double > > Definition
bool write(const QString &path) const
void append(const MNEDerivSet &other)
static std::optional< MNEDerivSet > read(const QString &path)
std::vector< std::unique_ptr< MNEDeriv > > derivs
static std::optional< MNEDerivSet > readText(const QString &path)
std::unique_ptr< MNEDeriv > match(const QStringList &chNames) const
QList< Definition > definitions() const
std::unique_ptr< FIFFLIB::FiffSparseMatrix > data
static std::unique_ptr< MNESparseNamedMatrix > read(FIFFLIB::FiffStream::SPtr &stream, const FIFFLIB::FiffDirNode::SPtr &node, int kind)