NIFTI Orientation Issues

Hello,

I am having trouble with NIFTI orientations (again). I am using the Bruker reader I contributed to read in a 2dseq file and then save to NIFTI. When I then read that file with FSL or nibabel, the orientation comes back as different. This seems to be some incompatibility between NIFTI and ITK orientation definitions, but I can’t work it out.

I have a test dataset with images acquired in axial, sagittal and coronal orientations. The Bruker header claims the orientation of the axial image is:

1 0 0
0 1 0
0 0 1

ITK saves this as a NIFTI with

pixdim[0] = 1 //q_fac
quatern_b = 0
quatern_c = 0
quatern_d = 1

This doesn’t look right to me - if I follow the guide at https://nifti.nimh.nih.gov/nifti-1/documentation/nifti1fields/nifti1fields_pages/quatern.html then I think quatern_d should be 0, not 1.

However, ITK will read this file back in and recover the identity orientation matrix. FSL on the other hand, reads the orientation as

-1 0 0
0 -1 0
0 0 1

According to the above link, I think that FSL is actually doing this correctly. However, I’m really confused by this because I thought that both ITK and FSL used the official libnifti underneath? What am I missing? Should I be manipulating the orientations before trying to save to NIFTI somehow?

(I am compiling against ITK master to use my Bruker IO).

Thanks for any help.

1 Like

I think the confusion may be due to different frames of reference. ITK uses LPS, while NIfTI and FSL use RAS, so those matrices are the same after accounting for frame of reference. Some links to extended discussion below.

  • Bruker uses LPS:


  • ITK converts the matrix to RAS when writing the NIfTI:

https://github.com/InsightSoftwareConsortium/ITK/blob/master/Modules/IO/NIFTI/src/itkNiftiImageIO.cxx#L1905-L1974 (especially 1959-60)

2 Likes

Hi @ihnorton - I owe you thanks twice. First for the LPS / RAS difference - I think you are correct here, and second for pointing out those Bruker readers. I had completely missed them when I started to update the ITK reader.

This question was actually a prelude to another, because for coronal 2D images there appears to be a real bug on top of my LPS/RAS confusion. However, I will look through both of those readers first and see if I can find a solution there. I’ll send an update here when I know more.

1 Like

(First a question of Discourse etiquette - should I be replying to my own messages or should I be editing them?)

I have had a chance to look at bruker2nifti.py, and it does not deal with the orientation information correctly either - at least judged by whether my test phantom acquired in axial, coronal & sagittal orientations lines up on itself in FSLView.

As best as I can tell, there appears to be a bug in the 2dseq format for 2D coronal. I have now special-cased this in my ITK reader and my images all line up. I will test more thoroughly here and submit a patch on gerrit in the near future.

1 Like

Hi Toby,

Regarding this issue, in most cases it is best to create a reply because it will result in a new message sent to folks following via email.

Thanks,
Matt

In case it’s helpful for folks to know, Discourse usually has an edit window before emails are sent out, so small edits within that window are fine and will still be reflected in the notification emails. I think it’s 5 or 10 minutes by default (and configurable). But +1 about replies rather than large edits after the fact.

1 Like

All–

I’ve run into an issue that I think is related to @spinicist’s question–I hope it’s alright if I jump into this thread. My understanding had been that, although ITK and each image type all have their own convention, ITK should preserve this through reads/writes. That is, if I use ITK to convert from image type A to image type B, and then back to image type A (e.g., *.nii.gz => *.vtk => *.nii.gz), the direction matrices of the first and last images should be unchanged. However, in the MCVE below, I observe that the direction matrix changes from *.nii.gz to *.vtk, and doesn’t change back from *.vtk to *.nii.gz. Is this expected behavior? If so, is there an idiomatic way to handle this in ITK?

I’m compiling on the master branch, using g++ 5.4.0 on Ubuntu 16.04, using -std=c++11. (I’ve omitted c++11 language features in the example below just in case.)

Best, and thanks,

–Davis

#include "itkImageFileReader.h"
#include "itkImageFileWriter.h"

typedef short                              PixelType;
const unsigned int                         Dimension = 3;
typedef itk::Image< PixelType, Dimension > ImageType;

typedef itk::ImageFileReader< ImageType > ReaderType;
typedef itk::ImageFileWriter< ImageType > WriterType;

