158 const bool compressed = niiFile.endsWith(QStringLiteral(
".gz"), Qt::CaseInsensitive);
165 if (!f.open(QIODevice::ReadOnly)) {
166 qCritical() <<
"MriNiftiIO::read - Could not open" << niiFile;
172 if (bytes.size() < NIFTI_HDR_SIZE) {
173 qCritical() <<
"MriNiftiIO::read - file too small (" << bytes.size() <<
"bytes) :" << niiFile;
177 const char* hdr = bytes.constData();
180 qint32 sizeofHdr = readLE<qint32>(hdr);
181 bool bigEndian =
false;
182 if (sizeofHdr != NIFTI_HDR_SIZE) {
183 sizeofHdr = readBE<qint32>(hdr);
184 if (sizeofHdr != NIFTI_HDR_SIZE) {
185 qCritical() <<
"MriNiftiIO::read - not a NIfTI-1 file (sizeof_hdr ="
186 << sizeofHdr <<
"):" << niiFile;
192 auto i16 = [&](
int off) {
193 return bigEndian ? readBE<qint16>(hdr + off) : readLE<qint16>(hdr + off);
195 auto f32 = [&](
int off) {
197 quint32 raw = readBE<quint32>(hdr + off);
199 std::memcpy(&v, &raw,
sizeof(
float));
202 quint32 raw = readLE<quint32>(hdr + off);
204 std::memcpy(&v, &raw,
sizeof(
float));
209 qint16 ndim = i16(40);
214 if (ndim < 3 || nx <= 0 || ny <= 0 || nz <= 0) {
215 qCritical() <<
"MriNiftiIO::read - degenerate dims" << ndim << nx << ny << nz;
219 qint16 datatype = i16(70);
220 qint16 bitpix = i16(72);
224 float qfac = f32(76);
228 const float dx = std::fabs(f32(80));
229 const float dy = std::fabs(f32(84));
230 const float dz = std::fabs(f32(88));
232 const float voxOffset = f32(108);
233 const float sclSlope = f32(112);
234 const float sclInter = f32(116);
236 const qint16 qformCode = i16(252);
237 const qint16 sformCode = i16(254);
243 case DT_UINT8: mriType =
MRI_UCHAR; bpv = 1;
break;
244 case DT_INT8: mriType =
MRI_UCHAR; bpv = 1;
break;
245 case DT_INT16: mriType =
MRI_SHORT; bpv = 2;
break;
246 case DT_UINT16: mriType =
MRI_SHORT; bpv = 2;
break;
247 case DT_INT32: mriType =
MRI_INT; bpv = 4;
break;
248 case DT_FLOAT32: mriType =
MRI_FLOAT; bpv = 4;
break;
250 qCritical() <<
"MriNiftiIO::read - unsupported datatype" << datatype;
255 Matrix4f vox2ras = Matrix4f::Identity();
257 for (
int c = 0; c < 4; ++c) {
258 vox2ras(0, c) = f32(280 + c * 4);
259 vox2ras(1, c) = f32(296 + c * 4);
260 vox2ras(2, c) = f32(312 + c * 4);
262 }
else if (qformCode > 0) {
263 const float b = f32(256);
264 const float c = f32(260);
265 const float d = f32(264);
266 const float aSq = 1.0f - (b * b + c * c + d * d);
267 const float a = aSq > 0.0f ? std::sqrt(aSq) : 0.0f;
269 R << a*a + b*b - c*c - d*d, 2.0f*(b*c - a*d), 2.0f*(b*d + a*c),
270 2.0f*(b*c + a*d), a*a + c*c - b*b - d*d, 2.0f*(c*d - a*b),
271 2.0f*(b*d - a*c), 2.0f*(c*d + a*b), a*a + d*d - b*b - c*c;
272 Matrix3f
S = Matrix3f::Zero();
277 vox2ras.block<3, 3>(0, 0) = M;
278 vox2ras(0, 3) = f32(268);
279 vox2ras(1, 3) = f32(272);
280 vox2ras(2, 3) = f32(276);
286 vox2ras(0, 3) = -0.5f * dx * nx;
287 vox2ras(1, 3) = -0.5f * dy * ny;
288 vox2ras(2, 3) = -0.5f * dz * nz;
292 Vector3f col0 = vox2ras.block<3, 1>(0, 0);
293 Vector3f col1 = vox2ras.block<3, 1>(0, 1);
294 Vector3f col2 = vox2ras.block<3, 1>(0, 2);
295 float sx = col0.norm();
296 float sy = col1.norm();
297 float sz = col2.norm();
298 if (sx <= 0.0f) sx = dx > 0.0f ? dx : 1.0f;
299 if (sy <= 0.0f) sy = dy > 0.0f ? dy : 1.0f;
300 if (sz <= 0.0f) sz = dz > 0.0f ? dz : 1.0f;
302 Vector3f xRas = col0 / sx;
303 Vector3f yRas = col1 / sy;
304 Vector3f zRas = col2 / sz;
306 Vector4f centreVox(nx * 0.5f, ny * 0.5f, nz * 0.5f, 1.0f);
307 Vector4f cRas4 = vox2ras * centreVox;
313 volData.
nframes = nt > 0 ? nt : 1;
314 volData.
type = mriType;
320 volData.
x_ras = xRas;
321 volData.
y_ras = yRas;
322 volData.
z_ras = zRas;
323 volData.
c_ras = cRas4.head<3>();
328 const qint64 dataOff =
static_cast<qint64
>(voxOffset > 0.0f ? voxOffset : NIFTI_HDR_SIZE + 4);
329 const qint64 frameSize =
static_cast<qint64
>(nx) * ny * nz * bpv;
330 if (bytes.size() < dataOff + frameSize) {
331 qCritical() <<
"MriNiftiIO::read - data section truncated (need"
332 << (dataOff + frameSize) <<
"bytes, got" << bytes.size() <<
")";
336 QDataStream stream(bytes);
337 stream.setByteOrder(bigEndian ? QDataStream::BigEndian : QDataStream::LittleEndian);
338 stream.setFloatingPointPrecision(QDataStream::SinglePrecision);
339 stream.device()->seek(dataOff);
341 const int nslice = nz;
342 const int nPixels = nx * ny;
343 volData.
slices.resize(nslice);
346 const bool useScale = (sclSlope != 0.0f) && !(sclSlope == 1.0f && sclInter == 0.0f);
348 for (
int k = 0; k < nslice; ++k) {
352 slice.
dimx = sx / 1000.0f;
353 slice.
dimy = sy / 1000.0f;
359 slice.
pixels.resize(nPixels);
360 if (datatype == DT_INT8) {
361 for (
int p = 0; p < nPixels; ++p) {
364 slice.
pixels[p] =
static_cast<unsigned char>(v < 0 ? 0 : v);
367 for (
int p = 0; p < nPixels; ++p) {
378 if (datatype == DT_UINT16) {
379 for (
int p = 0; p < nPixels; ++p) {
385 for (
int p = 0; p < nPixels; ++p) {
388 slice.
pixelsWord[p] =
static_cast<unsigned short>(v < 0 ? 0 : v);
396 for (
int p = 0; p < nPixels; ++p) {
406 for (
int p = 0; p < nPixels; ++p) {
419 for (
int p = 0; p < slice.
pixelsFloat.size(); ++p) {
424 Vector3f sliceOrigin;
425 sliceOrigin(0) = vox2rasFinal(0, 2) * k + vox2rasFinal(0, 3);
426 sliceOrigin(1) = vox2rasFinal(1, 2) * k + vox2rasFinal(1, 3);
427 sliceOrigin(2) = vox2rasFinal(2, 2) * k + vox2rasFinal(2, 3);
430 sliceRot.col(0) = vox2rasFinal.block<3, 1>(0, 0);
431 sliceRot.col(1) = vox2rasFinal.block<3, 1>(0, 1);
432 sliceRot.col(2) = vox2rasFinal.block<3, 1>(0, 2);
438 qInfo(
"NIfTI file: %dx%dx%d, datatype=%d, voxel %.3fx%.3fx%.3f mm, c_ras=(%.2f, %.2f, %.2f)",
439 nx, ny, nz, datatype, sx, sy, sz,