v2.0.0
Loading...
Searching...
No Matches
mne_morph_map.cpp
Go to the documentation of this file.
1//=============================================================================================================
12
13//=============================================================================================================
14// INCLUDES
15//=============================================================================================================
16
17#include "mne_morph_map.h"
18
19#include <fiff/fiff_constants.h>
20#include <fiff/fiff_dir_node.h>
21#include <fiff/fiff_stream.h>
22#include <fiff/fiff_tag.h>
23
24//=============================================================================================================
25// QT INCLUDES
26//=============================================================================================================
27
28#include <QFile>
29
30#include <Eigen/Geometry>
31
32//=============================================================================================================
33// STL INCLUDES
34//=============================================================================================================
35
36#include <algorithm>
37#include <cmath>
38#include <limits>
39#include <utility>
40#include <vector>
41
42//=============================================================================================================
43// USED NAMESPACES
44//=============================================================================================================
45
46using namespace MNELIB;
47using namespace FIFFLIB;
48using namespace Eigen;
49
50//=============================================================================================================
51// DEFINE STATIC METHODS
52//=============================================================================================================
53
54namespace
55{
56
57constexpr int kHemiKind[2] = {FIFFV_MNE_SURF_LEFT_HEMI, FIFFV_MNE_SURF_RIGHT_HEMI};
58
59//=============================================================================================================
63class SphereGrid
64{
65public:
66 explicit SphereGrid(const MatrixX3d& points)
67 : m_points(points)
68 , m_n(std::max(1, static_cast<int>(std::cbrt(static_cast<double>(points.rows()) / 2.0))))
69 , m_cells(static_cast<std::size_t>(m_n) * m_n * m_n)
70 {
71 for (Index k = 0; k < points.rows(); ++k) {
72 m_cells[cellIndex(cellOf(points.row(k)))].push_back(static_cast<int>(k));
73 }
74 }
75
76 int nearest(const RowVector3d& r) const
77 {
78 const Vector3i c = cellOf(r);
79 const double size = 2.0 / m_n;
80 int best = -1;
81 double bestDist = std::numeric_limits<double>::max();
82 // Grow the searched cube until no cell outside it can hold a closer point.
83 for (int ring = 0; ring <= m_n; ++ring) {
84 for (int i = c.x() - ring; i <= c.x() + ring; ++i) {
85 for (int j = c.y() - ring; j <= c.y() + ring; ++j) {
86 for (int k = c.z() - ring; k <= c.z() + ring; ++k) {
87 const bool onShell = std::max({std::abs(i - c.x()), std::abs(j - c.y()), std::abs(k - c.z())}) == ring;
88 if (!onShell || i < 0 || j < 0 || k < 0 || i >= m_n || j >= m_n || k >= m_n) {
89 continue;
90 }
91 for (int p : m_cells[cellIndex(Vector3i(i, j, k))]) {
92 const double d = (m_points.row(p) - r).squaredNorm();
93 if (d < bestDist) {
94 bestDist = d;
95 best = p;
96 }
97 }
98 }
99 }
100 }
101 if (best >= 0 && std::sqrt(bestDist) <= ring * size) {
102 break;
103 }
104 }
105 return best;
106 }
107
108private:
109 Vector3i cellOf(const RowVector3d& r) const
110 {
111 Vector3i c;
112 for (int d = 0; d < 3; ++d) {
113 c[d] = std::clamp(static_cast<int>((r[d] + 1.0) / 2.0 * m_n), 0, m_n - 1);
114 }
115 return c;
116 }
117
118 std::size_t cellIndex(const Vector3i& c) const
119 {
120 return (static_cast<std::size_t>(c.x()) * m_n + c.y()) * m_n + c.z();
121 }
122
123 const MatrixX3d& m_points;
124 int m_n;
125 std::vector<std::vector<int>> m_cells;
126};
127
128//=============================================================================================================
133struct TriangleProjection
134{
135 double p;
136 double q;
137 double dist;
138 double a;
139 double b;
140 double c;
141
142 TriangleProjection(const RowVector3d& r, const RowVector3d& r1, const RowVector3d& r2, const RowVector3d& r3)
143 {
144 const RowVector3d r12 = r2 - r1;
145 const RowVector3d r13 = r3 - r1;
146 const RowVector3d d = r - r1;
147 a = r12.squaredNorm();
148 b = r13.squaredNorm();
149 c = r12.dot(r13);
150 double det = a * b - c * c;
151 if (det == 0.0) {
152 det = 1.0;
153 }
154 const double v1 = d.dot(r12);
155 const double v2 = d.dot(r13);
156 p = (b * v1 - c * v2) / det;
157 q = (a * v2 - c * v1) / det;
158 dist = d.dot(r12.cross(r13).normalized());
159 }
160
161 bool inside() const
162 {
163 return p >= 0.0 && q >= 0.0 && p <= 1.0 && q <= 1.0 && p + q < 1.0;
164 }
165
167 double edgeDistance(double p0, double q0) const
168 {
169 const double dp = p - p0;
170 const double dq = q - q0;
171 return std::sqrt(dp * dp * a + dq * dq * b + dp * dq * c + dist * dist);
172 }
173
175 double nearestEdge(double& pOut, double& qOut) const
176 {
177 const double sides[3][2] = {
178 {std::clamp(p + 0.5 * q * c / a, 0.0, 1.0), 0.0},
179 {0.0, std::clamp(0.5 * ((2.0 * a - c) * (1.0 - p) + (2.0 * b - c) * q) / (a + b - c), 0.0, 1.0)},
180 {0.0, std::clamp(q + 0.5 * p * c / b, 0.0, 1.0)},
181 };
182 double best = std::numeric_limits<double>::max();
183 for (int side = 0; side < 3; ++side) {
184 const double p0 = side == 1 ? 1.0 - sides[1][1] : sides[side][0];
185 const double q0 = sides[side][1];
186 const double d = edgeDistance(p0, q0);
187 if (d < best) {
188 best = d;
189 pOut = p0;
190 qOut = q0;
191 }
192 }
193 return best;
194 }
195};
196
197} // namespace
198
199//=============================================================================================================
200// DEFINE MEMBER METHODS
201//=============================================================================================================
202
204: map(other.map ? std::make_unique<FiffSparseMatrix>(*other.map) : nullptr)
205, best(other.best)
206, hemi(other.hemi)
207, from_subj(other.from_subj)
208, to_subj(other.to_subj)
209{
210}
211
212//=============================================================================================================
213
214MNEMorphMap MNEMorphMap::compute(const MatrixX3f& fromRr, const MatrixX3i& fromTris, const MatrixX3f& toRr)
215{
216 const MatrixX3d from = fromRr.cast<double>().rowwise().normalized();
217 const MatrixX3d to = toRr.cast<double>().rowwise().normalized();
218
219 std::vector<std::vector<int>> trisOfVertex(from.rows());
220 for (Index t = 0; t < fromTris.rows(); ++t) {
221 for (int v = 0; v < 3; ++v) {
222 trisOfVertex[fromTris(t, v)].push_back(static_cast<int>(t));
223 }
224 }
225
226 MNEMorphMap result;
227 result.best.resize(to.rows());
228 const SphereGrid grid(from);
229 std::vector<Triplet<float>> weights;
230 weights.reserve(3 * to.rows());
231 for (Index j = 0; j < to.rows(); ++j) {
232 const RowVector3d r = to.row(j);
233 const int nearest = grid.nearest(r);
234 result.best[j] = nearest;
235 // A triangle around the nearest vertex that contains the projected point wins (smallest plane
236 // distance); only if none does, the nearest point on their sides is used.
237 double bestDist = std::numeric_limits<double>::max();
238 int bestTri = -1;
239 double bestP = 0.0;
240 double bestQ = 0.0;
241 std::vector<std::pair<int, TriangleProjection>> outside;
242 for (int t : trisOfVertex[nearest]) {
243 const TriangleProjection proj(r, from.row(fromTris(t, 0)), from.row(fromTris(t, 1)), from.row(fromTris(t, 2)));
244 if (!proj.inside()) {
245 outside.emplace_back(t, proj);
246 } else if (std::abs(proj.dist) < bestDist) {
247 bestDist = std::abs(proj.dist);
248 bestTri = t;
249 bestP = proj.p;
250 bestQ = proj.q;
251 }
252 }
253 if (bestTri < 0) {
254 for (const auto& [t, proj] : outside) {
255 double p = 0.0;
256 double q = 0.0;
257 const double dist = proj.nearestEdge(p, q);
258 if (dist < bestDist) {
259 bestDist = dist;
260 bestTri = t;
261 bestP = p;
262 bestQ = q;
263 }
264 }
265 }
266 const int row = static_cast<int>(j);
267 weights.emplace_back(row, fromTris(bestTri, 0), static_cast<float>(1.0 - bestP - bestQ));
268 weights.emplace_back(row, fromTris(bestTri, 1), static_cast<float>(bestP));
269 weights.emplace_back(row, fromTris(bestTri, 2), static_cast<float>(bestQ));
270 }
271 SparseMatrix<float> matrix(to.rows(), from.rows());
272 matrix.setFromTriplets(weights.begin(), weights.end());
273 result.map = std::make_unique<FiffSparseMatrix>(std::move(matrix));
274 return result;
275}
276
277//=============================================================================================================
278
279std::optional<MNEMorphMap> MNEMorphMap::read(const QString& path, const QString& fromSubj, const QString& toSubj, int hemi)
280{
281 QFile file(path);
282 FiffStream::SPtr stream(new FiffStream(&file));
283 if (hemi < 0 || hemi > 1 || !stream->open()) {
284 return std::nullopt;
285 }
286 FiffTag::UPtr tag;
287 for (const FiffDirNode::SPtr& node : stream->dirtree()->dir_tree_find(FIFFB_MNE_MORPH_MAP)) {
288 if (!node->find_tag(stream, FIFF_MNE_HEMI, tag) || *tag->toInt() != kHemiKind[hemi]) {
289 continue;
290 }
291 if (!node->find_tag(stream, FIFF_MNE_MORPH_MAP_FROM, tag) || tag->toString() != fromSubj) {
292 continue;
293 }
294 if (!node->find_tag(stream, FIFF_MNE_MORPH_MAP_TO, tag) || tag->toString() != toSubj) {
295 continue;
296 }
297 if (!node->find_tag(stream, FIFF_MNE_MORPH_MAP, tag)) {
298 break;
299 }
300 MNEMorphMap result;
302 if (!result.map) {
303 break;
304 }
305 result.hemi = hemi;
306 result.from_subj = fromSubj;
307 result.to_subj = toSubj;
308 return result;
309 }
310 qWarning("MNEMorphMap::read - %s hemisphere morph map from %s to %s not found in %s",
311 hemi == 0 ? "left" : "right", qPrintable(fromSubj), qPrintable(toSubj), qPrintable(path));
312 return std::nullopt;
313}
314
315//=============================================================================================================
316
317bool MNEMorphMap::write(const QString& path, const QList<const MNEMorphMap*>& maps)
318{
319 QFile file(path);
321 if (!stream) {
322 return false;
323 }
324 for (const MNEMorphMap* morph : maps) {
325 if (!morph || !morph->map || morph->hemi < 0 || morph->hemi > 1) {
326 qWarning("MNEMorphMap::write - Incomplete morph map");
327 return false;
328 }
329 stream->start_block(FIFFB_MNE_MORPH_MAP);
330 stream->write_string(FIFF_MNE_MORPH_MAP_FROM, morph->from_subj);
331 stream->write_string(FIFF_MNE_MORPH_MAP_TO, morph->to_subj);
332 stream->write_int(FIFF_MNE_HEMI, &kHemiKind[morph->hemi]);
333 stream->write_float_sparse_rcs(FIFF_MNE_MORPH_MAP, morph->map->eigen());
334 stream->end_block(FIFFB_MNE_MORPH_MAP);
335 }
336 stream->end_file();
337 return true;
338}
339
340//=============================================================================================================
341
342SparseMatrix<double> MNEMorphMap::toEigen() const
343{
344 return map ? map->eigen().cast<double>() : SparseMatrix<double>();
345}
Symbolic FIFF tag, block, value, unit and channel-type constants shared across FIFFLIB.
#define FIFFV_MNE_SURF_RIGHT_HEMI
#define FIFFV_MNE_SURF_LEFT_HEMI
#define FIFF_MNE_MORPH_MAP_TO
#define FIFF_MNE_MORPH_MAP
#define FIFF_MNE_MORPH_MAP_FROM
#define FIFFB_MNE_MORPH_MAP
#define FIFF_MNE_HEMI
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,...
Recursive node of the parsed FIFF block tree (FIFFB_* hierarchy with directory entries and children).
Sphere-registration morph map of one hemisphere: the sparse matrix taking vertex values from one subj...
Core MNE data structures (source spaces, source estimates, hemispheres).
FIFF file I/O, in-memory data structures and high-level readers/writers.
QSharedPointer< FiffDirNode > SPtr
Sparse FIFF matrix: CCS or RCS storage with the value / index / pointer triple as written by FiffStre...
static FiffSparseMatrix::UPtr fiff_get_float_sparse_matrix(const FIFFLIB::FiffTag::UPtr &tag)
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)
std::unique_ptr< FiffTag > UPtr
Definition fiff_tag.h:165
Eigen::VectorXi best
static bool write(const QString &path, const QList< const MNEMorphMap * > &maps)
static std::optional< MNEMorphMap > read(const QString &path, const QString &fromSubj, const QString &toSubj, int hemi)
std::unique_ptr< FIFFLIB::FiffSparseMatrix > map
Eigen::SparseMatrix< double > toEigen() const
static MNEMorphMap compute(const Eigen::MatrixX3f &fromRr, const Eigen::MatrixX3i &fromTris, const Eigen::MatrixX3f &toRr)