ImageType::Pointer
CreateImage()
{
  ImageType::Pointer image = ImageType::New();
  ImageType::IndexType start;
  start.Fill(0);

  ImageType::SizeType size;
  size.Fill(64);

  ImageType::RegionType region(start, size);
  image->SetRegions(region);
  image->Allocate();
  image->FillBuffer(0);

  ImageType::DirectionType direction = image->GetDirection();
  direction(1, 1) = -1;
  image->SetDirection( direction );

  return image;
}

int main()
{

  ImageType::Pointer image = CreateImage();
  std::cout << image->GetDirection() << std::endl;
  // 1 0 0
  // 0 -1 0
  // 0 0 1

  {
    WriterType::Pointer writer = WriterType::New();
    writer->SetFileName( "./a.nii.gz" );
    writer->SetInput( image );
    writer->Update();
  }

  {
    ReaderType::Pointer reader = ReaderType::New();
    reader->SetFileName( "./a.nii.gz" );
    reader->Update();

    std::cout << reader->GetOutput()->GetDirection() << std::endl;
    // 1 0 0
    // 0 -1 0
    // 0 0 1

    WriterType::Pointer writer = WriterType::New();
    writer->SetInput( reader->GetOutput() );
    writer->SetFileName( "./b.vtk" );
    writer->Update();
  }

  {
    ReaderType::Pointer reader = ReaderType::New();
    reader->SetFileName( "./b.vtk" );
    reader->Update();

    std::cout << reader->GetOutput()->GetDirection() << std::endl;
    // 1 0 0
    // 0 1 0 <------- !!!
    // 0 0 1

    WriterType::Pointer writer = WriterType::New();
    writer->SetInput( reader->GetOutput() );
    writer->SetFileName( "./c.nii.gz" );
    writer->Update();
  }

  {
    ReaderType::Pointer reader = ReaderType::New();
    reader->SetFileName( "./c.nii.gz" );
    reader->Update();

    std::cout << reader->GetOutput()->GetDirection() << std::endl;
    // 1 0 0
    // 0 1 0 <------- !!!
    // 0 0 1
  }

  return EXIT_SUCCESS;

}

Hi @DVigneault,

The observed behavior is a side effect of the fact that the .vtk format does not support image direction information. A different format that supports direction information will need to be used.

HTH,
Matt

3 Likes

MataImage and NRRD are best supported formats in ITK. If you can freely choose the intermediate format, chose one of these two.

1 Like

Thanks very much @matt.mccormick and @dzenanz! That’s very helpful.

The motivation for my question was that I’ve been having trouble co-registering meshes (read using vtkSTLReader or vtkOBJReader) and images (read using ITK and converted using itk::ImageToVTKImageFilter) when displaying in VTK. I noticed that if I read an STL (or OBJ) and Nifti file, they are not co-registered. However, if I first convert the Nifti file to a VTK file, they are.

It sounds like, by converting to VTK and discarding the direction matrix, I “accidentally” put the image and mesh into the same coordinate system. Is that plausible? Is there a better mesh file format I should be using for inter-operation between ITK and VTK (I’ve been using STL or OBJ because they be read/written by ITK, VTK, and Blender), or is there a step I’m missing during my mesh reads/writes?

Best, and thanks again,

–Davis

Hi Davis,

Since the mesh points are located in physical space, there should not be too many issues there.

The problem encountered is likely from itk::ImageToVTKImageFiltervtkImageData does not support orientation, so some other method will need to be used, e.g. an additional explicit transformation, in VTK for its position to show up properly.

HTH,
Matt

Thanks, @matt.mccormick, that helps very much; you’ve lead me to this example [1], which may be exactly what I’m looking for.

[1] https://itk.org/Wiki/ITK/Examples/IO/itkVtkImageConvertDICOM

1 Like

Yes, that example tries to do exactly what you want. An older version of the example had image reading instead of generating one itself.

One last thing, then I will stop polluting this thread. :slight_smile: Previously, I’d been using vtkImagePlaneWidget to scroll through my images. However, this widget seems to be restricted to viewing axis-aligned data, preventing me from using it to view slices through the (world-coordinate-aligned) volume rendered data. However, I was able to replicate the functionality pretty closely. Broadly, the steps were:

  1. Use vtkPlaneWidget to select the plane (in world coordinates) you’d like to reslice.
  2. Tie this using a callback to a vtkPlaneSource.
  3. Transform the vtkPlaneSource data back into image space.
  4. Use vtkProbeFilter to resample the image data according to the transformed vtkPlaneSource data.
  5. Transform the data back, and display it on top of the vtkPlaneWidget.

