Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
1345 lines (1193 sloc) 39.6 KB
/*=========================================================================
Program: Visualization Toolkit
Module: vtkNIFTIImageReader.cxx
Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
All rights reserved.
See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
This software is distributed WITHOUT ANY WARRANTY; without even
the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
PURPOSE. See the above copyright notice for more information.
=========================================================================*/
#include "vtkNIFTIImageReader.h"
#include "vtkByteSwap.h"
#include "vtkCommand.h"
#include "vtkDataArray.h"
#include "vtkErrorCode.h"
#include "vtkImageData.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkMath.h"
#include "vtkMatrix4x4.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkStreamingDemandDrivenPipeline.h"
#include "vtkStringArray.h"
#include "vtkVersion.h"
#include "vtksys/SystemTools.hxx"
#include <sstream>
// Header for NIFTI
#include "vtkNIFTIImageHeader.h"
#include "vtkNIFTIImagePrivate.h"
// Header for zlib
#include "vtk_zlib.h"
#include <cctype>
#include <cstring>
#include <string>
vtkStandardNewMacro(vtkNIFTIImageReader);
//----------------------------------------------------------------------------
vtkNIFTIImageReader::vtkNIFTIImageReader()
{
for (int i = 0; i < 8; i++)
{
this->Dim[i] = 0;
}
for (int i = 0; i < 8; i++)
{
this->PixDim[i] = 1.0;
}
this->TimeAsVector = false;
this->RescaleSlope = 1.0;
this->RescaleIntercept = 0.0;
this->QFac = 1.0;
this->QFormMatrix = nullptr;
this->SFormMatrix = nullptr;
this->NIFTIHeader = nullptr;
this->PlanarRGB = false;
}
//----------------------------------------------------------------------------
vtkNIFTIImageReader::~vtkNIFTIImageReader()
{
if (this->QFormMatrix)
{
this->QFormMatrix->Delete();
}
if (this->SFormMatrix)
{
this->SFormMatrix->Delete();
}
if (this->NIFTIHeader)
{
this->NIFTIHeader->Delete();
}
}
//----------------------------------------------------------------------------
namespace
{ // anonymous namespace
void vtkNIFTIImageReaderSwapHeader(nifti_1_header* hdr)
{
// Common to NIFTI and Analyze 7.5
vtkByteSwap::SwapVoidRange(&hdr->sizeof_hdr, 1, 4);
vtkByteSwap::SwapVoidRange(&hdr->extents, 1, 4);
vtkByteSwap::SwapVoidRange(&hdr->session_error, 1, 2);
vtkByteSwap::SwapVoidRange(hdr->dim, 8, 2);
vtkByteSwap::SwapVoidRange(&hdr->intent_p1, 1, 4); // unused in 7.5
vtkByteSwap::SwapVoidRange(&hdr->intent_p2, 1, 4); // unused in 7.5
vtkByteSwap::SwapVoidRange(&hdr->intent_p3, 1, 4); // unused in 7.5
vtkByteSwap::SwapVoidRange(&hdr->intent_code, 1, 2); // unused in 7.5
vtkByteSwap::SwapVoidRange(&hdr->datatype, 1, 2);
vtkByteSwap::SwapVoidRange(&hdr->bitpix, 1, 2);
vtkByteSwap::SwapVoidRange(&hdr->slice_start, 1, 2); // dim_un0 in 7.5
vtkByteSwap::SwapVoidRange(hdr->pixdim, 8, 4);
vtkByteSwap::SwapVoidRange(&hdr->vox_offset, 1, 4);
vtkByteSwap::SwapVoidRange(&hdr->scl_slope, 1, 4); // unused in 7.5
vtkByteSwap::SwapVoidRange(&hdr->scl_inter, 1, 4); // unused in 7.5
vtkByteSwap::SwapVoidRange(&hdr->slice_end, 1, 2); // unused in 7.5
vtkByteSwap::SwapVoidRange(&hdr->cal_max, 1, 4);
vtkByteSwap::SwapVoidRange(&hdr->cal_min, 1, 4);
vtkByteSwap::SwapVoidRange(&hdr->slice_duration, 1, 4); // compressed in 7.5
vtkByteSwap::SwapVoidRange(&hdr->toffset, 1, 4); // verified in 7.5
vtkByteSwap::SwapVoidRange(&hdr->glmax, 1, 4);
vtkByteSwap::SwapVoidRange(&hdr->glmin, 1, 4);
// All NIFTI-specific (meaning is totally different in Analyze 7.5)
if (strncmp(hdr->magic, "ni1", 4) == 0 || strncmp(hdr->magic, "n+1", 4) == 0)
{
vtkByteSwap::SwapVoidRange(&hdr->qform_code, 1, 2);
vtkByteSwap::SwapVoidRange(&hdr->sform_code, 1, 2);
vtkByteSwap::SwapVoidRange(&hdr->quatern_b, 1, 4);
vtkByteSwap::SwapVoidRange(&hdr->quatern_c, 1, 4);
vtkByteSwap::SwapVoidRange(&hdr->quatern_d, 1, 4);
vtkByteSwap::SwapVoidRange(&hdr->qoffset_x, 1, 4);
vtkByteSwap::SwapVoidRange(&hdr->qoffset_y, 1, 4);
vtkByteSwap::SwapVoidRange(&hdr->qoffset_z, 1, 4);
vtkByteSwap::SwapVoidRange(hdr->srow_x, 4, 4);
vtkByteSwap::SwapVoidRange(hdr->srow_y, 4, 4);
vtkByteSwap::SwapVoidRange(hdr->srow_z, 4, 4);
}
}
void vtkNIFTIImageReaderSwapHeader(nifti_2_header* hdr)
{
vtkByteSwap::SwapVoidRange(&hdr->sizeof_hdr, 1, 4);
vtkByteSwap::SwapVoidRange(&hdr->datatype, 1, 2);
vtkByteSwap::SwapVoidRange(&hdr->bitpix, 1, 2);
vtkByteSwap::SwapVoidRange(hdr->dim, 8, 8);
vtkByteSwap::SwapVoidRange(&hdr->intent_p1, 1, 8);
vtkByteSwap::SwapVoidRange(&hdr->intent_p2, 1, 8);
vtkByteSwap::SwapVoidRange(&hdr->intent_p3, 1, 8);
vtkByteSwap::SwapVoidRange(hdr->pixdim, 8, 8);
vtkByteSwap::SwapVoidRange(&hdr->vox_offset, 1, 8);
vtkByteSwap::SwapVoidRange(&hdr->scl_slope, 1, 8);
vtkByteSwap::SwapVoidRange(&hdr->scl_inter, 1, 8);
vtkByteSwap::SwapVoidRange(&hdr->cal_max, 1, 8);
vtkByteSwap::SwapVoidRange(&hdr->cal_min, 1, 8);
vtkByteSwap::SwapVoidRange(&hdr->slice_duration, 1, 8);
vtkByteSwap::SwapVoidRange(&hdr->toffset, 1, 8);
vtkByteSwap::SwapVoidRange(&hdr->slice_start, 1, 8);
vtkByteSwap::SwapVoidRange(&hdr->slice_end, 1, 8);
vtkByteSwap::SwapVoidRange(&hdr->qform_code, 1, 4);
vtkByteSwap::SwapVoidRange(&hdr->sform_code, 1, 4);
vtkByteSwap::SwapVoidRange(&hdr->quatern_b, 1, 8);
vtkByteSwap::SwapVoidRange(&hdr->quatern_c, 1, 8);
vtkByteSwap::SwapVoidRange(&hdr->quatern_d, 1, 8);
vtkByteSwap::SwapVoidRange(&hdr->qoffset_x, 1, 8);
vtkByteSwap::SwapVoidRange(&hdr->qoffset_y, 1, 8);
vtkByteSwap::SwapVoidRange(&hdr->qoffset_z, 1, 8);
vtkByteSwap::SwapVoidRange(hdr->srow_x, 4, 8);
vtkByteSwap::SwapVoidRange(hdr->srow_y, 4, 8);
vtkByteSwap::SwapVoidRange(hdr->srow_z, 4, 8);
vtkByteSwap::SwapVoidRange(&hdr->slice_code, 1, 4);
vtkByteSwap::SwapVoidRange(&hdr->xyzt_units, 1, 4);
vtkByteSwap::SwapVoidRange(&hdr->intent_code, 1, 4);
}
} // end anonymous namespace
//----------------------------------------------------------------------------
vtkNIFTIImageHeader* vtkNIFTIImageReader::GetNIFTIHeader()
{
if (!this->NIFTIHeader)
{
this->NIFTIHeader = vtkNIFTIImageHeader::New();
}
return this->NIFTIHeader;
}
//----------------------------------------------------------------------------
void vtkNIFTIImageReader::PrintSelf(ostream& os, vtkIndent indent)
{
this->Superclass::PrintSelf(os, indent);
os << indent << "TimeAsVector: " << (this->TimeAsVector ? "On\n" : "Off\n");
os << indent << "TimeDimension: " << this->GetTimeDimension() << "\n";
os << indent << "TimeSpacing: " << this->GetTimeSpacing() << "\n";
os << indent << "RescaleSlope: " << this->RescaleSlope << "\n";
os << indent << "RescaleIntercept: " << this->RescaleIntercept << "\n";
os << indent << "QFac: " << this->QFac << "\n";
os << indent << "QFormMatrix:";
if (this->QFormMatrix)
{
double mat[16];
vtkMatrix4x4::DeepCopy(mat, this->QFormMatrix);
for (int i = 0; i < 16; i++)
{
os << " " << mat[i];
}
os << "\n";
}
else
{
os << " (none)\n";
}
os << indent << "SFormMatrix:";
if (this->SFormMatrix)
{
double mat[16];
vtkMatrix4x4::DeepCopy(mat, this->SFormMatrix);
for (int i = 0; i < 16; i++)
{
os << " " << mat[i];
}
os << "\n";
}
else
{
os << " (none)\n";
}
os << indent << "NIFTIHeader:" << (this->NIFTIHeader ? "\n" : " (none)\n");
os << indent << "PlanarRGB: " << (this->PlanarRGB ? "On\n" : "Off\n");
}
//----------------------------------------------------------------------------
bool vtkNIFTIImageReader::CheckExtension(const char* filename, const char* ext)
{
if (strlen(ext) == 4 && ext[0] == '.')
{
size_t n = strlen(filename);
if (n > 2 && filename[n - 3] == '.' && tolower(filename[n - 2]) == 'g' &&
tolower(filename[n - 1]) == 'z')
{
n -= 3;
}
if (n > 3 && filename[n - 4] == '.' && tolower(filename[n - 3]) == tolower(ext[1]) &&
tolower(filename[n - 2]) == tolower(ext[2]) && tolower(filename[n - 1]) == tolower(ext[3]))
{
return true;
}
}
return false;
}
//----------------------------------------------------------------------------
char* vtkNIFTIImageReader::ReplaceExtension(
const char* filename, const char* ext1, const char* ext2)
{
char* newname = nullptr;
if (strlen(ext1) == 4 && ext1[0] == '.' && strlen(ext2) == 4 && ext2[0] == '.')
{
size_t n = strlen(filename);
size_t m = n;
newname = new char[n + 4];
strcpy(newname, filename);
// check for trailing .gz
if (n > 2 && filename[n - 3] == '.' && tolower(filename[n - 2]) == 'g' &&
tolower(filename[n - 1]) == 'z')
{
m = n - 3;
}
if (vtkNIFTIImageReader::CheckExtension(filename, ext1))
{
// replace the extension
if (isupper(filename[m - 3]))
{
newname[m - 3] = toupper(ext2[1]);
newname[m - 2] = toupper(ext2[2]);
newname[m - 1] = toupper(ext2[3]);
}
else
{
newname[m - 3] = tolower(ext2[1]);
newname[m - 2] = tolower(ext2[2]);
newname[m - 1] = tolower(ext2[3]);
}
}
// existence of file
for (int i = 0; i < 2; i++)
{
if (vtksys::SystemTools::FileExists(newname))
{
return newname;
}
if (i == 0)
{
if (m < n)
{
// try again without the ".gz"
newname[m] = '\0';
n = m;
}
else
{
// try again with the ".gz"
newname[m] = '.';
newname[m + 1] = (isupper(newname[m - 3]) ? 'G' : 'g');
newname[m + 2] = (isupper(newname[m - 3]) ? 'Z' : 'z');
newname[m + 3] = '\0';
}
}
}
delete[] newname;
newname = nullptr;
}
return newname;
}
//----------------------------------------------------------------------------
int vtkNIFTIImageReader::CheckNIFTIVersion(const nifti_1_header* hdr)
{
int version = 0;
// Check for NIFTIv2. The NIFTIv2 magic number is stored where
// the data_type appears in the NIFTIv1 header.
if (hdr->data_type[0] == 'n' && (hdr->data_type[1] == '+' || hdr->data_type[1] == 'i') &&
(hdr->data_type[2] >= '2' && hdr->data_type[2] <= '9') && hdr->data_type[3] == '\0')
{
version = (hdr->data_type[2] - '0');
if (hdr->data_type[4] != '\r' || hdr->data_type[5] != '\n' || hdr->data_type[6] != '\032' ||
hdr->data_type[7] != '\n')
{
// Indicate that file was corrupted by newline conversion
version = -version;
}
}
// Check for NIFTIv1
else if (hdr->magic[0] == 'n' && (hdr->magic[1] == '+' || hdr->magic[1] == 'i') &&
hdr->magic[2] == '1' && hdr->magic[3] == '\0')
{
version = 1;
}
return version;
}
//----------------------------------------------------------------------------
bool vtkNIFTIImageReader::CheckAnalyzeHeader(const nifti_1_header* hdr)
{
if (hdr->sizeof_hdr == 348 || // Analyze 7.5 header size
hdr->sizeof_hdr == 1543569408) // byte-swapped 348
{
return true;
}
return false;
}
//----------------------------------------------------------------------------
int vtkNIFTIImageReader::CanReadFile(const char* filename)
{
vtkDebugMacro("Opening NIFTI file " << filename);
char* hdrname = vtkNIFTIImageReader::ReplaceExtension(filename, ".img", ".hdr");
if (hdrname == nullptr)
{
return 0;
}
// try opening file
gzFile file = gzopen(hdrname, "rb");
delete[] hdrname;
if (!file)
{
return 0;
}
// read and check the header
bool canRead = false;
nifti_1_header hdr;
int hsize = vtkNIFTIImageHeader::NIFTI1HeaderSize; // nifti_1 header size
int rsize = gzread(file, &hdr, hsize);
if (rsize == hsize)
{
int version = vtkNIFTIImageReader::CheckNIFTIVersion(&hdr);
if (version > 0)
{
// NIFTI file
canRead = true;
}
else if (version == 0)
{
// Analyze 7.5 file
canRead = vtkNIFTIImageReader::CheckAnalyzeHeader(&hdr);
}
}
gzclose(file);
return canRead;
}
//----------------------------------------------------------------------------
int vtkNIFTIImageReader::RequestInformation(vtkInformation* vtkNotUsed(request),
vtkInformationVector** vtkNotUsed(inputVector), vtkInformationVector* outputVector)
{
// Clear the error indicator.
this->SetErrorCode(vtkErrorCode::NoError);
// Create the header object
if (!this->NIFTIHeader)
{
this->NIFTIHeader = vtkNIFTIImageHeader::New();
}
// default byte order is native byte order
#ifdef VTK_WORDS_BIGENDIAN
bool isLittleEndian = false;
#else
bool isLittleEndian = true;
#endif
const char* filename = nullptr;
char* hdrname = nullptr;
if (this->FileNames)
{
vtkIdType n = this->FileNames->GetNumberOfValues();
int headers = 0;
for (vtkIdType i = 0; i < n; i++)
{
filename = this->FileNames->GetValue(i);
// this checks for .hdr and .hdr.gz, case insensitive
if (vtkNIFTIImageReader::CheckExtension(filename, ".hdr"))
{
if (++headers < 2)
{
hdrname = new char[strlen(filename) + 1];
strcpy(hdrname, filename);
}
}
}
if (n != 2 || headers != 1)
{
vtkErrorMacro("There must be two files and one must be a .hdr file.");
delete[] hdrname;
return 0;
}
}
else
{
filename = this->GetFileName();
}
if (filename == nullptr)
{
vtkErrorMacro("A FileName must be provided");
this->SetErrorCode(vtkErrorCode::NoFileNameError);
return 0;
}
if (hdrname == nullptr)
{
hdrname = vtkNIFTIImageReader::ReplaceExtension(filename, ".img", ".hdr");
}
if (hdrname == nullptr)
{
vtkErrorMacro("Unable to locate header for file " << filename);
this->SetErrorCode(vtkErrorCode::CannotOpenFileError);
return 0;
}
vtkDebugMacro("Opening NIFTI file " << hdrname);
// try opening file
gzFile file = gzopen(hdrname, "rb");
if (!file)
{
vtkErrorMacro("Cannot open file " << hdrname);
delete[] hdrname;
this->SetErrorCode(vtkErrorCode::CannotOpenFileError);
return 0;
}
// read and check the header
bool canRead = false;
int niftiVersion = 0;
nifti_1_header* hdr1 = new nifti_1_header;
nifti_2_header hdr2obj;
nifti_2_header* hdr2 = &hdr2obj;
const int hsize = vtkNIFTIImageHeader::NIFTI1HeaderSize;
int rsize = gzread(file, hdr1, hsize);
if (rsize == hsize)
{
niftiVersion = vtkNIFTIImageReader::CheckNIFTIVersion(hdr1);
if (niftiVersion >= 2)
{
// the header was a NIFTIv2 header
const int h2size = vtkNIFTIImageHeader::NIFTI2HeaderSize;
// copy what was read into the NIFTIv1 header
memcpy(hdr2, hdr1, hsize);
// read the remainder of the NIFTIv2 header
rsize = gzread(file, reinterpret_cast<char*>(hdr2) + hsize, h2size - hsize);
if (rsize == h2size - hsize)
{
canRead = true;
}
}
else if (niftiVersion == 1)
{
// the header was a NIFTIv1 header
canRead = true;
}
else if (niftiVersion == 0)
{
// Analyze 7.5 file
canRead = vtkNIFTIImageReader::CheckAnalyzeHeader(hdr1);
}
}
if (canRead)
{
if (niftiVersion >= 2)
{
if (NIFTI_NEEDS_SWAP(*hdr2))
{
vtkNIFTIImageReaderSwapHeader(hdr2);
isLittleEndian = !isLittleEndian;
}
this->NIFTIHeader->SetHeader(hdr2);
}
else
{
if (NIFTI_NEEDS_SWAP(*hdr1))
{
vtkNIFTIImageReaderSwapHeader(hdr1);
isLittleEndian = !isLittleEndian;
}
// convert NIFTIv1 header into NIFTIv2
this->NIFTIHeader->SetHeader(hdr1);
this->NIFTIHeader->GetHeader(hdr2);
}
}
gzclose(file);
// delete the NIFTIv1 header, use the NIFTIv2 header
delete hdr1;
hdr1 = nullptr;
if (!canRead)
{
const char* message =
(niftiVersion <= -2 ? "NIfTI header has newline corruption " : "Bad NIfTI header in file ");
vtkErrorMacro(<< message << hdrname);
this->SetErrorCode(vtkErrorCode::UnrecognizedFileTypeError);
delete[] hdrname;
return 0;
}
delete[] hdrname;
// number of dimensions
int ndim = hdr2->dim[0];
if (ndim < 0 || ndim > 7)
{
vtkErrorMacro("NIfTI image has illegal ndim of " << ndim);
this->SetErrorCode(vtkErrorCode::FileFormatError);
return 0;
}
// sanity checks
for (int i = 0; i < 8; i++)
{
// voxel spacing cannot be zero
if (hdr2->pixdim[i] == 0)
{
hdr2->pixdim[i] = 1.0;
}
if (i > ndim)
{
// dimensions greater than ndim have size of 1
hdr2->dim[i] = 1;
}
else if (hdr2->dim[i] < 0)
{
vtkErrorMacro("NIfTI image dimension " << i << " is negative");
this->SetErrorCode(vtkErrorCode::FileFormatError);
return 0;
}
else if ((hdr2->dim[i] & 0x7fffffff) != hdr2->dim[i])
{
// dimension does not fit in signed int
vtkErrorMacro("NIfTI image dimension " << i << " is larger than int32");
this->SetErrorCode(vtkErrorCode::FileFormatError);
return 0;
}
}
if (niftiVersion > 0)
{
// pass rescale info to user (do not rescale in the reader)
this->RescaleSlope = hdr2->scl_slope;
this->RescaleIntercept = hdr2->scl_inter;
}
else
{
// rescale information not available for Analyze 7.5
this->RescaleSlope = 1.0;
this->RescaleIntercept = 0.0;
}
// header might be extended, vox_offset says where data starts
this->SetHeaderSize(static_cast<unsigned long>(hdr2->vox_offset));
// endianness of data
if (isLittleEndian)
{
this->SetDataByteOrderToLittleEndian();
}
else
{
this->SetDataByteOrderToBigEndian();
}
// NIFTI images are stored in a single file, not one file per slice
this->SetFileDimensionality(3);
// NIFTI uses a lower-left-hand origin
this->FileLowerLeftOn();
// dim
this->SetDataExtent(0, hdr2->dim[1] - 1, 0, hdr2->dim[2] - 1, 0, hdr2->dim[3] - 1);
// pixdim
this->SetDataSpacing(hdr2->pixdim[1], hdr2->pixdim[2], hdr2->pixdim[3]);
// offset is part of the transform, so set origin to zero
this->SetDataOrigin(0.0, 0.0, 0.0);
// map the NIFTI type to a VTK type and number of components
static const int typeMap[][3] = { { NIFTI_TYPE_INT8, VTK_TYPE_INT8, 1 },
{ NIFTI_TYPE_UINT8, VTK_TYPE_UINT8, 1 }, { NIFTI_TYPE_INT16, VTK_TYPE_INT16, 1 },
{ NIFTI_TYPE_UINT16, VTK_TYPE_UINT16, 1 }, { NIFTI_TYPE_INT32, VTK_TYPE_INT32, 1 },
{ NIFTI_TYPE_UINT32, VTK_TYPE_UINT32, 1 }, { NIFTI_TYPE_INT64, VTK_TYPE_INT64, 1 },
{ NIFTI_TYPE_UINT64, VTK_TYPE_UINT64, 1 }, { NIFTI_TYPE_FLOAT32, VTK_TYPE_FLOAT32, 1 },
{ NIFTI_TYPE_FLOAT64, VTK_TYPE_FLOAT64, 1 }, { NIFTI_TYPE_COMPLEX64, VTK_TYPE_FLOAT32, 2 },
{ NIFTI_TYPE_COMPLEX128, VTK_TYPE_FLOAT64, 2 }, { NIFTI_TYPE_RGB24, VTK_TYPE_UINT8, 3 },
{ NIFTI_TYPE_RGBA32, VTK_TYPE_UINT8, 4 }, { 0, 0, 0 } };
int scalarType = 0;
int numComponents = 0;
for (int i = 0; typeMap[2] != nullptr; i++)
{
if (hdr2->datatype == typeMap[i][0])
{
scalarType = typeMap[i][1];
numComponents = typeMap[i][2];
break;
}
}
// if loop finished without finding a match
if (numComponents == 0)
{
vtkErrorMacro("Unrecognized NIFTI data type: " << hdr2->datatype);
this->SetErrorCode(vtkErrorCode::FileFormatError);
return 0;
}
// vector planes become vector components
if (ndim >= 5)
{
numComponents *= hdr2->dim[5];
}
if (ndim >= 4 && this->TimeAsVector)
{
numComponents *= hdr2->dim[4];
}
this->SetDataScalarType(scalarType);
this->SetNumberOfScalarComponents(numComponents);
// Set the output information.
vtkInformation* outInfo = outputVector->GetInformationObject(0);
vtkDataObject::SetPointDataActiveScalarInfo(
outInfo, this->DataScalarType, this->NumberOfScalarComponents);
outInfo->Set(vtkDataObject::SPACING(), this->DataSpacing, 3);
outInfo->Set(vtkDataObject::ORIGIN(), this->DataOrigin, 3);
outInfo->Set(vtkStreamingDemandDrivenPipeline::WHOLE_EXTENT(), this->DataExtent, 6);
// copy dim for when RequestData is called
for (int j = 0; j < 8; j++)
{
this->Dim[j] = hdr2->dim[j];
this->PixDim[j] = hdr2->pixdim[j];
}
// === Image Orientation in NIfTI files ===
//
// The vtkImageData class does not provide a way of storing image
// orientation. So when we read a NIFTI file, we should also provide
// the user with a 4x4 matrix that can transform VTK's data coordinates
// into NIFTI's intended coordinate system for the image. NIFTI defines
// these coordinate systems as:
// 1) NIFTI_XFORM_SCANNER_ANAT - coordinate system of the imaging device
// 2) NIFTI_XFORM_ALIGNED_ANAT - result of registration to another image
// 3) NIFTI_XFORM_TALAIRACH - a brain-specific coordinate system
// 4) NIFTI_XFORM_MNI_152 - a similar brain-specific coordinate system
//
// NIFTI images can store orientation in two ways:
// 1) via a quaternion (orientation and offset, i.e. rigid-body)
// 2) via a matrix (used to store e.g. the results of registration)
//
// A NIFTI file can have both a quaternion (qform) and matrix (sform)
// stored in the same file. The NIFTI documentation recommends that
// the qform be used to record the "scanner anatomical" coordinates
// and that the sform, if present, be used to define a secondary
// coordinate system, e.g. a coordinate system derived through
// registration to a template.
//
// -- Quaternion Representation --
//
// If the "quaternion" form is used, then the following equation
// defines the transformation from voxel indices to NIFTI's world
// coordinates, where R is the rotation matrix computed from the
// quaternion components:
//
// [ x ] [ R11 R12 R13 ] [ pixdim[1] * i ] [ qoffset_x ]
// [ y ] = [ R21 R22 R23 ] [ pixdim[2] * j ] + [ qoffset_y ]
// [ z ] [ R31 R32 R33 ] [ pixdim[3] * k * qfac ] [ qoffset_z ]
//
// qfac is stored in pixdim[0], if it is equal to -1 then the slices
// are stacked in reverse: VTK will have to reorder the slices in order
// to maintain a right-handed coordinate transformation between indices
// and coordinates.
//
// Let's call VTK data coordinates X,Y,Z to distinguish them from
// the NIFTI coordinates x,y,z. The relationship between X,Y,Z and
// x,y,z is expressed by a 4x4 matrix M:
//
// [ x ] [ M11 M12 M13 M14 ] [ X ]
// [ y ] = [ M21 M22 M23 M24 ] [ Y ]
// [ z ] [ M31 M32 M33 M34 ] [ Z ]
// [ 1 ] [ 0 0 0 1 ] [ 1 ]
//
// where the VTK data coordinates X,Y,Z are related to the
// VTK structured coordinates IJK (i.e. point indices) by:
//
// X = I*Spacing[0] + Origin[0]
// Y = J*Spacing[1] + Origin[1]
// Z = K*Spacing[2] + Origin[2]
//
// Now let's consider: when we read a NIFTI image, how should we set
// the Spacing, the Origin, and the matrix M? Let's consider the
// cases:
//
// 1) If there is no qform, then R is identity and qoffset is zero,
// and qfac will be 1 (never -1). So:
// I,J,K = i,j,k, Spacing = pixdim, Origin = 0, M = Identity
//
// 2) If there is a qform, and qfac is 1, then:
//
// I,J,K = i,j,k (i.e. voxel order in VTK same as in NIFTI)
//
// Spacing[0] = pixdim[1]
// Spacing[1] = pixdim[2]
// Spacing[2] = pixdim[3]
//
// Origin[0] = 0.0
// Origin[1] = 0.0
// Origin[2] = 0.0
//
// [ R11 R12 R13 qoffset_x ]
// M = [ R21 R22 R23 qoffset_y ]
// [ R31 R32 R33 qoffset_z ]
// [ 0 0 0 1 ]
//
// Note that we cannot store qoffset in the origin. That would
// be mathematically incorrect. It would only give us the right
// offset when R is the identity matrix.
//
// 3) If there is a qform and qfac is -1, then the situation is more
// compilcated. We have three choices, each of which is a compromise:
// a) we can use Spacing[2] = qfac*pixdim[3], i.e. use a negative
// slice spacing, which might cause some VTK algorithms to
// misbehave (the VTK tests only use images with positive spacing).
// b) we can use M13 = -R13, M23 = -R23, M33 = -R33 i.e. introduce
// a flip into the matrix, which is very bad for VTK rendering
// algorithms and should definitely be avoided.
// c) we can reverse the order of the slices in VTK relative to
// NIFTI, which allows us to preserve positive spacing and retain
// a well-behaved rotation matrix, by using these equations:
//
// K = number_of_slices - k - 1
//
// M14 = qoffset_x - (number_of_slices - 1)*pixdim[3]*R13
// M24 = qoffset_y - (number_of_slices - 1)*pixdim[3]*R23
// M34 = qoffset_z - (number_of_slices - 1)*pixdim[3]*R33
//
// This will give us data that will be well-behaved in VTK, at
// the expense of making VTK slice numbers not match with
// the original NIFTI slice numbers. NIFTI slice 0 will become
// VTK slice N-1, and the order will be reversed.
//
// -- Matrix Representation --
//
// If the "matrix" form is used, then pixdim[] is ignored, and the
// voxel spacing is implicitly stored in the matrix. In addition,
// the matrix may have a negative determinant, there is no "qfac"
// flip-factor as there is in the quaternion representation.
//
// Let S be the matrix stored in the NIFTI header, and let M be our
// desired coordinate transformation from VTK data coordinates X,Y,Z
// to NIFTI data coordinates x,y,z (see discussion above for more
// information). Let's consider the cases where the determinant
// is positive, or negative.
//
// 1) If the determinant is positive, we will factor the spacing
// (but not the origin) out of the matrix.
//
// Spacing[0] = pixdim[1]
// Spacing[1] = pixdim[2]
// Spacing[2] = pixdim[3]
//
// Origin[0] = 0.0
// Origin[1] = 0.0
// Origin[2] = 0.0
//
// [ S11/pixdim[1] S12/pixdim[2] S13/pixdim[3] S14 ]
// M = [ S21/pixdim[1] S22/pixdim[2] S23/pixdim[3] S24 ]
// [ S31/pixdim[1] S32/pixdim[2] S33/pixdim[3] S34 ]
// [ 0 0 0 1 ]
//
// 2) If the determinant is negative, then we face the same choices
// as when qfac is -1 for the quaternion transformation. We can:
// a) use a negative Z spacing and multiply the 3rd column of M by -1
// b) keep the matrix as is (with a negative determinant)
// c) reorder the slices, multiply the 3rd column by -1, and adjust
// the 4th column of the matrix:
//
// M14 = S14 + (number_of_slices - 1)*S13
// M24 = S24 + (number_of_slices - 1)*S23
// M34 = S34 + (number_of_slices - 1)*S33
//
// The third choice will provide a VTK image that has positive
// spacing and a matrix with a positive determinant.
//
// -- Analyze 7.5 Orientation --
//
// This reader provides only bare-bones backwards compatibility with
// the Analyze 7.5 file header. We do not orient these files.
// Initialize
this->QFac = 1.0;
if (this->QFormMatrix)
{
this->QFormMatrix->Delete();
this->QFormMatrix = nullptr;
}
if (this->SFormMatrix)
{
this->SFormMatrix->Delete();
this->SFormMatrix = nullptr;
}
// Set the QFormMatrix from the quaternion data in the header.
// See the long discussion above for more information.
if (niftiVersion > 0 && hdr2->qform_code > 0)
{
double mmat[16];
double rmat[3][3];
double quat[4];
quat[1] = hdr2->quatern_b;
quat[2] = hdr2->quatern_c;
quat[3] = hdr2->quatern_d;
quat[0] = 1.0 - quat[1] * quat[1] - quat[2] * quat[2] - quat[3] * quat[3];
if (quat[0] > 0.0)
{
quat[0] = sqrt(quat[0]);
}
else
{
quat[0] = 0.0;
}
vtkMath::QuaternionToMatrix3x3(quat, rmat);
// If any matrix values are close to zero, then they should actually
// be zero but aren't due to limited numerical precision in the
// quaternion-to-matrix conversion.
const double tol = 2.384185791015625e-07; // 2**-22
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
if (fabs(rmat[i][j]) < tol)
{
rmat[i][j] = 0.0;
}
}
vtkMath::Normalize(rmat[i]);
}
// first row
mmat[0] = rmat[0][0];
mmat[1] = rmat[0][1];
mmat[2] = rmat[0][2];
mmat[3] = hdr2->qoffset_x;
// second row
mmat[4] = rmat[1][0];
mmat[5] = rmat[1][1];
mmat[6] = rmat[1][2];
mmat[7] = hdr2->qoffset_y;
// third row
mmat[8] = rmat[2][0];
mmat[9] = rmat[2][1];
mmat[10] = rmat[2][2];
mmat[11] = hdr2->qoffset_z;
mmat[12] = 0.0;
mmat[13] = 0.0;
mmat[14] = 0.0;
mmat[15] = 1.0;
this->QFac = ((hdr2->pixdim[0] < 0) ? -1.0 : 1.0);
if (this->QFac < 0)
{
// We will be reversing the order of the slices, so the first VTK
// slice will be at the position of the last NIfTI slice, and we
// must adjust the offset to compensate for this.
mmat[3] -= rmat[0][2] * hdr2->pixdim[3] * (hdr2->dim[3] - 1);
mmat[7] -= rmat[1][2] * hdr2->pixdim[3] * (hdr2->dim[3] - 1);
mmat[11] -= rmat[2][2] * hdr2->pixdim[3] * (hdr2->dim[3] - 1);
}
this->QFormMatrix = vtkMatrix4x4::New();
this->QFormMatrix->DeepCopy(mmat);
}
// Set the SFormMatrix from the matrix information in the header.
// See the long discussion above for more information.
if (niftiVersion > 0 && hdr2->sform_code > 0)
{
double mmat[16];
// first row
mmat[0] = hdr2->srow_x[0] / hdr2->pixdim[1];
mmat[1] = hdr2->srow_x[1] / hdr2->pixdim[2];
mmat[2] = hdr2->srow_x[2] / hdr2->pixdim[3];
mmat[3] = hdr2->srow_x[3];
// second row
mmat[4] = hdr2->srow_y[0] / hdr2->pixdim[1];
mmat[5] = hdr2->srow_y[1] / hdr2->pixdim[2];
mmat[6] = hdr2->srow_y[2] / hdr2->pixdim[3];
mmat[7] = hdr2->srow_y[3];
// third row
mmat[8] = hdr2->srow_z[0] / hdr2->pixdim[1];
mmat[9] = hdr2->srow_z[1] / hdr2->pixdim[2];
mmat[10] = hdr2->srow_z[2] / hdr2->pixdim[3];
mmat[11] = hdr2->srow_z[3];
mmat[12] = 0.0;
mmat[13] = 0.0;
mmat[14] = 0.0;
mmat[15] = 1.0;
// Set QFac to -1 if the determinant is negative, unless QFac
// has already been set by the qform information.
if (vtkMatrix4x4::Determinant(mmat) < 0 && hdr2->qform_code == 0)
{
this->QFac = -1.0;
}
if (this->QFac < 0)
{
// If QFac is set to -1 then the slices will be reversed, and we must
// reverse the slice orientation vector (the third column of the matrix)
// to compensate.
// reverse the slice orientation vector
mmat[2] = -mmat[2];
mmat[6] = -mmat[6];
mmat[10] = -mmat[10];
// adjust the offset to compensate for changed slice ordering
mmat[3] += hdr2->srow_x[2] * (hdr2->dim[3] - 1);
mmat[7] += hdr2->srow_y[2] * (hdr2->dim[3] - 1);
mmat[11] += hdr2->srow_z[2] * (hdr2->dim[3] - 1);
}
this->SFormMatrix = vtkMatrix4x4::New();
this->SFormMatrix->DeepCopy(mmat);
if (this->SFormMatrix->Determinant() < 0)
{
vtkWarningMacro("SFormMatrix is flipped compared to QFormMatrix");
}
}
return 1;
}
//----------------------------------------------------------------------------
int vtkNIFTIImageReader::RequestData(vtkInformation* request,
vtkInformationVector** vtkNotUsed(inputVector), vtkInformationVector* outputVector)
{
// check whether the reader is in an error state
if (this->GetErrorCode() != vtkErrorCode::NoError)
{
return 0;
}
// which output port did the request come from
int outputPort = request->Get(vtkDemandDrivenPipeline::FROM_OUTPUT_PORT());
// for now, this reader has only one output
if (outputPort > 0)
{
return 1;
}
vtkInformation* outInfo = outputVector->GetInformationObject(0);
int extent[6];
outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT(), extent);
// get the data object, allocate memory
vtkImageData* data = static_cast<vtkImageData*>(outInfo->Get(vtkDataObject::DATA_OBJECT()));
#if VTK_MAJOR_VERSION >= 6
this->AllocateOutputData(data, outInfo, extent);
#else
this->AllocateOutputData(data, extent);
#endif
data->GetPointData()->GetScalars()->SetName("NIFTI");
const char* filename = nullptr;
char* imgname = nullptr;
if (this->FileNames)
{
vtkIdType n = this->FileNames->GetNumberOfValues();
int headers = 0;
for (vtkIdType i = 0; i < n; i++)
{
filename = this->FileNames->GetValue(i);
// this checks for .hdr and .hdr.gz, case insensitive
if (vtkNIFTIImageReader::CheckExtension(filename, ".hdr"))
{
headers++;
}
else
{
imgname = new char[strlen(filename) + 1];
strcpy(imgname, filename);
}
}
if (n != 2 || headers != 1)
{
vtkErrorMacro("There must be two files and one must be a .hdr file.");
delete[] imgname;
return 0;
}
}
else
{
filename = this->GetFileName();
}
if (filename == nullptr)
{
vtkErrorMacro("A FileName must be provided");
return 0;
}
if (imgname == nullptr)
{
imgname = vtkNIFTIImageReader::ReplaceExtension(filename, ".hdr", ".img");
}
if (imgname == nullptr)
{
vtkErrorMacro("Unable to locate image for file " << filename);
return 0;
}
vtkDebugMacro("Opening NIFTI file " << imgname);
data->GetPointData()->GetScalars()->SetName("NIFTI");
unsigned char* dataPtr = static_cast<unsigned char*>(data->GetScalarPointer());
gzFile file = gzopen(imgname, "rb");
delete[] imgname;
if (!file)
{
return 0;
}
// check if planar RGB is applicable (Analyze only)
bool planarRGB = (this->PlanarRGB &&
(this->NIFTIHeader->GetDataType() == NIFTI_TYPE_RGB24 ||
this->NIFTIHeader->GetDataType() == NIFTI_TYPE_RGBA32));
int swapBytes = this->GetSwapBytes();
int scalarSize = data->GetScalarSize();
int numComponents = data->GetNumberOfScalarComponents();
int timeDim = (this->Dim[0] >= 4 ? this->Dim[4] : 1);
int vectorDim = (this->Dim[0] >= 5 ? this->Dim[5] : 1);
if (this->TimeAsVector)
{
vectorDim *= timeDim;
}
int outSizeX = extent[1] - extent[0] + 1;
int outSizeY = extent[3] - extent[2] + 1;
int outSizeZ = extent[5] - extent[4] + 1;
z_off_t fileVoxelIncr = scalarSize * numComponents / vectorDim;
z_off_t fileRowIncr = fileVoxelIncr * this->Dim[1];
z_off_t filePlaneIncr = fileRowIncr * this->Dim[2];
z_off_t fileSliceIncr = fileRowIncr * this->Dim[2];
z_off_t fileTimeIncr = fileSliceIncr * this->Dim[3];
z_off_t fileVectorIncr = fileTimeIncr * this->Dim[4];
if (this->TimeAsVector)
{
fileVectorIncr = fileTimeIncr;
}
// planar RGB requires different increments
int planarSize = 1; // if > 1, indicates planar RGB
if (planarRGB)
{
planarSize = numComponents / vectorDim;
fileVoxelIncr = scalarSize;
fileRowIncr = fileVoxelIncr * this->Dim[1];
filePlaneIncr = fileRowIncr * this->Dim[2];
}
// add a buffer for planar-vector to packed-vector conversion
unsigned char* rowBuffer = nullptr;
if (vectorDim > 1 || planarRGB)
{
rowBuffer = new unsigned char[outSizeX * fileVoxelIncr];
}
// special increment to reverse the slices if needed
vtkIdType sliceOffset = 0;
if (this->GetQFac() < 0)
{
// put slices in reverse order
sliceOffset = scalarSize * numComponents;
sliceOffset *= outSizeX;
sliceOffset *= outSizeY;
dataPtr += sliceOffset * (outSizeZ - 1);
}
// special increment to handle planar RGB
vtkIdType planarOffset = 0;
vtkIdType planarEndOffset = 0;
if (planarRGB)
{
planarOffset = scalarSize * numComponents;
planarOffset *= outSizeX;
planarOffset *= outSizeY;
planarOffset -= scalarSize;
planarEndOffset = planarOffset - scalarSize * (planarSize - 1);
}
// report progress every 2% of the way to completion
this->InvokeEvent(vtkCommand::StartEvent);
this->UpdateProgress(0.0);
vtkIdType target =
static_cast<vtkIdType>(0.02 * planarSize * outSizeY * outSizeZ * vectorDim) + 1;
vtkIdType count = 0;
// seek to the start of the data
z_off_t offset = static_cast<z_off_t>(this->GetHeaderSize());
offset += extent[0] * fileVoxelIncr;
offset += extent[2] * fileRowIncr;
offset += extent[4] * fileSliceIncr;
// read the data one row at a time, do planar-to-packed conversion
// of vector components if NIFTI file has a vector dimension
int rowSize = fileVoxelIncr / scalarSize * outSizeX;
int t = 0; // counter for time
int c = 0; // counter for vector components
int j = 0; // counter for rows
int p = 0; // counter for planes (planar RGB)
int k = 0; // counter for slices
unsigned char* ptr = dataPtr;
int errorCode = 0;
while (!this->AbortExecute)
{
if (offset)
{
int rval = gzseek(file, offset, SEEK_CUR);
if (rval == -1)
{
errorCode = vtkErrorCode::FileFormatError;
if (gzeof(file))
{
errorCode = vtkErrorCode::PrematureEndOfFileError;
}
break;
}
}
if (vectorDim == 1 && !planarRGB)
{
// read directly into the output instead of into a buffer
rowBuffer = ptr;
}
int code = gzread(file, rowBuffer, rowSize * scalarSize);
if (code != rowSize * scalarSize)
{
errorCode = vtkErrorCode::FileFormatError;
if (gzeof(file))
{
errorCode = vtkErrorCode::PrematureEndOfFileError;
}
break;
}
if (swapBytes != 0 && scalarSize > 1)
{
vtkByteSwap::SwapVoidRange(rowBuffer, rowSize, scalarSize);
}
if (vectorDim == 1 && !planarRGB)
{
// advance the pointer to the next row
ptr += outSizeX * numComponents * scalarSize;
rowBuffer = nullptr;
}
else
{
// write vector plane to packed vector component
unsigned char* tmpPtr = rowBuffer;
z_off_t skipOther = scalarSize * numComponents - fileVoxelIncr;
for (int i = 0; i < outSizeX; i++)
{
// write one vector component of one voxel
z_off_t n = fileVoxelIncr;
do
{
*ptr++ = *tmpPtr++;
} while (--n);
// skip past the other components
ptr += skipOther;
}
}
if (++count % target == 0)
{
this->UpdateProgress(0.02 * count / target);
}
// offset to skip unread sections of the file, for when
// the update extent is less than the whole extent
offset = fileRowIncr - outSizeX * fileVoxelIncr;
if (++j == outSizeY)
{
j = 0;
offset += filePlaneIncr - outSizeY * fileRowIncr;
// back up for next plane (R, G, or B) if planar mode
ptr -= planarOffset;
if (++p == planarSize)
{
p = 0;
ptr += planarEndOffset; // advance to start of next slice
ptr -= 2 * sliceOffset; // for reverse slice order
if (++k == outSizeZ)
{
k = 0;
offset += fileVectorIncr - outSizeZ * fileSliceIncr;
if (++t == timeDim)
{
t = 0;
}
if (++c == vectorDim)
{
break;
}
// back up the ptr to the beginning of the image,
// then increment to the next vector component
ptr = dataPtr + c * fileVoxelIncr * planarSize;
if (this->TimeAsVector)
{
// if timeDim is included in the vectorDim (and hence in the
// VTK scalar components) then we have to make sure that
// the vector components are packed before the time steps
ptr = dataPtr + (c + t * (vectorDim - 1)) / timeDim * fileVoxelIncr * planarSize;
}
}
}
}
}
if (vectorDim > 1 || planarRGB)
{
delete[] rowBuffer;
}
gzclose(file);
if (errorCode)
{
const char* errorText = "Error in NIFTI file, cannot read.";
if (errorCode == vtkErrorCode::PrematureEndOfFileError)
{
errorText = "NIFTI file is truncated, some data is missing.";
}
this->SetErrorCode(errorCode);
vtkErrorMacro(<< errorText);
return 0;
}
this->UpdateProgress(1.0);
this->InvokeEvent(vtkCommand::EndEvent);
return 1;
}
You can’t perform that action at this time.