[vtkusers] Correct anatomical orientation of volumes from Niftii and DICOM
ochampao
ochampao at hotmail.com
Wed May 9 13:26:48 EDT 2018
Hi vtkUsers,
I am developing an application for displaying medical images (four pane
viewer with Sagittal, Coronal and Axial views). The volumes are loaded from
different files that can be DICOM, Niftii or HDF5. Multiple volumes can be
loaded and displayed simultaneously (by overlaying 2D slices).
Until recently I have been using vtkDICOMImageReader for DICOM (see
loadDicom()) and vtkNIFTIImageReader for Niftii (see loadNiftii()). Using
these two functions I was able to display the 3 views with the correct
anatomical orientations (i.e. Sagittal, Coronal and Axial). After reading
the very nice guide by David Gobbi about Medical Image Orientation (see
http://calgaryimageanalysis.ca/wiki/images/5/52/Image-orientation.pdf )
I followed the recommendation therein and decided to switch to
vtkGDCMImageReader (see loadDicomGDCM() ). Despite using FileLowerLeftOn()
and SetOutputOrigin() (as recommended in the guide) after switching to the
vtkGDCMImageReader I am unable to display datasets coming from both DICOM
and Niftii with the correct anatomical orientation.
Using the pipeline as configured in the code segments below, I am able to
correctly display volumes loaded from Niftii files (using loadNiftii()) and
incorrectly display volumes loaded from DICOM (using loadDicomGDCM()). The
2D slices loaded using loadDicomGDCM() appear flipped both in the X and Y
axes. The X axis flip can be avoided by skipping the call to
FileLowerLeftOn().
So, my question is: how can I setup/correct my pipeline below to correctly
display volumes loaded from both Niftii and DICOM (using
vtkGDCMImageReader)?
Below you can see relevant segments of my code.
Thanks for your help
Panos.
================================================================
// setups empty view with a vtkImageStack for overalying slices from
multiple datasets
void QuadView::initStaticPipelineForView(const IQuadViewUI::VTKDockWidget
view)
{
// create an object that will hold the pointers to the static objects of
the pipeline
PipelineObjects2D pipelineObject2D;
// setup image stack for overalying the multiple slices
pipelineObject2D.imageStack = vtkSmartPointer<vtkImageStack>::New();
// Setup renderers
pipelineObject2D.renderer = vtkSmartPointer<vtkRenderer>::New();
pipelineObject2D.renderer->AddViewProp(pipelineObject2D.imageStack);
pipelineObject2D.renderer->GetActiveCamera()->ParallelProjectionOn();
pipelineObject2D.renderer->ResetCameraClippingRange();
pipelineObject2D.renderer->ResetCamera();
// Setup camera for each view
if (view == IQuadViewUI::VTKDockWidget::TopLeft) // Axial
{
// do nothing
}
if (view == IQuadViewUI::VTKDockWidget::TopRight) // Sagittal
{
pipelineObject2D.renderer->GetActiveCamera()->Azimuth(90);
pipelineObject2D.renderer->GetActiveCamera()->Roll(90);
pipelineObject2D.renderer->GetActiveCamera()->OrthogonalizeViewUp();
}
if (view == IQuadViewUI::VTKDockWidget::BottomLeft) // Coronal
{
pipelineObject2D.renderer->GetActiveCamera()->Elevation(90);
pipelineObject2D.renderer->GetActiveCamera()->OrthogonalizeViewUp();
}
// Setup render window
pipelineObject2D.renderWindow =
vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
pipelineObject2D.renderWindow->AddRenderer(pipelineObject2D.renderer);
// Attach render window to QVTK widget
QVTKOpenGLWidget& qVtkWidget = mQuadViewUI.getQVTKOpenGLWidget(view);
qVtkWidget.SetRenderWindow(pipelineObject2D.renderWindow);
//setup interaction style
pipelineObject2D.interactorStyle =
vtkSmartPointer<vtkInteractorStyleImage>::New();
pipelineObject2D.interactorStyle->SetInteractionModeToImageSlicing();
// Setup render window interactor
pipelineObject2D.renderWindowInteractor =
vtkSmartPointer<vtkRenderWindowInteractor>::New();
pipelineObject2D.renderWindowInteractor->SetInteractorStyle(pipelineObject2D.interactorStyle);
pipelineObject2D.renderWindowInteractor->SetRenderWindow(pipelineObject2D.renderWindow);
pipelineObject2D.renderWindowInteractor->Initialize();
mPipelineObject2DContainer.insert(std::pair<IQuadViewUI::VTKDockWidget,
PipelineObjects2D>(view, pipelineObject2D));
// render
pipelineObject2D.renderWindow->Render();
}
// load a new volume
unsigned long QuadView::addImage(const char* filename)
{
ImageDataStruct imageDataStruct;
// *** SELECT ONE OF READERS
imageDataStruct.imageData = loadDicomGDCM(filename);
//imageDataStruct.imageData = loadDicomGDCM(filename);
//imageDataStruct.imageData = loadNiftii(filename);
//imageDataStruct.imageData = loadH5(filename);
// setup lookup for the image data.
imageDataStruct.lookupTable = setupLookupTableInternal();
// iterate through the active 2D views
for (const auto &view : mQuadViewUI.getQVTKOpenGLWidget2DKeyList())
{
// get a reference to the
PipelineObjects2D &pipelineObjects2D =
mPipelineObject2DContainer.at(view);
// setup slice mapper for the current view
imageDataStruct.resliceMapperContainer[view] =
vtkSmartPointer<vtkImageResliceMapper>::New();
imageDataStruct.resliceMapperContainer[view]->SetInputData(imageDataStruct.imageData);
imageDataStruct.resliceMapperContainer[view]->SliceFacesCameraOn();
imageDataStruct.resliceMapperContainer[view]->SliceAtFocalPointOn();
// setup prop holding the slice
imageDataStruct.imageSliceContainer[view] =
vtkSmartPointer<vtkImageSlice>::New();
imageDataStruct.imageSliceContainer[view]->SetMapper(imageDataStruct.resliceMapperContainer[view]);
imageDataStruct.imageSliceContainer[view]->GetProperty()->SetLookupTable(imageDataStruct.lookupTable);
imageDataStruct.imageSliceContainer[view]->GetProperty()->UseLookupTableScalarRangeOn();
// add to image stack
pipelineObjects2D.imageStack->AddImage(imageDataStruct.imageSliceContainer[view]);
}
// reset camera and clipping range
resetCameras2D();
// render views
renderViews2D();
// save object holding the pointers to the dynamic part of pipeline
associated with the current image data
mImageDataContainer.insert(
std::pair<unsigned long, ImageDataStruct>(imageDataStruct.getUID(),
imageDataStruct));
return imageDataStruct.getUID();
}
// load volume from dicom using vtkDICOMImageReader
vtkSmartPointer<vtkImageData> QuadView::loadDicom(const char* filename)
const
{
vtkNew<vtkDICOMImageReader> dicomReader;
dicomReader->SetDirectoryName(filename);
dicomReader->Update();
return dicomReader->GetOutput();
}
// load volume from dicom using vtkDICOMImageReader
vtkSmartPointer<vtkGDCMImageReader> QuadView::loadDicomGDCM(const char*
finename) const
{
gdcm::Directory gdcmDir;
gdcmDir.Load(finename, false);
gdcm::IPPSorter ippSorter;
ippSorter.SetComputeZSpacing(true);
ippSorter.SetZSpacingTolerance(1e-3);
ippSorter.Sort(gdcmDir.GetFilenames());
vtkNew<vtkStringArray> sortedFiles;
for (const auto file : ippSorter.GetFilenames())
sortedFiles->InsertNextValue(file);
vtkNew<vtkGDCMImageReader> reader;
reader->SetFileNames(sortedFiles);
reader->FileLowerLeftOn();
reader->Update();
vtkNew<vtkImageChangeInformation> imageInfo;
imageInfo->SetOutputOrigin(0.0, 0.0, 0.0);
imageInfo->SetOutputSpacing(
reader->GetOutput()->GetSpacing()[0],
reader->GetOutput()->GetSpacing()[1],
ippSorter.GetZSpacing());
imageInfo->SetInputConnection(reader->GetOutputPort());
imageInfo->Update();
return imageInfo->GetOutput();
}
// load volume from Niftii
vtkSmartPointer<vtkImageData> QuadView::loadNiftii(const char* filename)
const
{
vtkNew<vtkNIFTIImageReader> niftiiReader;
niftiiReader->SetFileName(filename);
niftiiReader->Update();
return niftiiReader->GetOutput();
}
void QuadView::renderViews2D() const
{
// Render changes in 2D views
for (const auto view : mQuadViewUI.getQVTKOpenGLWidget2DKeyList())
mPipelineObject2DContainer.at(view).renderWindow->Render();
}
void QuadView::resetCameras2D() const
{
// iterate through the active 2D views.
for (const auto &view : mQuadViewUI.getQVTKOpenGLWidget2DKeyList())
{
// Reset camera & clipping range
mPipelineObject2DContainer.at(view).renderer->ResetCamera();
switch (view)
{
case IQuadViewUI::VTKDockWidget::TopLeft: //Axial
mPipelineObject2DContainer.at(view).renderer->GetActiveCamera()->SetViewUp(0,
1, 0);
mPipelineObject2DContainer.at(view).renderer->GetActiveCamera()->OrthogonalizeViewUp();
break;
case IQuadViewUI::VTKDockWidget::TopRight: //Sagittal
mPipelineObject2DContainer.at(view).renderer->GetActiveCamera()->SetViewUp(0,
0, -1);
mPipelineObject2DContainer.at(view).renderer->GetActiveCamera()->OrthogonalizeViewUp();
break;
case IQuadViewUI::VTKDockWidget::BottomLeft: //Coronal
mPipelineObject2DContainer.at(view).renderer->GetActiveCamera()->SetViewUp(0,
0, -1);
mPipelineObject2DContainer.at(view).renderer->GetActiveCamera()->OrthogonalizeViewUp();
break;
default:
break;
}
}
}
================================================================
--
Sent from: http://vtk.1045678.n5.nabble.com/VTK-Users-f1224199.html
More information about the vtkusers
mailing list