When you do this on to of a volume render, moving the vtkPlaneWidget around gives the effect of reslicing the image data underlying the volume render. I don’t know if this is the best or right way to approach this problem, but it works quite well for my purposes, so I thought I’d share the code in case anyone needs to do something similar.

Best,

–Davis

Screenshot:

screenshot

Code:

// ITK
#include <itkImageFileReader.h>

// VTK
#include <vtkNIFTIImageReader.h>
#include <vtkPlaneSource.h>
#include <vtkProbeFilter.h>
#include <vtkLookupTable.h>
#include <vtkRenderer.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkDataSetMapper.h>
#include <vtkProperty.h>
#include <vtkActor.h>
#include <vtkProperty.h>
#include <vtkPlaneWidget.h>
#include <vtkCommand.h>
#include <vtkMatrix4x4.h>
#include <vtkGPUVolumeRayCastMapper.h>
#include <vtkPiecewiseFunction.h>
#include <vtkVolumeProperty.h>
#include <vtkColorTransferFunction.h>
#include <vtkTransform.h>
#include <vtkTransformFilter.h>
#include <vtkAxesActor.h>
#include <vtkImageData.h>

/*
 Given an ITK image, use the origin and direction to create a vtkMatrix.
 This matrix can be used to construct a vtkTransform, which will take data in image coordinates
 and transform them into world coordinates.
 */
template<typename TImage>
void
GetVTKTransformationMatrixFromITKImage(const typename TImage::Pointer image, vtkMatrix4x4* mat)
{
  // Direction Matrix
  typename TImage::DirectionType d = image->GetDirection();
  for (int i=0; i<3; ++i)
    {
    for (int j=0; j<3; ++j)
      {
      mat->SetElement(i, j, d(i,j));
      }
    }
 
  // Origin
  typename TImage::PointType o = image->GetOrigin();
  for (int i=0; i<3; ++i)
    {
    mat->SetElement(i, 3, o[i]);
    }
}

/*
 Get a sensible origin, point1, and point2 (in world coordinates) to initialize
 the vtkPlaneWidget, which is used to control which cross section of the data is viewed.
 */
template<typename TImage>
void
GetPointsFromITKImage(const typename TImage::Pointer image,
                      typename TImage::PointType &o,
                      typename TImage::PointType &p1,
                      typename TImage::PointType &p2)
{
  const typename TImage::SizeType size = image->GetLargestPossibleRegion().GetSize();
  const typename TImage::IndexType index = image->GetLargestPossibleRegion().GetIndex();

  auto o_index = index;
  o_index[2] += size[2] / 2;

  auto p1_index = index;
  p1_index[2] += size[2] / 2;
  p1_index[0] += size[0];

  auto p2_index = index;
  p2_index[1] += size[1];
  p2_index[2] += size[2] / 2;

  image->TransformIndexToPhysicalPoint(o_index, o);
  image->TransformIndexToPhysicalPoint(p1_index, p1);
  image->TransformIndexToPhysicalPoint(p2_index, p2);
}

/*
 A callback function which keeps the position of a vtkPlaneSource
 in sync with a vtkPlaneWidget.
 */
class PlaneWidgetCallback: public vtkCommand
{
public:
static PlaneWidgetCallback *New()
{
  return new PlaneWidgetCallback;
}

void Execute(vtkObject*, unsigned long, void*) override
{
  this->source->SetOrigin(this->widget->GetOrigin());
  this->source->SetPoint1(this->widget->GetPoint1());
  this->source->SetPoint2(this->widget->GetPoint2());
}

vtkPlaneWidget* widget;
vtkPlaneSource* source;

};

// Standard ITK typedefs.
typedef itk::Image<short, 3> TImage;
typedef itk::ImageFileReader< TImage > TReader;

