30#include <Eigen/Geometry>
66 explicit SphereGrid(
const MatrixX3d& 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)
71 for (Index k = 0; k < points.rows(); ++k) {
72 m_cells[cellIndex(cellOf(points.row(k)))].push_back(
static_cast<int>(k));
76 int nearest(
const RowVector3d& r)
const
78 const Vector3i c = cellOf(r);
79 const double size = 2.0 / m_n;
81 double bestDist = std::numeric_limits<double>::max();
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) {
91 for (
int p : m_cells[cellIndex(Vector3i(i, j, k))]) {
92 const double d = (m_points.row(p) - r).squaredNorm();
101 if (best >= 0 && std::sqrt(bestDist) <= ring * size) {
109 Vector3i cellOf(
const RowVector3d& r)
const
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);
118 std::size_t cellIndex(
const Vector3i& c)
const
120 return (
static_cast<std::size_t
>(c.x()) * m_n + c.y()) * m_n + c.z();
123 const MatrixX3d& m_points;
125 std::vector<std::vector<int>> m_cells;
133struct TriangleProjection
142 TriangleProjection(
const RowVector3d& r,
const RowVector3d& r1,
const RowVector3d& r2,
const RowVector3d& r3)
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();
150 double det = a * b - c * c;
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());
163 return p >= 0.0 && q >= 0.0 && p <= 1.0 && q <= 1.0 && p + q < 1.0;
167 double edgeDistance(
double p0,
double q0)
const
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);
175 double nearestEdge(
double& pOut,
double& qOut)
const
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)},
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);
216 const MatrixX3d from = fromRr.cast<
double>().rowwise().normalized();
217 const MatrixX3d to = toRr.cast<
double>().rowwise().normalized();
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));
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;
237 double bestDist = std::numeric_limits<double>::max();
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);
254 for (
const auto& [t, proj] : outside) {
257 const double dist = proj.nearestEdge(p, q);
258 if (dist < bestDist) {
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));
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));
279std::optional<MNEMorphMap>
MNEMorphMap::read(
const QString& path,
const QString& fromSubj,
const QString& toSubj,
int hemi)
288 if (!node->find_tag(stream,
FIFF_MNE_HEMI, tag) || *tag->toInt() != kHemiKind[
hemi]) {
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));
325 if (!morph || !morph->map || morph->hemi < 0 || morph->hemi > 1) {
326 qWarning(
"MNEMorphMap::write - Incomplete morph map");
344 return map ?
map->eigen().cast<
double>() : SparseMatrix<double>();
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
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
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)