v2.0.0
Loading...
Searching...
No Matches
fs_surface.cpp
Go to the documentation of this file.
1//=============================================================================================================
12
13//=============================================================================================================
14// INCLUDES
15//=============================================================================================================
16
17#include "fs_surface.h"
18#include <fiff/fiff_byte_swap.h>
19
20#include <iostream>
21
22//=============================================================================================================
23// QT INCLUDES
24//=============================================================================================================
25
26#include <QFile>
27#include <QDataStream>
28#include <QTextStream>
29
30//=============================================================================================================
31// USED NAMESPACES
32//=============================================================================================================
33
34using namespace FSLIB;
35using namespace Eigen;
36
37//=============================================================================================================
38// DEFINE MEMBER METHODS
39//=============================================================================================================
40
42: m_sFilePath("")
43, m_sFileName("")
44, m_iHemi(-1)
45, m_sSurf("")
46, m_vecOffset(Vector3f::Zero(3))
47{
48}
49
50//=============================================================================================================
51
52FsSurface::FsSurface(const QString& p_sFile)
53: m_sFilePath("")
54, m_sFileName("")
55, m_iHemi(-1)
56, m_sSurf("")
57, m_vecOffset(Vector3f::Zero(3))
58{
59 FsSurface::read(p_sFile, *this);
60}
61
62//=============================================================================================================
63
64FsSurface::FsSurface(const QString& subject_id, qint32 hemi, const QString& surf, const QString& subjects_dir)
65: m_sFilePath("")
66, m_sFileName("")
67, m_iHemi(-1)
68, m_sSurf("")
69, m_vecOffset(Vector3f::Zero(3))
70{
71 FsSurface::read(subject_id, hemi, surf, subjects_dir, *this);
72}
73
74//=============================================================================================================
75
76FsSurface::FsSurface(const QString& path, qint32 hemi, const QString& surf)
77: m_sFilePath("")
78, m_sFileName("")
79, m_iHemi(-1)
80, m_sSurf("")
81, m_vecOffset(Vector3f::Zero(3))
82{
83 FsSurface::read(path, hemi, surf, *this);
84}
85
86//=============================================================================================================
87
91
92//=============================================================================================================
93
95{
96 m_sFilePath.clear();
97 m_sFileName.clear();
98 m_iHemi = -1;
99 m_sSurf.clear();
100 m_matRR.resize(0, 3);
101 m_matTris.resize(0, 3);
102 m_matNN.resize(0, 3);
103 m_vecCurv.resize(0);
104}
105
106//=============================================================================================================
107
108MatrixX3f FsSurface::compute_normals(const MatrixX3f& rr, const MatrixX3i& tris)
109{
110 qInfo("\tcomputing normals\n");
111 // first, compute triangle normals
112 MatrixX3f r1(tris.rows(), 3);
113 MatrixX3f r2(tris.rows(), 3);
114 MatrixX3f r3(tris.rows(), 3);
115
116 for (qint32 i = 0; i < tris.rows(); ++i) {
117 r1.row(i) = rr.row(tris(i, 0));
118 r2.row(i) = rr.row(tris(i, 1));
119 r3.row(i) = rr.row(tris(i, 2));
120 }
121
122 MatrixX3f x = r2 - r1;
123 MatrixX3f y = r3 - r1;
124 MatrixX3f tri_nn(x.rows(), y.cols());
125 tri_nn.col(0) = x.col(1).cwiseProduct(y.col(2)) - x.col(2).cwiseProduct(y.col(1));
126 tri_nn.col(1) = x.col(2).cwiseProduct(y.col(0)) - x.col(0).cwiseProduct(y.col(2));
127 tri_nn.col(2) = x.col(0).cwiseProduct(y.col(1)) - x.col(1).cwiseProduct(y.col(0));
128
129 // Triangle normals and areas
130 MatrixX3f tmp = tri_nn.cwiseProduct(tri_nn);
131 VectorXf normSize = tmp.rowwise().sum();
132 normSize = normSize.cwiseSqrt();
133
134 for (qint32 i = 0; i < normSize.size(); ++i)
135 if (normSize(i) != 0)
136 tri_nn.row(i) /= normSize(i);
137
138 MatrixX3f nn = MatrixX3f::Zero(rr.rows(), 3);
139
140 for (qint32 p = 0; p < tris.rows(); ++p) {
141 Vector3i verts = tris.row(p);
142 for (qint32 j = 0; j < verts.size(); ++j)
143 nn.row(verts(j)) += tri_nn.row(p);
144 }
145
146 tmp = nn.cwiseProduct(nn);
147 normSize = tmp.rowwise().sum();
148 normSize = normSize.cwiseSqrt();
149
150 for (qint32 i = 0; i < normSize.size(); ++i)
151 if (normSize(i) != 0)
152 nn.row(i) /= normSize(i);
153
154 return nn;
155}
156
157//=============================================================================================================
158
159bool FsSurface::read(const QString& subject_id, qint32 hemi, const QString& surf, const QString& subjects_dir, FsSurface& p_Surface, bool p_bLoadCurvature)
160{
161 if (hemi != 0 && hemi != 1)
162 return false;
163
164 QString p_sFile = QString("%1/%2/surf/%3.%4").arg(subjects_dir).arg(subject_id).arg(hemi == 0 ? "lh" : "rh").arg(surf);
165
166 return read(p_sFile, p_Surface, p_bLoadCurvature);
167}
168
169//=============================================================================================================
170
171bool FsSurface::read(const QString& path, qint32 hemi, const QString& surf, FsSurface& p_Surface, bool p_bLoadCurvature)
172{
173 if (hemi != 0 && hemi != 1)
174 return false;
175
176 QString p_sFile = QString("%1/%2.%3").arg(path).arg(hemi == 0 ? "lh" : "rh").arg(surf);
177
178 return read(p_sFile, p_Surface, p_bLoadCurvature);
179}
180
181//=============================================================================================================
182
183bool FsSurface::read(const QString& p_sFile, FsSurface& p_Surface, bool p_bLoadCurvature)
184{
185 p_Surface.clear();
186
187 QFile t_File(p_sFile);
188
189 if (!t_File.open(QIODevice::ReadOnly)) {
190 qWarning("\tError: Couldn't open the surface file");
191 return false;
192 }
193
194 qInfo("Reading surface...\n");
195
196 //Strip file name and path
197 qint32 t_NameIdx = 0;
198 if (p_sFile.contains("lh."))
199 t_NameIdx = p_sFile.indexOf("lh.");
200 else if (p_sFile.contains("rh."))
201 t_NameIdx = p_sFile.indexOf("rh.");
202 else
203 return false;
204
205 p_Surface.m_sFilePath = p_sFile.mid(0, t_NameIdx);
206 p_Surface.m_sFileName = p_sFile.mid(t_NameIdx, p_sFile.size() - t_NameIdx);
207
208 QDataStream t_DataStream(&t_File);
209 t_DataStream.setByteOrder(QDataStream::BigEndian);
210
211 //
212 // Magic numbers to identify QUAD and TRIANGLE files
213 //
214 // QUAD_FILE_MAGIC_NUMBER = (-1 & 0x00ffffff) ;
215 // NEW_QUAD_FILE_MAGIC_NUMBER = (-3 & 0x00ffffff) ;
216 //
217 qint32 NEW_QUAD_FILE_MAGIC_NUMBER = 16777213;
218 qint32 TRIANGLE_FILE_MAGIC_NUMBER = 16777214;
219 qint32 QUAD_FILE_MAGIC_NUMBER = 16777215;
220
221 qint32 magic = FsSurface::fread3(t_DataStream);
222
223 qint32 nvert = 0;
224 qint32 nquad = 0;
225 qint32 nface = 0;
226 MatrixXf verts;
227 MatrixXi faces;
228
229 if (magic == QUAD_FILE_MAGIC_NUMBER || magic == NEW_QUAD_FILE_MAGIC_NUMBER) {
230 nvert = FsSurface::fread3(t_DataStream);
231 nquad = FsSurface::fread3(t_DataStream);
232 // New-format curvature files start with the same magic number
233 if (nvert <= 0 || qint64(nvert) * (magic == QUAD_FILE_MAGIC_NUMBER ? 6 : 12) + qint64(nquad) * 12 > t_File.bytesAvailable()) {
234 qWarning("Invalid or truncated surface file %s", p_sFile.toUtf8().constData());
235 return false;
236 }
237 if (magic == QUAD_FILE_MAGIC_NUMBER)
238 qInfo("\t%s is a quad file (nvert = %d nquad = %d)\n", p_sFile.toUtf8().constData(), nvert, nquad);
239 else
240 qInfo("\t%s is a new quad file (nvert = %d nquad = %d)\n", p_sFile.toUtf8().constData(), nvert, nquad);
241
242 // vertices, one column per vertex like the triangle branch (transposed below)
243 verts.resize(3, nvert);
244 if (magic == QUAD_FILE_MAGIC_NUMBER) {
245 qint16 iVal;
246 for (qint32 i = 0; i < nvert; ++i) {
247 for (qint32 j = 0; j < 3; ++j) {
248 t_DataStream >> iVal;
249 verts(j, i) = static_cast<float>(iVal) / 100;
250 }
251 }
252 } else {
253 t_DataStream.readRawData(reinterpret_cast<char*>(verts.data()), nvert * 3 * sizeof(float));
254 for (qint32 i = 0; i < nvert; ++i)
255 for (qint32 j = 0; j < 3; ++j)
256 FIFFLIB::swap_floatp(&verts(j, i));
257 }
258
259 MatrixXi quads = FsSurface::fread3_many(t_DataStream, nquad * 4);
260 MatrixXi quads_new(4, nquad);
261 qint32 count = 0;
262 for (qint32 j = 0; j < quads.cols(); ++j) {
263 for (qint32 i = 0; i < quads.rows(); ++i) {
264 quads_new(i, j) = quads(count, 0);
265 ++count;
266 }
267 }
268 quads = quads_new.transpose();
269 //
270 // Face splitting follows
271 //
272 faces = MatrixXi::Zero(2 * nquad, 3);
273 for (qint32 k = 0; k < nquad; ++k) {
274 RowVectorXi quad = quads.row(k);
275 if ((quad[0] % 2) == 0) {
276 faces(nface, 0) = quad[0];
277 faces(nface, 1) = quad[1];
278 faces(nface, 2) = quad[3];
279 ++nface;
280
281 faces(nface, 0) = quad[2];
282 faces(nface, 1) = quad[3];
283 faces(nface, 2) = quad[1];
284 ++nface;
285 } else {
286 faces(nface, 0) = quad(0);
287 faces(nface, 1) = quad(1);
288 faces(nface, 2) = quad(2);
289 ++nface;
290
291 faces(nface, 0) = quad(0);
292 faces(nface, 1) = quad(2);
293 faces(nface, 2) = quad(3);
294 ++nface;
295 }
296 }
297 } else if (magic == TRIANGLE_FILE_MAGIC_NUMBER) {
298 QString s = t_File.readLine();
299 t_File.readLine();
300
301 t_DataStream >> nvert;
302 t_DataStream >> nface;
303
304 if (nvert <= 0 || nface < 0 || (qint64(nvert) + nface) * 12 > t_File.bytesAvailable()) {
305 qWarning("Invalid or truncated surface file %s", p_sFile.toUtf8().constData());
306 return false;
307 }
308 qInfo("\t%s is a triangle file (nvert = %d ntri = %d)\n", p_sFile.toUtf8().constData(), nvert, nface);
309 qInfo("\t%s", s.toUtf8().constData());
310
311 //vertices
312 verts.resize(3, nvert);
313 t_DataStream.readRawData(reinterpret_cast<char*>(verts.data()), nvert * 3 * sizeof(float));
314 for (qint32 i = 0; i < 3; ++i)
315 for (qint32 j = 0; j < nvert; ++j)
316 FIFFLIB::swap_floatp(&verts(i, j));
317
318 //faces
319 faces.resize(nface, 3);
320 qint32 iVal;
321 for (qint32 i = 0; i < nface; ++i) {
322 for (qint32 j = 0; j < 3; ++j) {
323 t_DataStream >> iVal;
324 faces(i, j) = iVal;
325 }
326 }
327 } else {
328 qWarning("Bad magic number (%d) in surface file %s", magic, p_sFile.toUtf8().constData());
329 return false;
330 }
331 if (faces.size() > 0 && (faces.minCoeff() < 0 || faces.maxCoeff() >= nvert)) {
332 qWarning("Invalid or truncated surface file %s", p_sFile.toUtf8().constData());
333 return false;
334 }
335
336 verts.transposeInPlace();
337 verts.array() *= 0.001f;
338
339 p_Surface.m_matRR = verts.block(0, 0, verts.rows(), 3);
340 p_Surface.m_matTris = faces.block(0, 0, faces.rows(), 3);
341
342 //-> not needed since qglbuilder is doing that for us
343 p_Surface.m_matNN = compute_normals(p_Surface.m_matRR, p_Surface.m_matTris);
344
345 // hemi info
346 if (t_File.fileName().contains("lh."))
347 p_Surface.m_iHemi = 0;
348 else if (t_File.fileName().contains("rh."))
349 p_Surface.m_iHemi = 1;
350 else {
351 p_Surface.m_iHemi = -1;
352 return false;
353 }
354
355 //Loaded surface
356 p_Surface.m_sSurf = t_File.fileName().mid((t_NameIdx + 3), t_File.fileName().size() - (t_NameIdx + 3));
357
358 //Load curvature
359 if (p_bLoadCurvature) {
360 QString t_sCurvatureFile = QString("%1%2.curv").arg(p_Surface.m_sFilePath).arg(p_Surface.m_iHemi == 0 ? "lh" : "rh");
361 qInfo("\t");
362 p_Surface.m_vecCurv = FsSurface::read_curv(t_sCurvatureFile);
363 }
364
365 t_File.close();
366 qInfo("\tRead a surface with %d vertices from %s\n[done]\n", nvert, p_sFile.toUtf8().constData());
367
368 return true;
369}
370
371//=============================================================================================================
372
373VectorXf FsSurface::read_curv(const QString& p_sFileName)
374{
375 VectorXf curv;
376
377 qInfo("Reading curvature...");
378 QFile t_File(p_sFileName);
379
380 if (!t_File.open(QIODevice::ReadOnly)) {
381 qWarning("\tError: Couldn't open the curvature file");
382 return curv;
383 }
384
385 QDataStream t_DataStream(&t_File);
386 t_DataStream.setByteOrder(QDataStream::BigEndian);
387
388 qint32 vnum = FsSurface::fread3(t_DataStream);
389 qint32 NEW_VERSION_MAGIC_NUMBER = 16777215;
390
391 if (vnum == NEW_VERSION_MAGIC_NUMBER) {
392 qint32 fnum, vals_per_vertex;
393 t_DataStream >> vnum;
394
395 t_DataStream >> fnum;
396 t_DataStream >> vals_per_vertex;
397
398 curv.resize(vnum, 1);
399 t_DataStream.readRawData(reinterpret_cast<char*>(curv.data()), vnum * sizeof(float));
400 for (qint32 i = 0; i < vnum; ++i)
402 } else {
403 qint32 fnum = FsSurface::fread3(t_DataStream);
404 Q_UNUSED(fnum)
405 qint16 iVal;
406 curv.resize(vnum, 1);
407 for (qint32 i = 0; i < vnum; ++i) {
408 t_DataStream >> iVal;
409 curv(i) = static_cast<float>(iVal) / 100;
410 }
411 }
412 t_File.close();
413
414 qInfo("[done]\n");
415
416 return curv;
417}
418
419//=============================================================================================================
420
421qint32 FsSurface::fread3(QDataStream& stream)
422{
423 char bytes[3];
424 stream.readRawData(bytes, 3);
425 return (static_cast<unsigned char>(bytes[0]) << 16) + (static_cast<unsigned char>(bytes[1]) << 8) + static_cast<unsigned char>(bytes[2]);
426}
427
428//=============================================================================================================
429
430qint32 FsSurface::fread3(std::iostream& stream)
431{
432 char bytes[3];
433 stream.read(bytes, 3);
434 return (static_cast<unsigned char>(bytes[0]) << 16) + (static_cast<unsigned char>(bytes[1]) << 8) + static_cast<unsigned char>(bytes[2]);
435}
436
437//=============================================================================================================
438
439VectorXi FsSurface::fread3_many(QDataStream& stream, qint32 count)
440{
441 VectorXi res(count);
442 for (qint32 i = 0; i < count; ++i)
443 res[i] = FsSurface::fread3(stream);
444 return res;
445}
446
447//=============================================================================================================
448
449VectorXi FsSurface::fread3_many(std::iostream& stream, qint32 count)
450{
451 VectorXi res(count);
452 for (qint32 i = 0; i < count; ++i)
453 res[i] = FsSurface::fread3(stream);
454 return res;
455}
Endianness swap helpers for the FIFF binary tag I/O layer (FIFF is always written big-endian on disk)...
Reader and in-memory representation of a single FreeSurfer triangular surface (e.g....
constexpr auto NEW_QUAD_FILE_MAGIC_NUMBER
constexpr auto TRIANGLE_FILE_MAGIC_NUMBER
constexpr auto QUAD_FILE_MAGIC_NUMBER
FreeSurfer surface, annotation and parcellation I/O for mne-cpp.
void swap_floatp(float *source)
static Eigen::VectorXf read_curv(const QString &p_sFileName)
const Eigen::MatrixX3f & nn() const
Definition fs_surface.h:392
static Eigen::VectorXi fread3_many(QDataStream &stream, qint32 count)
qint32 hemi() const
Definition fs_surface.h:357
static qint32 fread3(QDataStream &stream)
const Eigen::MatrixX3i & tris() const
Definition fs_surface.h:385
QString surf() const
Definition fs_surface.h:371
static bool read(const QString &subject_id, qint32 hemi, const QString &surf, const QString &subjects_dir, FsSurface &p_Surface, bool p_bLoadCurvature=true)
const Eigen::MatrixX3f & rr() const
Definition fs_surface.h:378
const Eigen::VectorXf & curv() const
Definition fs_surface.h:399
static Eigen::MatrixX3f compute_normals(const Eigen::MatrixX3f &rr, const Eigen::MatrixX3i &tris)