int
main (int argc, char **argv)
{

  if (argc != 2)
    {
    std::cerr << "Usage: " << argv[0] << " ./path/to/image.nii" << std::endl;
    return EXIT_FAILURE;
    }

  const std::string fileName(argv[1]);

  //
  // The ITK image reader is preserves the image's origin and direction.
  // Using this data, vtkTransforms are constructed which can transform
  // `forward` (i.e., from image coordinates to world coordinates) and
  // `backward` (i.e., from world coordinates to image coordinates).
  //

  TReader::Pointer itkReader = TReader::New();
  itkReader->SetFileName( fileName );
  itkReader->Update();

  const vtkSmartPointer<vtkMatrix4x4> fMat = vtkSmartPointer<vtkMatrix4x4>::New(); // forward
  const vtkSmartPointer<vtkMatrix4x4> bMat = vtkSmartPointer<vtkMatrix4x4>::New(); // backward

  GetVTKTransformationMatrixFromITKImage<TImage>( itkReader->GetOutput(), fMat);

  vtkSmartPointer<vtkTransform> fTrans = vtkSmartPointer<vtkTransform>::New();
  vtkSmartPointer<vtkTransform> bTrans = vtkSmartPointer<vtkTransform>::New();

  fTrans->SetMatrix( fMat );
  fTrans->GetInverse( bMat );
  bTrans->SetMatrix( bMat );

  // Get sensible points to initialize the vtkPlaneWidget.
  TImage::PointType origin, point1, point2;

  GetPointsFromITKImage<TImage>(itkReader->GetOutput(), origin, point1, point2);

  //
  // Create renderer, render window, and render window interactor.
  //

  vtkSmartPointer<vtkRenderer> renderer =
    vtkSmartPointer<vtkRenderer>::New();
  vtkSmartPointer<vtkRenderWindow> window =
    vtkSmartPointer<vtkRenderWindow>::New();
  window->AddRenderer(renderer);
  vtkSmartPointer<vtkRenderWindowInteractor> interactor =
    vtkSmartPointer<vtkRenderWindowInteractor>::New();
  interactor->SetRenderWindow(window);

  //
  // Read Image.
  // Note that, unlike itk::ImageFileReaders, vtk*ImageReaders do not
  // preserve origin or direction information.
  //

  vtkSmartPointer<vtkNIFTIImageReader> vtkReader =
    vtkSmartPointer<vtkNIFTIImageReader>::New();
  vtkReader->SetFileName( fileName.c_str() );
  vtkReader->Update();

  std::cout << vtkReader->GetOutput()->GetOrigin()[0] << ' '
            << vtkReader->GetOutput()->GetOrigin()[1] << ' '
            << vtkReader->GetOutput()->GetOrigin()[2] << std::endl;

  //
  // Render the volume, putting the volume into proper world coordinates.
  //

  // 200 to 700 is approximately the range of hounsfield units for
  // contrast enhansement in the cardiac bloodpool; we set alpha to be
  // zero outside of this range and 0.05 within.
  // Change these values to reflect the intensity range of the structure you
  // would like to highlight.
  vtkSmartPointer<vtkPiecewiseFunction> compositeOpacity =
  vtkSmartPointer<vtkPiecewiseFunction>::New();
  compositeOpacity->AddPoint(199.0,0.0);
  compositeOpacity->AddPoint(200.0,0.05);
  compositeOpacity->AddPoint(700.0,0.05);
  compositeOpacity->AddPoint(701.0,0.0);

  // Some arbitrary color gradient.
  vtkSmartPointer<vtkColorTransferFunction> color =
  vtkSmartPointer<vtkColorTransferFunction>::New();
  color->AddRGBPoint(200.0,1.0,0.5,0.5);
  color->AddRGBPoint(700.0,5.0,1.5,0.5);

  // Add to opacity and color gradient to a volume property.
  vtkSmartPointer<vtkVolumeProperty> volumeProperty =
  vtkSmartPointer<vtkVolumeProperty>::New();
  volumeProperty->SetScalarOpacity(compositeOpacity);
  volumeProperty->SetColor(color);

  // Create a volume mapper.
  vtkSmartPointer<vtkGPUVolumeRayCastMapper> volumeMapper =
    vtkSmartPointer<vtkGPUVolumeRayCastMapper>::New();
  volumeMapper->SetInputConnection( vtkReader->GetOutputPort() );

  // Create a volume, and add both the mapper and volume property.
  vtkSmartPointer<vtkVolume> volume =
    vtkSmartPointer<vtkVolume>::New();
  volume->SetMapper(volumeMapper);
  volume->SetProperty(volumeProperty);

  // Important!!
  // Set the user matrix to fMat (the forward transformation matrix
  // calculated from the ITK image).
  volume->SetUserMatrix(fMat);

  //
  // Image Plane, Oriented in World Coordinates
  //

  // vtkPlaneWidget has user interaction baked in; this will be used
  // to allow the user to select which slice to view.
  vtkSmartPointer<vtkPlaneWidget> plane =
    vtkSmartPointer<vtkPlaneWidget>::New();

  // Initialize the widget position with the origin, point1, and point2 from above.
  plane->SetInteractor(interactor);
  plane->SetOrigin(origin.GetVnlVector().data_block());
  plane->SetPoint1(point1.GetVnlVector().data_block());
  plane->SetPoint2(point2.GetVnlVector().data_block());
  plane->On();

  // Define a model for the "image". Its geometry matches the implicit
  // sphere used to clip the isosurface
  vtkSmartPointer<vtkPlaneSource> planeSource =
    vtkSmartPointer<vtkPlaneSource>::New();
  planeSource->SetOrigin(origin.GetVnlVector().data_block());
  planeSource->SetPoint1(point1.GetVnlVector().data_block());
  planeSource->SetPoint2(point2.GetVnlVector().data_block());
  planeSource->SetResolution(128, 128); // Good compromise between detail and performance.

  // This callback will update the plane source, keeping its position in sync
  // with the widget.
  vtkSmartPointer<PlaneWidgetCallback> callback =
    vtkSmartPointer<PlaneWidgetCallback>::New();
  callback->widget = plane;
  callback->source = planeSource;
  plane->AddObserver( vtkCommand::InteractionEvent, callback );

  // Transform the plane souce back into image coordinates.
  vtkSmartPointer<vtkTransformFilter> bTransFilter =
    vtkSmartPointer<vtkTransformFilter>::New();
  bTransFilter->SetTransform( bTrans );
  bTransFilter->SetInputConnection( planeSource->GetOutputPort() );

  // vtkProbeFilter will sample the values of the image data at the polydata positions.
  vtkSmartPointer<vtkProbeFilter> probe =
    vtkSmartPointer<vtkProbeFilter>::New();
  probe->SetInputConnection( bTransFilter->GetOutputPort() );
  probe->SetSourceConnection( vtkReader->GetOutputPort() );
  probe->Update();

  // Transform the sampled data back into world coordinates.
  vtkSmartPointer<vtkTransformFilter> fTransFilter =
    vtkSmartPointer<vtkTransformFilter>::New();
  fTransFilter->SetTransform( fTrans );
  fTransFilter->SetInputConnection( probe->GetOutputPort() );

  // Define a suitable grayscale lut
  vtkSmartPointer<vtkLookupTable> bwLut =
    vtkSmartPointer<vtkLookupTable>::New();
  bwLut->SetTableRange(-1024, 2048);
  bwLut->SetSaturationRange(0, 0);
  bwLut->SetHueRange(0, 0);
  bwLut->SetValueRange (.2, 1);
  bwLut->Build();

  // Create a mapper for the sampled image data.
  vtkSmartPointer<vtkDataSetMapper> imageSliceMapper =
    vtkSmartPointer<vtkDataSetMapper>::New();
  imageSliceMapper->SetInputConnection( fTransFilter->GetOutputPort());
  imageSliceMapper->SetScalarRange(-1024, 2048);
  imageSliceMapper->SetLookupTable(bwLut);

  vtkSmartPointer<vtkActor> image =
    vtkSmartPointer<vtkActor>::New();
  image->SetMapper(imageSliceMapper);

  //
  // Add coordinate system axes, so we have a reference for position and orientation.
  //

  vtkSmartPointer<vtkAxesActor> axes = vtkSmartPointer<vtkAxesActor>::New();
  axes->SetTotalLength(100,100,100);
  axes->SetShaftTypeToCylinder();
  axes->SetCylinderRadius(0.01);

  //
  // Put it all together.
  //

  // Set the background color
  renderer->SetBackground(1.0, 1.0, 1.0);

  // Add actors and volume.
  renderer->AddActor( axes );
  renderer->AddActor( image );
  renderer->AddVolume( volume );

  // Set a background color for the renderer and set the size of the
  // render window (expressed in pixels).
  window->SetSize(640, 480);

  // Initialize the event loop and then start it.
  interactor->Initialize();
  interactor->Start();

  return EXIT_SUCCESS;
}

[1] https://itk.org/Wiki/ITK/Examples/IO/itkVtkImageConvertDICOM

1 Like