v2.0.0
Loading...
Searching...
No Matches
fs_label_utils.cpp
Go to the documentation of this file.
1//=============================================================================================================
12
13//=============================================================================================================
14// INCLUDES
15//=============================================================================================================
16
17#include "fs_label_utils.h"
18
19//=============================================================================================================
20// QT INCLUDES
21//=============================================================================================================
22
23#include <QDebug>
24#include <QHash>
25#include <QQueue>
26
27//=============================================================================================================
28// STD INCLUDES
29//=============================================================================================================
30
31#include <algorithm>
32#include <cmath>
33
34//=============================================================================================================
35// USED NAMESPACES
36//=============================================================================================================
37
38using namespace FSLIB;
39using namespace Eigen;
40
41//=============================================================================================================
42// DEFINE MEMBER METHODS
43//=============================================================================================================
44
45QList<QSet<int>> FsLabelUtils::buildAdjacency(const MatrixX3i& tris, int nVerts)
46{
47 QList<QSet<int>> adj(nVerts);
48
49 for (int t = 0; t < tris.rows(); ++t) {
50 int v0 = tris(t, 0);
51 int v1 = tris(t, 1);
52 int v2 = tris(t, 2);
53
54 if (v0 >= 0 && v0 < nVerts && v1 >= 0 && v1 < nVerts && v2 >= 0 && v2 < nVerts) {
55 adj[v0].insert(v1);
56 adj[v0].insert(v2);
57 adj[v1].insert(v0);
58 adj[v1].insert(v2);
59 adj[v2].insert(v0);
60 adj[v2].insert(v1);
61 }
62 }
63
64 return adj;
65}
66
67//=============================================================================================================
68
70 const FsSurface& surface,
71 int nSteps)
72{
73 if (label.isEmpty() || surface.isEmpty() || nSteps <= 0)
74 return label;
75
76 const int nVerts = static_cast<int>(surface.rr().rows());
77 QList<QSet<int>> adj = buildAdjacency(surface.tris(), nVerts);
78
79 // Start with seed vertices
80 QSet<int> current;
81 for (int i = 0; i < label.vertices.size(); ++i)
82 current.insert(label.vertices[i]);
83
84 QSet<int> allVerts = current;
85
86 // BFS expansion
87 for (int step = 0; step < nSteps; ++step) {
88 QSet<int> frontier;
89 for (int v : current) {
90 if (v >= 0 && v < nVerts) {
91 for (int neighbor : adj[v]) {
92 if (!allVerts.contains(neighbor))
93 frontier.insert(neighbor);
94 }
95 }
96 }
97 allVerts.unite(frontier);
98 current = frontier;
99
100 if (frontier.isEmpty())
101 break;
102 }
103
104 // Build result label
105 QList<int> sortedVerts(allVerts.begin(), allVerts.end());
106 std::sort(sortedVerts.begin(), sortedVerts.end());
107
108 FsLabel result;
109 result.hemi = label.hemi;
110 result.name = label.name + "_grown";
111 result.vertices.resize(sortedVerts.size());
112 result.pos.resize(sortedVerts.size(), 3);
113 result.values = VectorXd::Ones(sortedVerts.size());
114
115 for (int i = 0; i < sortedVerts.size(); ++i) {
116 result.vertices[i] = sortedVerts[i];
117 if (sortedVerts[i] < nVerts)
118 result.pos.row(i) = surface.rr().row(sortedVerts[i]);
119 }
120
121 return result;
122}
123
124//=============================================================================================================
125
127 const QVector<int>& seeds,
128 double extentMm,
129 int hemi,
130 const QString& name)
131{
132 // Adapted from mne.label._verts_within_dist / _grow_labels (MNE-Python, BSD-3-Clause).
133 const MatrixX3f& rr = surface.rr();
134 const int nVerts = static_cast<int>(rr.rows());
135
136 // Neighbours in ascending order (the CSR order of mne.surface.mesh_dist) with edge lengths in mm
137 const QList<QSet<int>> adjacency = buildAdjacency(surface.tris(), nVerts);
138 QVector<QVector<QPair<int, double>>> graph(nVerts);
139 for (int v = 0; v < nVerts; ++v) {
140 QList<int> neighbours(adjacency[v].begin(), adjacency[v].end());
141 std::sort(neighbours.begin(), neighbours.end());
142 for (int n : neighbours)
143 graph[v].append({n, 1000.0 * (rr.row(n) - rr.row(v)).cast<double>().norm()});
144 }
145
146 QHash<int, double> distMap;
147 QVector<int> addedLast;
148 for (int seed : seeds) {
149 if (seed < 0 || seed >= nVerts)
150 continue;
151 distMap.insert(seed, 0.0);
152 addedLast.append(seed);
153 }
154 while (!addedLast.isEmpty()) {
155 QVector<int> added;
156 for (int i : std::as_const(addedLast)) {
157 const double vDist = distMap.value(i);
158 for (const auto& [j, d] : std::as_const(graph[i])) {
159 const double nDist = vDist + d;
160 const auto it = distMap.find(j);
161 if (it != distMap.end()) {
162 if (nDist < it.value())
163 it.value() = nDist;
164 } else if (nDist <= extentMm) {
165 distMap.insert(j, nDist);
166 added.append(j);
167 }
168 }
169 }
170 addedLast = added;
171 }
172
173 QList<int> verts = distMap.keys();
174 std::sort(verts.begin(), verts.end());
175 FsLabel label;
176 label.hemi = hemi;
177 const QString suffix = hemi == 0 ? QStringLiteral("lh") : QStringLiteral("rh");
178 label.name = name.endsWith(suffix) ? name : name + "-" + suffix;
179 label.vertices.resize(verts.size());
180 label.pos.resize(verts.size(), 3);
181 label.values.resize(verts.size());
182 for (int k = 0; k < verts.size(); ++k) {
183 label.vertices[k] = verts[k];
184 label.pos.row(k) = rr.row(verts[k]);
185 label.values[k] = std::trunc(distMap.value(verts[k]));
186 }
187 return label;
188}
189
190//=============================================================================================================
191
192QList<FsLabel> FsLabelUtils::splitLabel(const FsLabel& label,
193 const FsSurface& surface)
194{
195 QList<FsLabel> components;
196
197 if (label.isEmpty())
198 return components;
199
200 // Label row of each vertex: the parts keep the label's positions and values
201 QHash<int, int> rowOf;
202 for (int i = 0; i < label.vertices.size(); ++i)
203 rowOf.insert(label.vertices[i], i);
204
205 // Connected components over the surface edges; without a surface every vertex stands alone
206 const int nVerts = surface.isEmpty() ? 0 : static_cast<int>(surface.rr().rows());
207 const QList<QSet<int>> adj = surface.isEmpty() ? QList<QSet<int>>() : buildAdjacency(surface.tris(), nVerts);
208 QSet<int> visited;
209 QList<QList<int>> parts;
210 for (int i = 0; i < label.vertices.size(); ++i) {
211 const int seed = label.vertices[i];
212 if (visited.contains(seed))
213 continue;
214 QQueue<int> queue;
215 queue.enqueue(seed);
216 visited.insert(seed);
217 QList<int> part;
218 while (!queue.isEmpty()) {
219 const int v = queue.dequeue();
220 part.append(v);
221 if (v >= 0 && v < nVerts) {
222 for (int neighbor : adj[v]) {
223 if (rowOf.contains(neighbor) && !visited.contains(neighbor)) {
224 visited.insert(neighbor);
225 queue.enqueue(neighbor);
226 }
227 }
228 }
229 }
230 std::sort(part.begin(), part.end());
231 parts.append(part);
232 }
233
234 // As mne Label.split("contiguous"): largest part first, named <name>_div<i>[-lh|-rh]
235 std::stable_sort(parts.begin(), parts.end(), [](const QList<int>& a, const QList<int>& b) { return a.size() > b.size(); });
236 const bool hemiSuffix = label.name.endsWith(QStringLiteral("lh")) || label.name.endsWith(QStringLiteral("rh"));
237 const QString base = hemiSuffix ? label.name.left(label.name.size() - 3) : label.name;
238 const QString ext = hemiSuffix ? label.name.right(3) : QString();
239
240 for (int p = 0; p < parts.size(); ++p) {
241 const QList<int>& part = parts[p];
242 FsLabel comp;
243 comp.hemi = label.hemi;
244 comp.name = QStringLiteral("%1_div%2%3").arg(base).arg(p + 1).arg(ext);
245 comp.vertices.resize(part.size());
246 comp.pos.resize(part.size(), 3);
247 comp.values.resize(part.size());
248 for (int j = 0; j < part.size(); ++j) {
249 const int row = rowOf.value(part[j]);
250 comp.vertices[j] = part[j];
251 comp.pos.row(j) = label.pos.row(row);
252 comp.values[j] = row < label.values.size() ? label.values[row] : 1.0;
253 }
254 components.append(comp);
255 }
256
257 return components;
258}
259
260//=============================================================================================================
261
262QList<FsLabel> FsLabelUtils::stcToLabel(const MatrixXd& stcData,
263 const VectorXi& vertices,
264 const FsSurface& surface,
265 double dThreshold,
266 int iHemi)
267{
268 QList<FsLabel> labels;
269
270 if (stcData.size() == 0 || vertices.size() == 0 || surface.isEmpty())
271 return labels;
272
273 // Find vertices above threshold (max absolute value across time)
274 VectorXd maxAbs = stcData.cwiseAbs().rowwise().maxCoeff();
275
276 QSet<int> aboveThresh;
277 for (int i = 0; i < vertices.size(); ++i) {
278 if (maxAbs[i] > dThreshold)
279 aboveThresh.insert(vertices[i]);
280 }
281
282 if (aboveThresh.isEmpty())
283 return labels;
284
285 // Build a label from above-threshold vertices and split into components
286 FsLabel fullLabel;
287 fullLabel.hemi = iHemi;
288 fullLabel.name = "stc_label";
289
290 QList<int> sortedVerts(aboveThresh.begin(), aboveThresh.end());
291 std::sort(sortedVerts.begin(), sortedVerts.end());
292
293 const int nSurfVerts = static_cast<int>(surface.rr().rows());
294 fullLabel.vertices.resize(sortedVerts.size());
295 fullLabel.pos.resize(sortedVerts.size(), 3);
296 fullLabel.values.resize(sortedVerts.size());
297
298 for (int i = 0; i < sortedVerts.size(); ++i) {
299 fullLabel.vertices[i] = sortedVerts[i];
300 if (sortedVerts[i] < nSurfVerts)
301 fullLabel.pos.row(i) = surface.rr().row(sortedVerts[i]);
302
303 // Find the vertex's row in the STC
304 for (int j = 0; j < vertices.size(); ++j) {
305 if (vertices[j] == sortedVerts[i]) {
306 fullLabel.values[i] = maxAbs[j];
307 break;
308 }
309 }
310 }
311
312 // Split into connected components
313 labels = splitLabel(fullLabel, surface);
314
315 return labels;
316}
317
318//=============================================================================================================
319
320MatrixXd FsLabelUtils::labelsToStc(const QList<FsLabel>& labels,
321 const VectorXi& stcVertices,
322 int nTimes)
323{
324 const int nVerts = static_cast<int>(stcVertices.size());
325 MatrixXd mask = MatrixXd::Zero(nVerts, nTimes);
326
327 // Build vertex-to-row map
328 QMap<int, int> vertToRow;
329 for (int i = 0; i < nVerts; ++i)
330 vertToRow.insert(stcVertices[i], i);
331
332 for (const auto& label : labels) {
333 for (int i = 0; i < label.vertices.size(); ++i) {
334 auto it = vertToRow.find(label.vertices[i]);
335 if (it != vertToRow.end()) {
336 mask.row(it.value()).setOnes();
337 }
338 }
339 }
340
341 return mask;
342}
Surface-mesh label manipulation: grow, split into connected components, STC ↔ label conversion.
FreeSurfer surface, annotation and parcellation I/O for mne-cpp.
A FreeSurfer/MNE surface label: per-vertex indices, Tk-RAS positions and scalar values for one hemisp...
Definition fs_label.h:81
Eigen::VectorXd values
Definition fs_label.h:168
QString name
Definition fs_label.h:171
bool isEmpty() const
Definition fs_label.h:184
Eigen::MatrixX3f pos
Definition fs_label.h:167
Eigen::VectorXi vertices
Definition fs_label.h:166
static QList< QSet< int > > buildAdjacency(const Eigen::MatrixX3i &tris, int nVerts)
Build adjacency list from surface triangle mesh.
static FsLabel growCircularLabel(const FsSurface &surface, const QVector< int > &seeds, double extentMm, int hemi, const QString &name)
Grow a circular label around seed vertices, as mne.grow_labels does for one seed set.
static QList< FsLabel > splitLabel(const FsLabel &label, const FsSurface &surface)
Split a label into connected components.
static FsLabel growLabel(const FsLabel &label, const FsSurface &surface, int nSteps)
Grow a label by expanding along the surface mesh.
static Eigen::MatrixXd labelsToStc(const QList< FsLabel > &labels, const Eigen::VectorXi &stcVertices, int nTimes)
Convert labels to a binary source estimate mask.
static QList< FsLabel > stcToLabel(const Eigen::MatrixXd &stcData, const Eigen::VectorXi &vertices, const FsSurface &surface, double dThreshold=0.0, int iHemi=0)
Convert a source estimate to labels by thresholding.
In-memory FreeSurfer triangular cortical surface for one hemisphere.
Definition fs_surface.h:94
bool isEmpty() const
Definition fs_surface.h:364
const Eigen::MatrixX3i & tris() const
Definition fs_surface.h:385
const Eigen::MatrixX3f & rr() const
Definition fs_surface.h:378