v2.0.0
Loading...
Searching...
No Matches
fiff_dig_point_set.cpp
Go to the documentation of this file.
1//=============================================================================================================
20
21//=============================================================================================================
22// INCLUDES
23//=============================================================================================================
24
25#include "fiff_dig_point_set.h"
26
27//=============================================================================================================
28// INCLUDES
29//=============================================================================================================
30
31#include "fiff_dig_point.h"
32#include "fiff_dir_node.h"
33#include "fiff_tag.h"
34#include "fiff_types.h"
35#include <QDebug>
36#include <QDir>
37#include <QFile>
38#include <QFileInfo>
39#include <QHash>
40#include <QRegularExpression>
41#include <QXmlStreamReader>
42
43#include <optional>
44
45//=============================================================================================================
46// QT INCLUDES
47//=============================================================================================================
48
49//=============================================================================================================
50// EIGEN INCLUDES
51//=============================================================================================================
52
53#include <stdexcept>
54//=============================================================================================================
55// USED NAMESPACES
56//=============================================================================================================
57
58using namespace FIFFLIB;
59using namespace Eigen;
60
61//=============================================================================================================
62// DEFINE GLOBAL METHODS
63//=============================================================================================================
64
65//=============================================================================================================
66// DEFINE MEMBER METHODS
67//=============================================================================================================
68
70: m_qListDigPoint()
71{
72}
73
74//=============================================================================================================
75
77: m_qListDigPoint(p_FiffDigPointSet.m_qListDigPoint)
78{
79}
80
81//=============================================================================================================
82
83FiffDigPointSet::FiffDigPointSet(QList<FIFFLIB::FiffDigPoint> pointList)
84: m_qListDigPoint(pointList)
85{
86}
87
88//=============================================================================================================
89
90FiffDigPointSet::FiffDigPointSet(QIODevice& p_IODevice) //const FiffDigPointSet &p_FiffDigPointSet
91{
92 //
93 // Open the file
94 //
95 FiffStream::SPtr t_pStream(new FiffStream(&p_IODevice));
96
97 if (!FiffDigPointSet::readFromStream(t_pStream, *this)) {
98 t_pStream->close();
99 throw std::runtime_error("Could not read the FiffDigPointSet");
100 }
101
102 qInfo("[FiffDigPointSet::FiffDigPointSet] %i digitizer Points read from file.", this->size());
103}
104
105//=============================================================================================================
106
110
111//=============================================================================================================
112
114{
115 //
116 // Open the file, create directory
117 //
118 bool open_here = false;
119
120 if (!p_pStream->device()->isOpen()) {
121 QString t_sFileName = p_pStream->streamName();
122
123 if (!p_pStream->open())
124 return false;
125
126 qInfo("Opening header data %s...\n", t_sFileName.toUtf8().constData());
127
128 open_here = true;
129 }
130
131 //
132 // Read the measurement info
133 //
134 //read_hpi_info(p_pStream,p_Tree, info);
135 fiff_int_t kind = -1;
136 fiff_int_t pos = -1;
137 FiffTag::UPtr t_pTag;
138
139 //
140 // Locate the Electrodes
141 //
142 QList<FiffDirNode::SPtr> isotrak = p_pStream->dirtree()->dir_tree_find(FIFFB_ISOTRAK);
143
144 fiff_int_t coord_frame = FIFFV_COORD_HEAD;
145 qint32 k = 0;
146
147 if (isotrak.size() == 1) {
148 for (k = 0; k < isotrak[0]->nent(); ++k) {
149 kind = isotrak[0]->dir[k]->kind;
150 pos = isotrak[0]->dir[k]->pos;
151 if (kind == FIFF_DIG_POINT) {
152 p_pStream->read_tag(t_pTag, pos);
153 p_Dig.m_qListDigPoint.append(t_pTag->toDigPoint());
154 } else if (kind == FIFF_MNE_COORD_FRAME) {
155 // Applies to every point of the block, as mne's _read_dig_fif
156 p_pStream->read_tag(t_pTag, pos);
157 coord_frame = *t_pTag->toInt();
158 }
159 }
160 }
161 for (k = 0; k < p_Dig.size(); ++k) {
162 p_Dig[k].coord_frame = coord_frame;
163 }
164
165 //
166 // All kinds of auxliary stuff
167 //
168 if (open_here) {
169 p_pStream->close();
170 }
171 return true;
172}
173
174//=============================================================================================================
175
176namespace
177{
178
179// Metres per file unit, as mne's _check_unit_and_get_scaling; 0 for an unknown unit.
180double unitScale(const QString& unit)
181{
182 if (unit == QLatin1String("m"))
183 return 1.0;
184 if (unit == QLatin1String("cm"))
185 return 1e-2;
186 if (unit == QLatin1String("mm"))
187 return 1e-3;
188 qWarning() << "[FiffDigPointSet] unknown unit" << unit << "(use m, cm or mm)";
189 return 0.0;
190}
191
192// Whitespace-separated numbers; false if any token is not one.
193bool parseNumbers(QStringView text, QList<double>& values)
194{
195 static const QRegularExpression whitespace(QStringLiteral("\\s+"));
196 for (const QStringView token : text.split(whitespace, Qt::SkipEmptyParts)) {
197 bool ok = false;
198 values.append(token.toDouble(&ok));
199 if (!ok)
200 return false;
201 }
202 return true;
203}
204
205// The fiducials (nasion, LPA, RPA) followed by the points, as mne's _read_isotrak_elp_points.
206QList<Vector3d> readIsotrakElp(const QString& text)
207{
208 static const QRegularExpression coordinates(
209 QStringLiteral("(-?\\d+\\.?\\d*e?-?\\d*)\\s+(-?\\d+\\.?\\d*e?-?\\d*)\\s+(-?\\d+\\.?\\d*e?-?\\d*)\\s*$"),
210 QRegularExpression::MultilineOption);
211 QList<Vector3d> points;
212 for (QRegularExpressionMatchIterator it = coordinates.globalMatch(text); it.hasNext();) {
213 const QRegularExpressionMatch match = it.next();
214 points.append(Vector3d(match.captured(1).toDouble(), match.captured(2).toDouble(), match.captured(3).toDouble()));
215 }
216 return points;
217}
218
219// The same for an .hsp/.eeg file, as mne's _read_isotrak_hsp_points; empty on a malformed file.
220QList<Vector3d> readIsotrakHsp(const QString& text)
221{
222 const QStringList lines = text.split(QLatin1Char('\n'));
223 qsizetype line = 0;
224 while (line < lines.size() && !lines[line].toLower().contains(QLatin1String("position of fiducials")))
225 ++line;
226 QList<Vector3d> points;
227 for (int i = 1; i <= 3; ++i) {
228 QList<double> values;
229 if (line + i >= lines.size() || !parseNumbers(QString(lines[line + i]).remove(QLatin1String("%F")), values) || values.size() != 3)
230 return {};
231 points.append(Vector3d(values[0], values[1], values[2]));
232 }
233 // A comment line, then "<number of points> <number of columns>" and the points
234 const qsizetype header = line + 5;
235 if (header >= lines.size())
236 return points;
237 QList<double> shape;
238 if (!parseNumbers(lines[header], shape) || shape.size() != 2 || shape[1] != 3.0)
239 return {};
240 QList<double> values;
241 if (!parseNumbers(lines.mid(header + 1).join(QLatin1Char('\n')), values) || values.size() != 3 * shape[0])
242 return {};
243 for (qsizetype i = 0; i + 2 < values.size(); i += 3)
244 points.append(Vector3d(values[i], values[i + 1], values[i + 2]));
245 return points;
246}
247
248// The points of an mne DigMontage in metres, before they become dig points.
249struct Montage
250{
251 std::optional<Vector3d> nasion, lpa, rpa;
252 QList<Vector3d> hpi, extra;
253 QStringList chNames;
254 QList<Vector3d> chPos;
255
256 // A repeated name keeps its first place and its last position, as mne's _check_dupes_odict.
257 void addElectrode(const QString& name, const Vector3d& r)
258 {
259 const qsizetype index = chNames.indexOf(name);
260 if (index >= 0) {
261 qWarning() << "[FiffDigPointSet] duplicate electrode" << name << "- the last position is used";
262 chPos[index] = r;
263 return;
264 }
265 chNames.append(name);
266 chPos.append(r);
267 }
268};
269
270// The dig points of mne's make_dig_montage: cardinals, HPI coils, head shape, then electrodes,
271// which keep the number at the end of their names when every name has one (1..n otherwise).
272FiffDigPointSet makeDigMontage(const Montage& montage, QStringList* chNames)
273{
274 QList<FiffDigPoint> points;
275 const auto append = [&points](int kind, int ident, const Vector3d& r) {
276 FiffDigPoint point;
277 point.kind = kind;
278 point.ident = ident;
280 for (int c = 0; c < 3; ++c)
281 point.r[c] = static_cast<float>(r[c]);
282 points.append(point);
283 };
284 if (montage.lpa)
285 append(FIFFV_POINT_CARDINAL, FIFFV_POINT_LPA, *montage.lpa);
286 if (montage.nasion)
287 append(FIFFV_POINT_CARDINAL, FIFFV_POINT_NASION, *montage.nasion);
288 if (montage.rpa)
289 append(FIFFV_POINT_CARDINAL, FIFFV_POINT_RPA, *montage.rpa);
290 for (qsizetype i = 0; i < montage.hpi.size(); ++i)
291 append(FIFFV_POINT_HPI, static_cast<int>(i) + 1, montage.hpi[i]);
292 for (qsizetype i = 0; i < montage.extra.size(); ++i)
293 append(FIFFV_POINT_EXTRA, static_cast<int>(i) + 1, montage.extra[i]);
294
295 QList<int> idents;
296 for (const QString& name : montage.chNames) {
297 bool ok = false;
298 idents.append(name.right(3).trimmed().toInt(&ok));
299 if (!ok) {
300 idents.clear();
301 break;
302 }
303 }
304 for (qsizetype i = 0; i < montage.chPos.size(); ++i)
305 append(FIFFV_POINT_EEG, idents.isEmpty() ? static_cast<int>(i) + 1 : idents[i], montage.chPos[i]);
306 if (chNames)
307 *chNames = montage.chNames;
308 return FiffDigPointSet(points);
309}
310
311// The text of a file, or nothing with a warning.
312std::optional<QString> readText(const QString& path, const char* reader)
313{
314 QFile file(path);
315 if (!file.open(QIODevice::ReadOnly)) {
316 qWarning() << "[FiffDigPointSet::" << reader << "] cannot read" << path;
317 return std::nullopt;
318 }
319 return QString::fromUtf8(file.readAll());
320}
321
322// The text of each child element of every <element> in an XML file (namespaces ignored).
323QList<QHash<QString, QString>> readXmlRecords(const QString& path, const QString& element)
324{
325 QFile file(path);
326 QList<QHash<QString, QString>> records;
327 if (!file.open(QIODevice::ReadOnly))
328 return records;
329 QXmlStreamReader xml(&file);
330 while (!xml.atEnd()) {
331 if (xml.readNext() != QXmlStreamReader::StartElement || xml.name() != element)
332 continue;
333 QHash<QString, QString> record;
334 while (xml.readNextStartElement()) {
335 const QString key = xml.name().toString();
336 record.insert(key, xml.readElementText(QXmlStreamReader::SkipChildElements));
337 }
338 records.append(record);
339 }
340 if (xml.hasError()) {
341 qWarning() << "[FiffDigPointSet]" << path << xml.errorString();
342 records.clear();
343 }
344 return records;
345}
346
347// A position from the x/y/z fields of an XML record.
348bool recordPosition(const QHash<QString, QString>& record, const QStringList& keys, double scale, Vector3d& r)
349{
350 for (int c = 0; c < 3; ++c) {
351 bool ok = false;
352 r[c] = scale * record.value(keys[c]).toDouble(&ok);
353 if (!ok)
354 return false;
355 }
356 return true;
357}
358
359} // namespace
360
361//=============================================================================================================
362
363bool FiffDigPointSet::readPolhemusIsotrak(const QString& path, FiffDigPointSet& dig, const QStringList& chNames, const QString& unit)
364{
365 const QString extension = QLatin1Char('.') + QFileInfo(path).suffix();
366 const double scale = unitScale(unit);
367 if (scale == 0.0 || !QStringList{".hsp", ".elp", ".eeg"}.contains(extension)) {
368 qWarning() << "[FiffDigPointSet::readPolhemusIsotrak] not an Isotrak file:" << path;
369 return false;
370 }
371 const std::optional<QString> text = readText(path, "readPolhemusIsotrak");
372 if (!text)
373 return false;
374 QList<Vector3d> points = extension == QLatin1String(".elp") ? readIsotrakElp(*text) : readIsotrakHsp(*text);
375 const qsizetype count = points.size() - 3;
376 if (count < 0 || (!chNames.isEmpty() && chNames.size() != count)) {
377 qWarning() << "[FiffDigPointSet::readPolhemusIsotrak]" << path << "holds" << qMax(count, qsizetype(0)) << "points besides the fiducials, but"
378 << chNames.size() << "names were given";
379 return false;
380 }
381 for (Vector3d& point : points)
382 point *= scale;
383
384 Montage montage;
385 montage.nasion = points[0];
386 montage.lpa = points[1];
387 montage.rpa = points[2];
388 const QList<Vector3d> rest = points.mid(3);
389 if (!chNames.isEmpty()) {
390 for (qsizetype i = 0; i < count; ++i)
391 montage.addElectrode(chNames[i], rest[i]);
392 } else if (extension == QLatin1String(".elp")) {
393 montage.hpi = rest;
394 } else {
395 montage.extra = rest;
396 }
397 dig = makeDigMontage(montage, nullptr);
398 return true;
399}
400
401//=============================================================================================================
402
403bool FiffDigPointSet::readPolhemusFastscan(const QString& path, MatrixX3d& points, const QString& unit, bool requireHeader)
404{
405 const double scale = unitScale(unit);
406 if (scale == 0.0 || QFileInfo(path).suffix() != QLatin1String("txt")) {
407 qWarning() << "[FiffDigPointSet::readPolhemusFastscan] not a FastSCAN file:" << path;
408 return false;
409 }
410 const std::optional<QString> text = readText(path, "readPolhemusFastscan");
411 if (!text)
412 return false;
413 const QStringList lines = text->split(QLatin1Char('\n'));
414
415 QString header;
416 for (qsizetype i = 0; i < lines.size() && lines[i].startsWith(QLatin1Char('%')); ++i)
417 header += lines[i];
418 if (requireHeader && !header.contains(QLatin1String("FastSCAN"))) {
419 qWarning() << "[FiffDigPointSet::readPolhemusFastscan]" << path << "does not contain a valid Polhemus FastSCAN header";
420 return false;
421 }
422
423 QList<double> values;
424 for (const QString& line : lines) {
425 QList<double> row;
426 if (!parseNumbers(line.left(line.indexOf(QLatin1Char('%'))), row) || (!row.isEmpty() && row.size() != 3)) {
427 qWarning() << "[FiffDigPointSet::readPolhemusFastscan]" << path << "has a row that is not three numbers:" << line;
428 return false;
429 }
430 values.append(row);
431 }
432 points.resize(values.size() / 3, 3);
433 for (qsizetype i = 0; i < values.size(); ++i)
434 points(i / 3, i % 3) = scale * values[i];
435 return true;
436}
437
438//=============================================================================================================
439
440bool FiffDigPointSet::readCaptrak(const QString& path, FiffDigPointSet& dig, QStringList* chNames)
441{
442 Montage montage;
443 for (const QHash<QString, QString>& electrode : readXmlRecords(path, QStringLiteral("CapTrakElectrode"))) {
444 Vector3d r;
445 if (!recordPosition(electrode, {"X", "Y", "Z"}, 1e-3, r)) {
446 qWarning() << "[FiffDigPointSet::readCaptrak] electrode without a position in" << path;
447 return false;
448 }
449 const QString name = electrode.value(QStringLiteral("Name"));
450 if (name == QLatin1String("Nasion"))
451 montage.nasion = r;
452 else if (name == QLatin1String("LPA"))
453 montage.lpa = r;
454 else if (name == QLatin1String("RPA"))
455 montage.rpa = r;
456 else
457 montage.addElectrode(name, r);
458 }
459 if (!montage.nasion || !montage.lpa || !montage.rpa) {
460 qWarning() << "[FiffDigPointSet::readCaptrak]" << path << "lacks the Nasion, LPA or RPA electrode";
461 return false;
462 }
463 dig = makeDigMontage(montage, chNames);
464 return true;
465}
466
467//=============================================================================================================
468
469bool FiffDigPointSet::readEgi(const QString& path, FiffDigPointSet& dig, QStringList* chNames)
470{
471 Montage montage;
472 for (const QHash<QString, QString>& sensor : readXmlRecords(path, QStringLiteral("sensor"))) {
473 Vector3d r;
474 bool ok = false;
475 const int number = sensor.value(QStringLiteral("number")).toInt(&ok);
476 if (!ok || !recordPosition(sensor, {"x", "y", "z"}, 1e-2, r)) {
477 qWarning() << "[FiffDigPointSet::readEgi] sensor without a number or position in" << path;
478 return false;
479 }
480 const QString name = sensor.value(QStringLiteral("name"));
481 switch (sensor.value(QStringLiteral("type")).toInt()) {
482 case 0: // EEG
483 montage.addElectrode(QStringLiteral("EEG %1").arg(number, 3, 10, QLatin1Char('0')), r);
484 break;
485 case 1: // reference, numbered after the electrodes before it
486 montage.addElectrode(QStringLiteral("EEG %1").arg(montage.chNames.size() + 1, 3, 10, QLatin1Char('0')), r);
487 break;
488 case 2:
489 if (name == QLatin1String("Nasion"))
490 montage.nasion = r;
491 else if (name == QLatin1String("Left periauricular point"))
492 montage.lpa = r;
493 else if (name == QLatin1String("Right periauricular point"))
494 montage.rpa = r;
495 break;
496 default:
497 qWarning() << "[FiffDigPointSet::readEgi] skipping sensor" << number << "of unknown type";
498 }
499 }
500 if (!montage.nasion || !montage.lpa || !montage.rpa) {
501 qWarning() << "[FiffDigPointSet::readEgi]" << path << "lacks the nasion or a periauricular point";
502 return false;
503 }
504 dig = makeDigMontage(montage, chNames);
505 return true;
506}
507
508//=============================================================================================================
509
510bool FiffDigPointSet::readLocalite(const QString& path,
511 FiffDigPointSet& dig,
512 QStringList* chNames,
513 const QString& nasion,
514 const QString& lpa,
515 const QString& rpa)
516{
517 const std::optional<QString> text = readText(path, "readLocalite");
518 if (!text)
519 return false;
520 const QStringList lines = text->split(QLatin1Char('\n'));
521 Montage montage;
522 for (qsizetype i = 1; i < lines.size(); ++i) {
523 if (lines[i].trimmed().isEmpty())
524 continue;
525 const QStringList fields = lines[i].split(QLatin1Char(','));
526 Vector3d r;
527 bool ok = fields.size() == 5;
528 for (int c = 0; ok && c < 3; ++c)
529 r[c] = fields[c + 2].toDouble(&ok) / 1000.0;
530 if (!ok) {
531 qWarning() << "[FiffDigPointSet::readLocalite]" << path << "line" << i + 1 << "is not '#,name,x,y,z':" << lines[i];
532 return false;
533 }
534 montage.addElectrode(fields[1], r);
535 }
536 // The named fiducials leave the electrodes
537 const auto takeFiducial = [&montage](const QString& name, std::optional<Vector3d>& fiducial) {
538 if (name.isEmpty())
539 return true;
540 const qsizetype index = montage.chNames.indexOf(name);
541 if (index < 0)
542 return false;
543 fiducial = montage.chPos.takeAt(index);
544 montage.chNames.removeAt(index);
545 return true;
546 };
547 if (!takeFiducial(nasion, montage.nasion) || !takeFiducial(lpa, montage.lpa) || !takeFiducial(rpa, montage.rpa)) {
548 qWarning() << "[FiffDigPointSet::readLocalite]" << path << "has no point named" << nasion << lpa << rpa;
549 return false;
550 }
551 dig = makeDigMontage(montage, chNames);
552 return true;
553}
554
555//=============================================================================================================
556
557bool FiffDigPointSet::readNeuroscanDat(const QString& path, FiffDigPointSet& dig, QStringList* chNames)
558{
559 static const QRegularExpression whitespace(QStringLiteral("\\s+"));
560 const std::optional<QString> text = readText(path, "readNeuroscanDat");
561 if (!text)
562 return false;
563 const QStringList lines = text->split(QLatin1Char('\n'));
564 Montage montage;
565 for (qsizetype i = 0; i < lines.size(); ++i) {
566 const QStringList items = lines[i].split(whitespace, Qt::SkipEmptyParts);
567 if (items.isEmpty())
568 continue;
569 Vector3d r;
570 bool ok = items.size() == 5;
571 for (int c = 0; ok && c < 3; ++c)
572 r[c] = items[c + 2].toDouble(&ok);
573 if (!ok) {
574 qWarning() << "[FiffDigPointSet::readNeuroscanDat]" << path << "line" << i << "is not 'name number x y z':" << lines[i];
575 return false;
576 }
577 // The point number marks the fiducials (78 nasion, 76 LPA, 82 RPA) and the centroid (67)
578 const QString& number = items[1];
579 if (number == QLatin1String("78"))
580 montage.nasion = r;
581 else if (number == QLatin1String("76"))
582 montage.lpa = r;
583 else if (number == QLatin1String("82"))
584 montage.rpa = r;
585 else if (number != QLatin1String("67"))
586 montage.addElectrode(items[0], r);
587 }
588 dig = makeDigMontage(montage, chNames);
589 return true;
590}
591
592//=============================================================================================================
593
594bool FiffDigPointSet::write(QIODevice& p_IODevice)
595{
596 FiffStream::SPtr t_pStream = FiffStream::start_file(p_IODevice);
597 if (!t_pStream) {
598 return false;
599 }
600 qInfo("Write Digitizer Points in %s...\n", t_pStream->streamName().toUtf8().constData());
601 this->writeToStream(t_pStream.data());
602 t_pStream->end_file();
603 return true;
604}
605
606//=============================================================================================================
607
608bool FiffDigPointSet::write(const QString& filePath, QString* errorMessage)
609{
610 if (filePath.isEmpty()) {
611 if (errorMessage)
612 *errorMessage = QStringLiteral("Output path is empty.");
613 return false;
614 }
615
616 const QFileInfo info(filePath);
617 if (!info.dir().exists()) {
618 if (errorMessage) {
619 *errorMessage = QStringLiteral("Destination directory '%1' does not exist.")
620 .arg(info.dir().absolutePath());
621 }
622 return false;
623 }
624
625 QFile file(filePath);
626 if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
627 if (errorMessage) {
628 *errorMessage = QStringLiteral("Cannot open '%1' for writing: %2")
629 .arg(filePath, file.errorString());
630 }
631 return false;
632 }
633
634 write(file);
635 file.close();
636
637 if (!QFileInfo::exists(filePath)) {
638 if (errorMessage) {
639 *errorMessage = QStringLiteral("FiffDigPointSet::write produced no file at '%1'.")
640 .arg(filePath);
641 }
642 return false;
643 }
644 return true;
645}
646
647//=============================================================================================================
648
650{
651 p_pStream->start_block(FIFFB_MEAS);
652 p_pStream->start_block(FIFFB_MEAS_INFO);
653 p_pStream->start_block(FIFFB_ISOTRAK);
654
655 for (qint32 h = 0; h < m_qListDigPoint.size(); ++h) {
656 p_pStream->write_dig_point(m_qListDigPoint[h]);
657 }
658
659 qInfo("\t%lld digitizer points written\n", static_cast<long long>(m_qListDigPoint.size()));
660 p_pStream->end_block(FIFFB_ISOTRAK);
661 p_pStream->end_block(FIFFB_MEAS_INFO);
662 p_pStream->end_block(FIFFB_MEAS);
663}
664
665//=============================================================================================================
666
668{
669 if (idx >= m_qListDigPoint.length()) {
670 qWarning("Warning: Required DigPoint doesn't exist! Returning DigPoint '0'.");
671 idx = 0;
672 }
673 return m_qListDigPoint[idx];
674}
675
676//=============================================================================================================
677
679{
680 if (idx >= m_qListDigPoint.length()) {
681 qWarning("Warning: Required DigPoint doesn't exist! Returning DigPoint '0'.");
682 idx = 0;
683 }
684 return m_qListDigPoint[idx];
685}
686
687//=============================================================================================================
688
689FiffDigPointSet FiffDigPointSet::pickTypes(QList<int> includeTypes) const
690{
691 FiffDigPointSet pickedSet;
692
693 for (int i = 0; i < m_qListDigPoint.size(); ++i) {
694 if (includeTypes.contains(m_qListDigPoint[i].kind)) {
695 pickedSet << m_qListDigPoint[i];
696 }
697 }
698
699 return pickedSet;
700}
701
702//=============================================================================================================
703
705{
706 this->m_qListDigPoint.append(dig);
707 return *this;
708}
709
710//=============================================================================================================
711
713{
714 this->m_qListDigPoint.append(*dig);
715 return *this;
716}
717
718//=============================================================================================================
719
720void FiffDigPointSet::applyTransform(const FiffCoordTrans& coordTrans, bool bApplyInverse)
721{
722 Vector4f tempvec;
723 for (int i = 0; i < m_qListDigPoint.size(); ++i) {
724 tempvec(0) = m_qListDigPoint.at(i).r[0];
725 tempvec(1) = m_qListDigPoint.at(i).r[1];
726 tempvec(2) = m_qListDigPoint.at(i).r[2];
727 tempvec(3) = 1.0f;
728 if (bApplyInverse) {
729 tempvec = coordTrans.invtrans * tempvec;
730 } else {
731 tempvec = coordTrans.trans * tempvec;
732 }
733 m_qListDigPoint[i].r[0] = tempvec(0);
734 m_qListDigPoint[i].r[1] = tempvec(1);
735 m_qListDigPoint[i].r[2] = tempvec(2);
736 }
737}
738
739//=============================================================================================================
740
742{
743 const FiffDigPoint* fid[3] = {nullptr, nullptr, nullptr};
744 for (const FiffDigPoint& p : m_qListDigPoint) {
745 if (p.kind == FIFFV_POINT_CARDINAL && p.ident >= FIFFV_POINT_LPA && p.ident <= FIFFV_POINT_RPA) {
746 fid[p.ident - FIFFV_POINT_LPA] = &p;
747 }
748 }
749 if (!fid[0] || !fid[1] || !fid[2] || fid[0]->coord_frame != fid[1]->coord_frame || fid[0]->coord_frame != fid[2]->coord_frame) {
750 qWarning() << "[FiffDigPointSet::transformToHead] LPA, nasion and RPA in one frame are required.";
751 return false;
752 }
753 const int frame = fid[0]->coord_frame;
754 if (frame == FIFFV_COORD_HEAD) {
755 return true;
756 }
757 // head -> native, built from the fiducials given in the native frame
758 const FiffCoordTrans headNative = FiffCoordTrans::fromCardinalPoints(FIFFV_COORD_HEAD, frame, fid[0]->r, fid[1]->r, fid[2]->r);
759 for (FiffDigPoint& p : m_qListDigPoint) {
760 if (p.coord_frame == frame) {
761 FiffCoordTrans::apply_inverse_trans(p.r, headNative, true);
762 p.coord_frame = FIFFV_COORD_HEAD;
763 }
764 }
765 return true;
766}
767
768//=============================================================================================================
769
770QList<FiffDigPoint> FiffDigPointSet::getList()
771{
772 return m_qListDigPoint;
773}
constexpr auto FIFFV_COORD_HEAD
constexpr auto FIFFV_POINT_EXTRA
constexpr auto FIFFV_POINT_CARDINAL
constexpr auto FIFF_MNE_COORD_FRAME
constexpr auto FIFFV_POINT_HPI
constexpr auto FIFFV_POINT_EEG
constexpr auto FIFFV_POINT_LPA
constexpr auto FIFFV_COORD_UNKNOWN
constexpr auto FIFFV_POINT_NASION
constexpr auto FIFFV_POINT_RPA
FIFF tag: the 16-byte tag header (kind, type, size, next) plus its decoded payload.
Container for the FIFF_DIG_POINT records of a measurement (a parsed FIFFB_ISOTRAK block).
constexpr auto FIFFB_ISOTRAK
Definition fiff_file.h:361
constexpr auto FIFF_DIG_POINT
Definition fiff_file.h:457
constexpr auto FIFFB_MEAS_INFO
Definition fiff_file.h:354
constexpr auto FIFFB_MEAS
Definition fiff_file.h:353
Recursive node of the parsed FIFF block tree (FIFFB_* hierarchy with directory entries and children).
Single digitization point (FIFF_DIG_POINT) with kind (cardinal/HPI/EEG/extra), identifier and 3D coor...
Primitive scalar typedefs and forward-compatible aliases backing the FIFF type system.
FIFF file I/O, in-memory data structures and high-level readers/writers.
qint32 fiff_int_t
Definition fiff_types.h:86
Labelled 4x4 FIFF affine: source frame, destination frame, rotation, translation and cached inverse.
Eigen::Matrix< float, 4, 4, Eigen::DontAlign > invtrans
Eigen::MatrixX3f apply_inverse_trans(const Eigen::MatrixX3f &rr, bool do_move=true) const
static FiffCoordTrans fromCardinalPoints(int from, int to, const float *rL, const float *rN, const float *rR)
Eigen::Matrix< float, 4, 4, Eigen::DontAlign > trans
One digitizer point: kind (cardinal/HPI/EEG/extra), ident, 3D position in FIFFV_COORD_HEAD.
Collection of FiffDigPoint records as parsed from a FIFFB_ISOTRAK block.
static bool readPolhemusFastscan(const QString &path, Eigen::MatrixX3d &points, const QString &unit=QStringLiteral("mm"), bool requireHeader=true)
bool write(QIODevice &p_IODevice)
static bool readPolhemusIsotrak(const QString &path, FiffDigPointSet &dig, const QStringList &chNames=QStringList(), const QString &unit=QStringLiteral("m"))
static bool readLocalite(const QString &path, FiffDigPointSet &dig, QStringList *chNames=nullptr, const QString &nasion=QString(), const QString &lpa=QString(), const QString &rpa=QString())
const FiffDigPoint & operator[](qint32 idx) const
FiffDigPointSet & operator<<(const FiffDigPoint &dig)
static bool readEgi(const QString &path, FiffDigPointSet &dig, QStringList *chNames=nullptr)
FiffDigPointSet pickTypes(QList< int > includeTypes) const
static bool readCaptrak(const QString &path, FiffDigPointSet &dig, QStringList *chNames=nullptr)
void applyTransform(const FiffCoordTrans &coordTrans, bool bApplyInverse=false)
static bool readNeuroscanDat(const QString &path, FiffDigPointSet &dig, QStringList *chNames=nullptr)
void writeToStream(FiffStream *p_pStream)
static bool readFromStream(FiffStream::SPtr &p_Stream, FiffDigPointSet &p_Dig)
QList< FiffDigPoint > getList()
FIFF tag-stream reader/writer: wraps a QIODevice and exposes typed read_* / write_* methods for every...
fiff_long_t start_block(fiff_int_t kind)
QSharedPointer< FiffStream > SPtr
fiff_long_t write_dig_point(const FiffDigPoint &dig)
static FiffStream::SPtr start_file(QIODevice &p_IODevice)
fiff_long_t end_block(fiff_int_t kind, fiff_int_t next=FIFFV_NEXT_SEQ)
std::unique_ptr< FiffTag > UPtr
Definition fiff_tag.h:165