69 QVector<FiffCoordTrans>& additionalTrans,
70 const QString& subjectMriDir,
76 bool isCompressed = mgzFile.endsWith(
".mgz", Qt::CaseInsensitive);
80 if (!decompress(mgzFile, fileData)) {
85 if (!file.open(QIODevice::ReadOnly)) {
86 qCritical() <<
"MriMghIO::read - Could not open" << mgzFile;
89 fileData = file.readAll();
94 qCritical() <<
"MriMghIO::read - File" << mgzFile
95 <<
"is too small to be a valid MGH file ("
96 << fileData.size() <<
"bytes)";
101 if (!parseHeader(fileData, volData, verbose)) {
111 qInfo(
"Voxel -> FsSurface RAS transform:\n");
112 for (
int r = 0; r < 4; ++r) {
113 qInfo(
" %10.6f %10.6f %10.6f %10.6f\n",
114 vox2ras(r, 0), vox2ras(r, 1), vox2ras(r, 2), vox2ras(r, 3));
122 if (!readVoxelData(fileData, volData)) {
127 parseFooter(fileData, volData, additionalTrans, subjectMriDir, verbose);
130 qInfo(
"Read %d slices from %s (%dx%d pixels)\n",
131 static_cast<int>(volData.
slices.size()), qPrintable(mgzFile),
140bool MriMghIO::decompress(
const QString& mgzFile, QByteArray& rawData)
143 if (!file.open(QIODevice::ReadOnly)) {
144 qCritical() <<
"MriMghIO::decompress - Could not open" << mgzFile;
147 QByteArray compressedData = file.readAll();
150 if (compressedData.isEmpty()) {
151 qCritical() <<
"MriMghIO::decompress - File is empty:" << mgzFile;
159 int ret = inflateInit2(&strm, MAX_WBITS + 16);
161 qCritical() <<
"MriMghIO::decompress - inflateInit2 failed";
165 strm.next_in =
reinterpret_cast<Bytef*
>(compressedData.data());
166 strm.avail_in =
static_cast<uInt
>(compressedData.size());
168 const int chunkSize = 256 * 1024;
172 rawData.resize(rawData.size() + chunkSize);
173 strm.next_out =
reinterpret_cast<Bytef*
>(rawData.data() + rawData.size() - chunkSize);
174 strm.avail_out = chunkSize;
176 ret = inflate(&strm, Z_NO_FLUSH);
177 if (ret == Z_STREAM_ERROR || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) {
178 qCritical() <<
"MriMghIO::decompress - inflate failed for" << mgzFile
179 <<
"- zlib error:" << ret;
183 }
while (ret != Z_STREAM_END);
186 rawData.resize(rawData.size() -
static_cast<int>(strm.avail_out));
194bool MriMghIO::parseHeader(
const QByteArray& data,
MriVolData& volData,
bool verbose)
212 QDataStream stream(data);
213 stream.setByteOrder(QDataStream::BigEndian);
214 stream.setFloatingPointPrecision(QDataStream::SinglePrecision);
216 qint32 version, width, height, depth, nframes, type, dof;
217 stream >> version >> width >> height >> depth >> nframes >> type >> dof;
220 qCritical() <<
"MriMghIO::parseHeader - Unknown MGH version:" << version;
225 volData.
width = width;
227 volData.
depth = depth;
233 qInfo(
"MGH file: %dx%dx%d, %d frame(s), type=%d\n",
234 width, height, depth, nframes, type);
239 stream >> goodRASflag;
240 volData.
rasGood = (goodRASflag > 0);
242 if (goodRASflag > 0) {
260 qInfo(
"Voxel sizes: %.4f x %.4f x %.4f mm\n",
262 qInfo(
"goodRAS: %d\n", goodRASflag);
263 qInfo(
"c_ras: %.4f %.4f %.4f\n",
272bool MriMghIO::readVoxelData(
const QByteArray& data,
MriVolData& volData)
280 int bpv = bytesPerVoxel(volData.
type);
282 qCritical() <<
"MriMghIO::readVoxelData - Unsupported MGH data type:" << volData.
type;
286 qint64 frameSize =
static_cast<qint64
>(volData.
width) * volData.
height * volData.
depth * bpv;
288 qCritical() <<
"MriMghIO::readVoxelData - File too small for expected data size";
292 QDataStream stream(data);
293 stream.setByteOrder(QDataStream::BigEndian);
294 stream.setFloatingPointPrecision(QDataStream::SinglePrecision);
297 int nslice = volData.
depth;
299 volData.
slices.resize(nslice);
304 for (
int k = 0; k < nslice; ++k) {
312 switch (volData.
type) {
315 slice.
pixels.resize(nPixels);
316 for (
int p = 0; p < nPixels; ++p) {
327 for (
int p = 0; p < nPixels; ++p) {
330 slice.
pixelsWord[p] =
static_cast<unsigned short>(val < 0 ? 0 : val);
339 for (
int p = 0; p < nPixels; ++p) {
350 for (
int p = 0; p < nPixels; ++p) {
366 Vector3f sliceOrigin;
367 sliceOrigin(0) = vox2ras(0, 2) * k + vox2ras(0, 3);
368 sliceOrigin(1) = vox2ras(1, 2) * k + vox2ras(1, 3);
369 sliceOrigin(2) = vox2ras(2, 2) * k + vox2ras(2, 3);
372 sliceRot.col(0) = vox2ras.block<3, 1>(0, 0);
373 sliceRot.col(1) = vox2ras.block<3, 1>(0, 1);
374 sliceRot.col(2) = vox2ras.block<3, 1>(0, 2);
377 sliceMove << sliceOrigin(0), sliceOrigin(1), sliceOrigin(2);
387bool MriMghIO::parseFooter(
const QByteArray& data,
389 QVector<FiffCoordTrans>& additionalTrans,
390 const QString& subjectMriDir,
400 int bpv = bytesPerVoxel(volData.
type);
405 qint64 frameSize =
static_cast<qint64
>(volData.
width) * volData.
height * volData.
depth * bpv;
408 if (data.size() <= footerPos) {
413 QDataStream stream(data);
414 stream.setByteOrder(QDataStream::BigEndian);
415 stream.setFloatingPointPrecision(QDataStream::SinglePrecision);
416 stream.device()->seek(footerPos);
419 constexpr int kScanParamBytes = 5 *
sizeof(float);
420 qint64 remainingBytes = data.size() - footerPos;
421 if (remainingBytes >= kScanParamBytes) {
428 while (!stream.atEnd()) {
447 if (tagLen <= 0 || tagLen > data.size())
450 QByteArray tagData(tagLen,
'\0');
451 if (stream.readRawData(tagData.data(), tagLen) != tagLen)
456 QString xfmPath = QString::fromLatin1(tagData).trimmed();
460 qInfo(
"Found Talairach transform reference: %s\n", qPrintable(xfmPath));
464 if (!QFileInfo(xfmPath).isAbsolute() && !subjectMriDir.isEmpty()) {
465 xfmPath = subjectMriDir +
"/transforms/" + xfmPath;
468 FiffCoordTransSet talairach;
474 qInfo(
"Read Talairach transform from %s\n", qPrintable(xfmPath));
476 }
else if (verbose) {
477 qWarning(
"Talairach transform not readable: %s\n", qPrintable(xfmPath));
487int MriMghIO::bytesPerVoxel(
int type)
Symbolic FIFF tag, block, value, unit and channel-type constants shared across FIFFLIB.
#define FIFFV_COORD_MRI_SLICE
#define FIFFV_MNE_COORD_RAS
The MNE-C transform chain from MEG head coordinates to MNI and FreeSurfer Talairach coordinates.
4x4 affine FIFF coordinate transform (FIFF_COORD_TRANS) annotated with source/destination coordinate-...
FIFF tag-kind, block-kind and type-code numerical definitions, authoritative for FIFFLIB.
#define FIFFV_MRI_PIXEL_BYTE
#define FIFFV_MRI_PIXEL_FLOAT
#define FIFFV_MRI_PIXEL_WORD
return FiffCoordTrans(from_frame, to_frame, R, moveVec)
FreeSurfer MGH / MGZ volume reader: byte-level decoder for the 284-byte fixed header,...
FIFF file I/O, in-memory data structures and high-level readers/writers.
Volume I/O, voxel geometry and slice resampling for structural MRI data inside mne-cpp.
constexpr int MRI_MGH_VERSION
constexpr int MGH_TAG_OLD_MGH_XFORM
constexpr int MRI_MGH_DATA_OFFSET
constexpr int MGH_TAG_OLD_SURF_GEOM
constexpr int MGH_TAG_MGH_XFORM
bool addTalairach(const QString &xfmPath)
FiffCoordTrans MNI_tal_tal_ltz_t
FiffCoordTrans MNI_tal_tal_gtz_t
FiffCoordTrans RAS_MNI_tal_t
static bool read(const QString &mgzFile, MriVolData &volData, QVector< FIFFLIB::FiffCoordTrans > &additionalTrans, const QString &subjectMriDir=QString(), bool verbose=false)
QVector< unsigned char > pixels
FIFFLIB::FiffCoordTrans trans
QVector< unsigned short > pixelsWord
QVector< float > pixelsFloat
Format-agnostic 3D MRI volume: header geometry, voxel buffer (as a vector of MriSlice),...
FIFFLIB::FiffCoordTrans voxelSurfRasT
QVector< MriSlice > slices
Eigen::Matrix4f computeVox2Ras() const