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