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 <QQueue>
25
26//=============================================================================================================
27// STD INCLUDES
28//=============================================================================================================
29
30#include <algorithm>
31
32//=============================================================================================================
33// USED NAMESPACES
34//=============================================================================================================
35
36using namespace FSLIB;
37using namespace Eigen;
38
39//=============================================================================================================
40// DEFINE MEMBER METHODS
41//=============================================================================================================
42
43QList<QSet<int>> FsLabelUtils::buildAdjacency(const MatrixX3i& tris, int nVerts)
44{
45 QList<QSet<int>> adj(nVerts);
46
47 for (int t = 0; t < tris.rows(); ++t) {
48 int v0 = tris(t, 0);
49 int v1 = tris(t, 1);
50 int v2 = tris(t, 2);
51
52 if (v0 >= 0 && v0 < nVerts && v1 >= 0 && v1 < nVerts && v2 >= 0 && v2 < nVerts) {
53 adj[v0].insert(v1);
54 adj[v0].insert(v2);
55 adj[v1].insert(v0);
56 adj[v1].insert(v2);
57 adj[v2].insert(v0);
58 adj[v2].insert(v1);
59 }
60 }
61
62 return adj;
63}
64
65//=============================================================================================================
66
68 const FsSurface& surface,
69 int nSteps)
70{
71 if (label.isEmpty() || surface.isEmpty() || nSteps <= 0)
72 return label;
73
74 const int nVerts = static_cast<int>(surface.rr().rows());
75 QList<QSet<int>> adj = buildAdjacency(surface.tris(), nVerts);
76
77 // Start with seed vertices
78 QSet<int> current;
79 for (int i = 0; i < label.vertices.size(); ++i)
80 current.insert(label.vertices[i]);
81
82 QSet<int> allVerts = current;
83
84 // BFS expansion
85 for (int step = 0; step < nSteps; ++step) {
86 QSet<int> frontier;
87 for (int v : current) {
88 if (v >= 0 && v < nVerts) {
89 for (int neighbor : adj[v]) {
90 if (!allVerts.contains(neighbor))
91 frontier.insert(neighbor);
92 }
93 }
94 }
95 allVerts.unite(frontier);
96 current = frontier;
97
98 if (frontier.isEmpty())
99 break;
100 }
101
102 // Build result label
103 QList<int> sortedVerts(allVerts.begin(), allVerts.end());
104 std::sort(sortedVerts.begin(), sortedVerts.end());
105
106 FsLabel result;
107 result.hemi = label.hemi;
108 result.name = label.name + "_grown";
109 result.vertices.resize(sortedVerts.size());
110 result.pos.resize(sortedVerts.size(), 3);
111 result.values = VectorXd::Ones(sortedVerts.size());
112
113 for (int i = 0; i < sortedVerts.size(); ++i) {
114 result.vertices[i] = sortedVerts[i];
115 if (sortedVerts[i] < nVerts)
116 result.pos.row(i) = surface.rr().row(sortedVerts[i]);
117 }
118
119 return result;
120}
121
122//=============================================================================================================
123
124QList<FsLabel> FsLabelUtils::splitLabel(const FsLabel& label,
125 const FsSurface& surface)
126{
127 QList<FsLabel> components;
128
129 if (label.isEmpty())
130 return components;
131
132 // If surface is empty, treat each vertex as its own component
133 if (surface.isEmpty()) {
134 for (int i = 0; i < label.vertices.size(); ++i) {
135 FsLabel comp;
136 comp.hemi = label.hemi;
137 comp.name = QString("%1_part%2").arg(label.name).arg(i);
138 comp.vertices.resize(1);
139 comp.vertices[0] = label.vertices[i];
140 comp.pos = label.pos.row(i);
141 comp.values = VectorXd::Ones(1);
142 if (i < label.values.size())
143 comp.values[0] = label.values[i];
144 components.append(comp);
145 }
146 return components;
147 }
148
149 const int nVerts = static_cast<int>(surface.rr().rows());
150 QList<QSet<int>> adj = buildAdjacency(surface.tris(), nVerts);
151
152 // Build set of label vertices for fast lookup
153 QSet<int> labelSet;
154 for (int i = 0; i < label.vertices.size(); ++i)
155 labelSet.insert(label.vertices[i]);
156
157 QSet<int> visited;
158 int compIdx = 0;
159
160 for (int i = 0; i < label.vertices.size(); ++i) {
161 int seed = label.vertices[i];
162 if (visited.contains(seed))
163 continue;
164
165 // BFS from seed, restricted to label vertices
166 QQueue<int> queue;
167 queue.enqueue(seed);
168 visited.insert(seed);
169 QList<int> component;
170
171 while (!queue.isEmpty()) {
172 int v = queue.dequeue();
173 component.append(v);
174
175 if (v >= 0 && v < nVerts) {
176 for (int neighbor : adj[v]) {
177 if (labelSet.contains(neighbor) && !visited.contains(neighbor)) {
178 visited.insert(neighbor);
179 queue.enqueue(neighbor);
180 }
181 }
182 }
183 }
184
185 std::sort(component.begin(), component.end());
186
187 FsLabel comp;
188 comp.hemi = label.hemi;
189 comp.name = QString("%1_part%2").arg(label.name).arg(compIdx++);
190 comp.vertices.resize(component.size());
191 comp.pos.resize(component.size(), 3);
192 comp.values = VectorXd::Ones(component.size());
193
194 for (int j = 0; j < component.size(); ++j) {
195 comp.vertices[j] = component[j];
196 if (component[j] < nVerts)
197 comp.pos.row(j) = surface.rr().row(component[j]);
198 }
199
200 components.append(comp);
201 }
202
203 return components;
204}
205
206//=============================================================================================================
207
208QList<FsLabel> FsLabelUtils::stcToLabel(const MatrixXd& stcData,
209 const VectorXi& vertices,
210 const FsSurface& surface,
211 double dThreshold,
212 int iHemi)
213{
214 QList<FsLabel> labels;
215
216 if (stcData.size() == 0 || vertices.size() == 0 || surface.isEmpty())
217 return labels;
218
219 // Find vertices above threshold (max absolute value across time)
220 VectorXd maxAbs = stcData.cwiseAbs().rowwise().maxCoeff();
221
222 QSet<int> aboveThresh;
223 for (int i = 0; i < vertices.size(); ++i) {
224 if (maxAbs[i] > dThreshold)
225 aboveThresh.insert(vertices[i]);
226 }
227
228 if (aboveThresh.isEmpty())
229 return labels;
230
231 // Build a label from above-threshold vertices and split into components
232 FsLabel fullLabel;
233 fullLabel.hemi = iHemi;
234 fullLabel.name = "stc_label";
235
236 QList<int> sortedVerts(aboveThresh.begin(), aboveThresh.end());
237 std::sort(sortedVerts.begin(), sortedVerts.end());
238
239 const int nSurfVerts = static_cast<int>(surface.rr().rows());
240 fullLabel.vertices.resize(sortedVerts.size());
241 fullLabel.pos.resize(sortedVerts.size(), 3);
242 fullLabel.values.resize(sortedVerts.size());
243
244 for (int i = 0; i < sortedVerts.size(); ++i) {
245 fullLabel.vertices[i] = sortedVerts[i];
246 if (sortedVerts[i] < nSurfVerts)
247 fullLabel.pos.row(i) = surface.rr().row(sortedVerts[i]);
248
249 // Find the vertex's row in the STC
250 for (int j = 0; j < vertices.size(); ++j) {
251 if (vertices[j] == sortedVerts[i]) {
252 fullLabel.values[i] = maxAbs[j];
253 break;
254 }
255 }
256 }
257
258 // Split into connected components
259 labels = splitLabel(fullLabel, surface);
260
261 return labels;
262}
263
264//=============================================================================================================
265
266MatrixXd FsLabelUtils::labelsToStc(const QList<FsLabel>& labels,
267 const VectorXi& stcVertices,
268 int nTimes)
269{
270 const int nVerts = static_cast<int>(stcVertices.size());
271 MatrixXd mask = MatrixXd::Zero(nVerts, nTimes);
272
273 // Build vertex-to-row map
274 QMap<int, int> vertToRow;
275 for (int i = 0; i < nVerts; ++i)
276 vertToRow.insert(stcVertices[i], i);
277
278 for (const auto& label : labels) {
279 for (int i = 0; i < label.vertices.size(); ++i) {
280 auto it = vertToRow.find(label.vertices[i]);
281 if (it != vertToRow.end()) {
282 mask.row(it.value()).setOnes();
283 }
284 }
285 }
286
287 return mask;
288}
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:79
Eigen::VectorXd values
Definition fs_label.h:166
QString name
Definition fs_label.h:169
bool isEmpty() const
Definition fs_label.h:182
Eigen::MatrixX3f pos
Definition fs_label.h:165
Eigen::VectorXi vertices
Definition fs_label.h:164
static QList< QSet< int > > buildAdjacency(const Eigen::MatrixX3i &tris, int nVerts)
Build adjacency list from surface triangle mesh.
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:92
bool isEmpty() const
Definition fs_surface.h:362
const Eigen::MatrixX3i & tris() const
Definition fs_surface.h:383
const Eigen::MatrixX3f & rr() const
Definition fs_surface.h:376