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