v2.0.0
Loading...
Searching...
No Matches
fiff_coord_trans.cpp
Go to the documentation of this file.
1//=============================================================================================================
20
21//=============================================================================================================
22// INCLUDES
23//=============================================================================================================
24
25#include "fiff_coord_trans.h"
26
27#include "fiff_stream.h"
28#include "fiff_tag.h"
29#include "fiff_dir_node.h"
30
31
32#include <QFile>
33#include <QTextStream>
34
35// MSVC builds already define _USE_MATH_DEFINES globally (see src/CMakeLists.txt),
36// so define it here only for the toolchains that do not.
37#ifndef _USE_MATH_DEFINES
38#define _USE_MATH_DEFINES
39#endif
40#include <math.h>
41
42//=============================================================================================================
43// EIGEN INCLUDES
44//=============================================================================================================
45
46#include <Eigen/Dense>
47#include <QDebug>
48
49#include <stdexcept>
50//=============================================================================================================
51// USED NAMESPACES
52//=============================================================================================================
53
54using namespace FIFFLIB;
55using namespace Eigen;
56
57//=============================================================================================================
58// DEFINE MEMBER METHODS
59//=============================================================================================================
60
62: from(-1)
63, to(-1)
64, trans(MatrixXf::Identity(4, 4))
65, invtrans(MatrixXf::Identity(4, 4))
66{
67}
68
69//=============================================================================================================
70
71FiffCoordTrans::FiffCoordTrans(QIODevice& p_IODevice)
72: from(-1)
73, to(-1)
74, trans(MatrixXf::Identity(4, 4))
75, invtrans(MatrixXf::Identity(4, 4))
76{
77 if (!read(p_IODevice, *this)) {
78 throw std::runtime_error("Coordinate transform not found");
79 }
80}
81
82//=============================================================================================================
83
85: from(p_FiffCoordTrans.from)
86, to(p_FiffCoordTrans.to)
87, trans(p_FiffCoordTrans.trans)
88, invtrans(p_FiffCoordTrans.invtrans)
89{
90}
91
92//=============================================================================================================
93
95{
96}
97
98//=============================================================================================================
99
101{
102 from = -1;
103 to = -1;
104 trans.setIdentity();
105 invtrans.setIdentity();
106}
107
108//=============================================================================================================
109
111{
112 fiff_int_t from_new = this->to;
113 this->to = this->from;
114 this->from = from_new;
115 this->trans = this->trans.inverse().eval();
116 this->invtrans = this->invtrans.inverse().eval();
117
118 return true;
119}
120
121//=============================================================================================================
122
123bool FiffCoordTrans::read(QIODevice& p_IODevice, FiffCoordTrans& p_Trans)
124{
125 FiffStream::SPtr pStream(new FiffStream(&p_IODevice));
126
127 qInfo("Reading coordinate transform from %s...\n", pStream->streamName().toUtf8().constData());
128 if (!pStream->open())
129 return false;
130
131 //
132 // Locate and read the coordinate transformation
133 //
134 FiffTag::UPtr t_pTag;
135 bool success = false;
136
137 //
138 // Get the MRI <-> head coordinate transformation
139 //
140 for (qint32 k = 0; k < pStream->dir().size() && !success; ++k) {
141 if (pStream->dir()[k]->kind == FIFF_COORD_TRANS && pStream->read_tag(t_pTag, pStream->dir()[k]->pos)) {
142 p_Trans = t_pTag->toCoordTrans();
143 success = true;
144 }
145 }
146
147 return success;
148}
149
150//=============================================================================================================
151
152bool FiffCoordTrans::write(QIODevice& qIODevice)
153{
154 // Create the file and save the essentials
155 FiffStream::SPtr pStream = FiffStream::start_file(qIODevice);
156 if (!pStream) {
157 return false;
158 }
159 qInfo("Write coordinate transform in %s...\n", pStream->streamName().toUtf8().constData());
160 this->writeToStream(pStream.data());
161 pStream->end_file();
162 qIODevice.close();
163 return true;
164}
165
166//=============================================================================================================
167
169{
170 pStream->write_coord_trans(*this);
171}
172
173//=============================================================================================================
174
175MatrixX3f FiffCoordTrans::apply_trans(const MatrixX3f& rr, bool do_move) const
176{
177 MatrixX4f rr_ones(rr.rows(), 4);
178 if (do_move) {
179 rr_ones.setOnes();
180 } else {
181 rr_ones.setZero();
182 }
183 rr_ones.block(0, 0, rr.rows(), 3) = rr;
184 return rr_ones * trans.block<3, 4>(0, 0).transpose();
185}
186
187//=============================================================================================================
188
189MatrixX3f FiffCoordTrans::apply_inverse_trans(const MatrixX3f& rr, bool do_move) const
190{
191 MatrixX4f rr_ones(rr.rows(), 4);
192 if (do_move) {
193 rr_ones.setOnes();
194 } else {
195 rr_ones.setZero();
196 }
197 rr_ones.block(0, 0, rr.rows(), 3) = rr;
198 return rr_ones * invtrans.block<3, 4>(0, 0).transpose();
199}
200
201//=============================================================================================================
202
203QString FiffCoordTrans::frame_name(int frame)
204{
205 switch (frame) {
207 return "unknown";
209 return "MEG device";
211 return "isotrak";
212 case FIFFV_COORD_HPI:
213 return "hpi";
214 case FIFFV_COORD_HEAD:
215 return "head";
216 case FIFFV_COORD_MRI:
217 return "MRI (surface RAS)";
219 return "MRI voxel";
221 return "MRI slice";
223 return "MRI display";
225 return "CTF MEG device";
227 return "CTF/4D/KIT head";
229 return "RAS (non-zero origin)";
231 return "MNI Talairach";
233 return "Talairach (MNI z > 0)";
235 return "Talairach (MNI z < 0)";
236 default:
237 return "unknown";
238 }
239}
240
241//=============================================================================================================
242
243FiffCoordTrans::FiffCoordTrans(int from, int to, const Matrix3f& rot, const Vector3f& move)
244{
245 this->trans = MatrixXf::Zero(4, 4);
246
247 this->from = from;
248 this->to = to;
249
250 this->trans.block<3, 3>(0, 0) = rot;
251 this->trans.block<3, 1>(0, 3) = move;
252 this->trans(3, 3) = 1.0f;
253
255}
256
257//=============================================================================================================
258
259FiffCoordTrans::FiffCoordTrans(int from, int to, const Matrix4f& matTrans, bool bStandard)
260{
261 this->trans = matTrans;
262 this->from = from;
263 this->to = to;
264
265 if (bStandard) {
266 // make sure that it is a standard transform if requested
267 this->trans.row(3) = Vector4f(0, 0, 0, 1).transpose();
268 }
269
271}
272
273//=============================================================================================================
274
276{
277 t.invtrans = t.trans.inverse().eval();
278 return true;
279}
280
281//=============================================================================================================
282
283void FiffCoordTrans::print() const
284{
285 qInfo().noquote() << QString("Coordinate transformation: %1 -> %2").arg(frame_name(this->from), frame_name(this->to));
286
287 for (int p = 0; p < 3; p++)
288 qInfo("\t% 8.6f % 8.6f % 8.6f\t% 7.2f mm", trans(p, 0), trans(p, 1), trans(p, 2), 1000 * trans(p, 3));
289 qInfo("\t% 8.6f % 8.6f % 8.6f % 7.2f", trans(3, 0), trans(3, 1), trans(3, 2), trans(3, 3));
290}
291
292//=============================================================================================================
293
294float FiffCoordTrans::angleTo(Eigen::MatrixX4f mTransDest)
295{
296 MatrixX4f mDevHeadT = this->trans;
297 Matrix3f mRot = mDevHeadT.block(0, 0, 3, 3);
298 Matrix3f mRotNew = mTransDest.block(0, 0, 3, 3);
299
300 Quaternionf quat(mRot);
301 Quaternionf quatNew(mRotNew);
302
303 float fAngle;
304
305 // calculate rotation
306 Quaternionf quatCompare;
307
308 quatCompare = quat * quatNew.inverse();
309 fAngle = quat.angularDistance(quatNew);
310 fAngle = fAngle * 180 / M_PI;
311
312 return fAngle;
313}
314
315//=============================================================================================================
316
317float FiffCoordTrans::translationTo(Eigen::MatrixX4f mTransDest)
318{
319 VectorXf vTrans = this->trans.col(3);
320 VectorXf vTransDest = mTransDest.col(3);
321
322 float fMove = (vTrans - vTransDest).norm();
323 return fMove;
324}
325
326//=============================================================================================================
327
328void FiffCoordTrans::apply_trans(float r[3], const FiffCoordTrans& t, bool do_move)
329{
330 float res[3];
331 for (int j = 0; j < 3; j++) {
332 res[j] = do_move ? t.trans(j, 3) : 0.0f;
333 for (int k = 0; k < 3; k++)
334 res[j] += t.trans(j, k) * r[k];
335 }
336 for (int j = 0; j < 3; j++)
337 r[j] = res[j];
338}
339
340//=============================================================================================================
341
342void FiffCoordTrans::apply_inverse_trans(float r[3], const FiffCoordTrans& t, bool do_move)
343{
344 float res[3];
345 for (int j = 0; j < 3; j++) {
346 res[j] = do_move ? t.invtrans(j, 3) : 0.0f;
347 for (int k = 0; k < 3; k++)
348 res[j] += t.invtrans(j, k) * r[k];
349 }
350 for (int j = 0; j < 3; j++)
351 r[j] = res[j];
352}
353
354//=============================================================================================================
355
357{
359 t.from = from;
360 t.to = to;
361 // trans and invtrans are already identity from default constructor
362 return t;
363}
364
365//=============================================================================================================
366
368{
370 t.from = this->to;
371 t.to = this->from;
372 t.trans = this->invtrans;
373 t.invtrans = this->trans;
374 return t;
375}
376
377//=============================================================================================================
378
379FiffCoordTrans FiffCoordTrans::combine(int from, int to, const FiffCoordTrans& t1, const FiffCoordTrans& t2)
380{
381 FiffCoordTrans a, b;
382 bool found = false;
383
384 for (int swapped = 0; swapped < 2 && !found; swapped++) {
385 const FiffCoordTrans& s1 = (swapped == 0) ? t1 : t2;
386 const FiffCoordTrans& s2 = (swapped == 0) ? t2 : t1;
387
388 if (s1.to == to && s2.from == from) {
389 a = s1;
390 b = s2;
391 found = true;
392 } else if (s1.from == to && s2.from == from) {
393 a = s1.inverted();
394 b = s2;
395 found = true;
396 } else if (s1.to == to && s2.to == from) {
397 a = s1;
398 b = s2.inverted();
399 found = true;
400 } else if (s1.from == to && s2.to == from) {
401 a = s1.inverted();
402 b = s2.inverted();
403 found = true;
404 }
405 }
406
407 if (!found || a.from != b.to) {
408 qCritical("Cannot combine coordinate transforms");
409 return FiffCoordTrans();
410 }
411
412 // Catenate: result = a * b (apply b first, then a)
413 FiffCoordTrans result;
414 result.from = b.from;
415 result.to = a.to;
416 result.trans = a.trans * b.trans;
417 result.trans.row(3) << 0.0f, 0.0f, 0.0f, 1.0f;
418 addInverse(result);
419 return result;
420}
421
422//=============================================================================================================
423
425 const float* rL,
426 const float* rN,
427 const float* rR)
428{
429 Map<const Vector3f> L(rL);
430 Map<const Vector3f> N(rN);
431 Map<const Vector3f> R(rR);
432
433 Vector3f diff1 = N - L;
434 Vector3f diff2 = R - L;
435
436 float alpha = diff1.dot(diff2) / diff2.dot(diff2);
437 Vector3f r0 = (1.0f - alpha) * L + alpha * R;
438 Vector3f ex = diff2.normalized();
439 Vector3f ey = (N - r0).normalized();
440 Vector3f ez = ex.cross(ey);
441
442 FiffCoordTrans result;
443 result.from = from;
444 result.to = to;
445 result.rot().col(0) = ex;
446 result.rot().col(1) = ey;
447 result.rot().col(2) = ez;
448 result.move() = r0;
449 addInverse(result);
450 return result;
451}
452
453//=============================================================================================================
454
455FiffCoordTrans FiffCoordTrans::readTransform(const QString& name, int from, int to)
456{
457 QFile file(name);
458 FiffStream::SPtr stream(new FiffStream(&file));
459
460 if (!stream->open()) {
461 return FiffCoordTrans();
462 }
463
464 FiffTag::UPtr t_pTag;
465 for (int k = 0; k < stream->dir().size(); k++) {
466 if (stream->dir()[k]->kind == FIFF_COORD_TRANS) {
467 if (!stream->read_tag(t_pTag, stream->dir()[k]->pos))
468 continue;
469
470 FiffCoordTrans t = t_pTag->toCoordTrans();
471 if (t.from == from && t.to == to) {
472 stream->close();
473 return t;
474 } else if (t.from == to && t.to == from) {
476 stream->close();
477 return t;
478 }
479 }
480 }
481
482 qCritical("No suitable coordinate transformation found in %s.", name.toUtf8().constData());
483 stream->close();
484 return FiffCoordTrans();
485}
486
487//=============================================================================================================
488
490{
492}
493
494//=============================================================================================================
495
497{
499}
500
501//=============================================================================================================
502
503FiffCoordTrans FiffCoordTrans::readTransformAscii(const QString& name, int from, int to)
504{
505 QFile file(name);
506 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
507 qCritical("Cannot open %s", name.toUtf8().constData());
508 return FiffCoordTrans();
509 }
510
511 QTextStream in(&file);
512 Matrix3f rot;
513 Vector3f moveVec;
514
515 int row = 0;
516 while (!in.atEnd() && row < 4) {
517 QString line = in.readLine();
518 // Strip comments and whitespace
519 int commentIdx = line.indexOf('#');
520 if (commentIdx >= 0)
521 line = line.left(commentIdx);
522 line = line.trimmed();
523 if (line.isEmpty())
524 continue;
525
526 if (row < 3) {
527 QStringList parts = line.simplified().split(' ', Qt::SkipEmptyParts);
528 if (parts.size() < 4) {
529 qCritical("Cannot read the coordinate transformation from %s",
530 name.toUtf8().constData());
531 return FiffCoordTrans();
532 }
533 bool ok1, ok2, ok3, ok4;
534 rot(row, 0) = parts[0].toFloat(&ok1);
535 rot(row, 1) = parts[1].toFloat(&ok2);
536 rot(row, 2) = parts[2].toFloat(&ok3);
537 moveVec[row] = parts[3].toFloat(&ok4) / 1000.0f;
538 if (!ok1 || !ok2 || !ok3 || !ok4) {
539 qCritical("Bad floating point number in coordinate transformation");
540 return FiffCoordTrans();
541 }
542 }
543 // Row 3 (the last row of the 4x4 matrix) is consumed but ignored
544 row++;
545 }
546
547 if (row < 4) {
548 qCritical("Cannot read the coordinate transformation from %s",
549 name.toUtf8().constData());
550 return FiffCoordTrans();
551 }
552
553 return FiffCoordTrans(from, to, rot, moveVec);
554}
555
556//=============================================================================================================
557
559{
561}
562
563//=============================================================================================================
564
566{
568 if (tag->isMatrix() || tag->getType() != FIFFT_COORD_TRANS_STRUCT || tag->data() == nullptr)
569 return t;
570
571 qint32* t_pInt32 = reinterpret_cast<qint32*>(tag->data());
572 t.from = t_pInt32[0];
573 t.to = t_pInt32[1];
574
575 float* t_pFloat = reinterpret_cast<float*>(tag->data());
576 int count = 0;
577 int r, c;
578 for (r = 0; r < 3; ++r) {
579 t.trans(r, 3) = t_pFloat[11 + r];
580 for (c = 0; c < 3; ++c) {
581 t.trans(r, c) = t_pFloat[2 + count];
582 ++count;
583 }
584 }
585 t.trans(3, 0) = 0.0f;
586 t.trans(3, 1) = 0.0f;
587 t.trans(3, 2) = 0.0f;
588 t.trans(3, 3) = 1.0f;
589
590 count = 0;
591 for (r = 0; r < 3; ++r) {
592 t.invtrans(r, 3) = t_pFloat[23 + r];
593 for (c = 0; c < 3; ++c) {
594 t.invtrans(r, c) = t_pFloat[14 + count];
595 ++count;
596 }
597 }
598 t.invtrans(3, 0) = 0.0f;
599 t.invtrans(3, 1) = 0.0f;
600 t.invtrans(3, 2) = 0.0f;
601 t.invtrans(3, 3) = 1.0f;
602
603 return t;
604}
605
606//=============================================================================================================
607
609 const FiffDirNode::SPtr& node,
610 int from, int to)
611{
612 FiffTag::UPtr t_pTag;
613
614 // Scan every directory entry of the node for a coordinate transformation
615 // matching the requested frames. The loop previously had no braces, so it
616 // only recorded the last entry's kind and then indexed dir[nent()], which
617 // is one past the end - and left `kind` uninitialised for an empty node.
618 for (int k = 0; k < node->nent(); ++k) {
619 const FiffDirEntry::SPtr& entry = node->dir[k];
620 if (entry->kind != FIFF_COORD_TRANS)
621 continue;
622
623 if (!stream->read_tag(t_pTag, entry->pos))
624 continue;
625
626 FiffCoordTrans res = readFromTag(t_pTag);
627 if (res.isEmpty())
628 continue;
629
630 if (res.from == from && res.to == to) {
631 return res;
632 }
633 if (res.from == to && res.to == from) {
634 return res.inverted();
635 }
636 }
637
638 qWarning("No suitable coordinate transformation found");
639 return FiffCoordTrans();
640}
641
642//=============================================================================================================
643
645 int to_frame,
646 const Eigen::MatrixXf& fromPts,
647 const Eigen::MatrixXf& toPts,
648 const Eigen::VectorXf& w,
649 float max_diff)
650{
651 int np = fromPts.rows();
652
653 /* Calculate centroids and subtract */
654 Eigen::Vector3f from0 = fromPts.colwise().mean();
655 Eigen::Vector3f to0 = toPts.colwise().mean();
656
657 Eigen::MatrixXf fromC = fromPts.rowwise() - from0.transpose();
658 Eigen::MatrixXf toC = toPts.rowwise() - to0.transpose();
659
660 /* Compute the cross-covariance matrix S */
661 Eigen::Matrix3f S;
662 if (w.size() > 0) {
663 S = fromC.transpose() * w.asDiagonal() * toC;
664 } else {
665 S = fromC.transpose() * toC;
666 }
667
668 /* SVD of S to solve the orthogonal Procrustes problem */
669 Eigen::JacobiSVD<Eigen::Matrix3f> svd(S, Eigen::ComputeFullU | Eigen::ComputeFullV);
670 Eigen::Matrix3f R = svd.matrixV() * svd.matrixU().transpose();
671
672 /* Translation */
673 Eigen::Vector3f moveVec = to0 - R * from0;
674
675 /* Test the transformation */
676 for (int p = 0; p < np; ++p) {
677 Eigen::Vector3f rr = R * fromPts.row(p).transpose() + moveVec;
678 float diff = (toPts.row(p).transpose() - rr).norm();
679 if (diff > max_diff) {
680 qWarning("Too large difference in matching : %7.1f > %7.1f mm",
681 1000.0f * diff, 1000.0f * max_diff);
682 return FiffCoordTrans();
683 }
684 }
685
686 return FiffCoordTrans(from_frame, to_frame, R, moveVec);
687}
constexpr auto FIFFV_MNE_COORD_RAS
constexpr auto FIFFV_COORD_HEAD
constexpr auto FIFFV_COORD_MRI_DISPLAY
constexpr auto FIFFV_MNE_COORD_CTF_HEAD
constexpr auto FIFFV_MNE_COORD_CTF_DEVICE
constexpr auto FIFFV_COORD_MRI_SLICE
constexpr auto FIFFV_COORD_DEVICE
constexpr auto FIFFV_MNE_COORD_MRI_VOXEL
constexpr auto FIFFV_MNE_COORD_MNI_TAL
constexpr auto FIFFV_COORD_UNKNOWN
constexpr auto FIFFV_COORD_MRI
constexpr auto FIFFV_MNE_COORD_FS_TAL_GTZ
constexpr auto FIFFV_COORD_ISOTRAK
constexpr auto FIFFV_COORD_HPI
constexpr auto FIFFV_MNE_COORD_FS_TAL_LTZ
FIFF tag: the 16-byte tag header (kind, type, size, next) plus its decoded payload.
FIFF binary tag-stream layer: wraps a QIODevice to read and write FIFF tags, directories,...
4x4 affine FIFF coordinate transform (FIFF_COORD_TRANS) annotated with source/destination coordinate-...
constexpr auto FIFFT_COORD_TRANS_STRUCT
Definition fiff_file.h:243
constexpr auto FIFF_COORD_TRANS
Definition fiff_file.h:466
Eigen::MatrixXf toC
Eigen::MatrixXf fromC
Eigen::Matrix3f R
return FiffCoordTrans(from_frame, to_frame, R, moveVec)
Eigen::Vector3f moveVec
Eigen::Vector3f to0
Eigen::Vector3f from0
Eigen::Matrix3f S
Eigen::JacobiSVD< Eigen::Matrix3f > svd(S, Eigen::ComputeFullU|Eigen::ComputeFullV)
Recursive node of the parsed FIFF block tree (FIFFB_* hierarchy with directory entries and children).
FIFF file I/O, in-memory data structures and high-level readers/writers.
qint32 fiff_int_t
Definition fiff_types.h:86
Eigen::Matrix< float, 4, 4, Eigen::DontAlign > invtrans
static FiffCoordTrans procrustesAlign(int from_frame, int to_frame, const Eigen::MatrixXf &fromp, const Eigen::MatrixXf &top, const Eigen::VectorXf &w, float max_diff)
static FiffCoordTrans combine(int from, int to, const FiffCoordTrans &t1, const FiffCoordTrans &t2)
static QString frame_name(int frame)
static FiffCoordTrans readTransformFromNode(FiffStream::SPtr &stream, const FiffDirNode::SPtr &node, int from, int to)
void writeToStream(FiffStream *p_pStream)
Writes the transformation to a FIFF stream.
static FiffCoordTrans readMeasTransform(const QString &name)
static FiffCoordTrans readFShead2mriTransform(const QString &name)
Eigen::MatrixX3f apply_inverse_trans(const Eigen::MatrixX3f &rr, bool do_move=true) const
bool write(QIODevice &p_IODevice)
Writes the transformation to file.
static FiffCoordTrans readMriTransform(const QString &name)
static FiffCoordTrans readTransformAscii(const QString &name, int from, int to)
static bool read(QIODevice &p_IODevice, FiffCoordTrans &p_Trans)
FiffCoordTrans inverted() const
Eigen::MatrixX3f apply_trans(const Eigen::MatrixX3f &rr, bool do_move=true) const
float angleTo(Eigen::MatrixX4f mTransDest)
static FiffCoordTrans identity(int from, int to)
static FiffCoordTrans fromCardinalPoints(int from, int to, const float *rL, const float *rN, const float *rR)
float translationTo(Eigen::MatrixX4f mTransDest)
Eigen::Matrix< float, 4, 4, Eigen::DontAlign > trans
static FiffCoordTrans readTransform(const QString &name, int from, int to)
static bool addInverse(FiffCoordTrans &t)
static FiffCoordTrans readFromTag(const std::unique_ptr< FiffTag > &tag)
QSharedPointer< FiffDirNode > SPtr
FIFF tag-stream reader/writer: wraps a QIODevice and exposes typed read_* / write_* methods for every...
QSharedPointer< FiffStream > SPtr
fiff_long_t write_coord_trans(const FiffCoordTrans &trans)
std::unique_ptr< FiffTag > UPtr
Definition fiff_tag.h:165
Eigen::Matrix< float, 4, 4, Eigen::DontAlign > invtrans
FiffCoordTrans inverted() const
bool isEmpty() const
bool invert_transform()
Eigen::Matrix< float, 4, 4, Eigen::DontAlign > trans
static bool addInverse(FiffCoordTrans &t)