Medical Imaging Interaction Toolkit  2016.11.0
Medical Imaging Interaction Toolkit
mitkExtractSliceFilter.cpp
Go to the documentation of this file.
1 /*===================================================================
2 
3 The Medical Imaging Interaction Toolkit (MITK)
4 
5 Copyright (c) German Cancer Research Center,
6 Division of Medical and Biological Informatics.
7 All rights reserved.
8 
9 This software is distributed WITHOUT ANY WARRANTY; without
10 even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 A PARTICULAR PURPOSE.
12 
13 See LICENSE.txt or http://www.mitk.org for details.
14 
15 ===================================================================*/
16 
17 #include "mitkExtractSliceFilter.h"
18 
20 #include <mitkPlaneClipping.h>
21 
22 #include <vtkGeneralTransform.h>
23 #include <vtkImageChangeInformation.h>
24 #include <vtkImageData.h>
25 #include <vtkImageExtractComponents.h>
26 #include <vtkLinearTransform.h>
27 
29 {
30  if (reslicer == nullptr)
31  {
33  }
34  else
35  {
36  m_Reslicer = reslicer;
37  }
38 
39  m_TimeStep = 0;
40  m_Reslicer->ReleaseDataFlagOn();
42  m_ResliceTransform = nullptr;
46  m_ZSpacing = 1.0;
47  m_ZMin = 0;
48  m_ZMax = 0;
49  m_VtkOutputRequested = false;
50  m_BackgroundLevel = -32768.0;
51  m_Component = 0;
52 }
53 
55 {
56  m_ResliceTransform = nullptr;
57  m_WorldGeometry = nullptr;
58  delete[] m_OutPutSpacing;
59 }
60 
62 {
63  Superclass::GenerateOutputInformation();
64  // TODO try figure out how to set the specs of the slice before it is actually extracted
65  /*Image::Pointer output = this->GetOutput();
66  Image::ConstPointer input = this->GetInput();
67  if (input.IsNull()) return;
68  unsigned int dimensions[2];
69  dimensions[0] = m_WorldGeometry->GetExtent(0);
70  dimensions[1] = m_WorldGeometry->GetExtent(1);
71  output->Initialize(input->GetPixelType(), 2, dimensions, 1);*/
72 }
73 
75 {
76  // As we want all pixel information fo the image in our plane, the requested region
77  // is set to the largest possible region in the image.
78  // This is needed because an oblique plane has a larger extent then the image
79  // and the in pipeline it is checked via PropagateResquestedRegion(). But the
80  // extent of the slice is actually fitting because it is oblique within the image.
81  ImageToImageFilter::InputImagePointer input = const_cast<ImageToImageFilter::InputImageType *>(this->GetInput());
82  input->SetRequestedRegionToLargestPossibleRegion();
83 }
84 
86 {
87  return m_OutPutSpacing;
88 }
89 
91 {
92  mitk::Image *input = const_cast<mitk::Image *>(this->GetInput());
93 
94  if (!input)
95  {
96  MITK_ERROR << "mitk::ExtractSliceFilter: No input image available. Please set the input!" << std::endl;
97  itkExceptionMacro("mitk::ExtractSliceFilter: No input image available. Please set the input!");
98  return;
99  }
100 
101  if (!m_WorldGeometry)
102  {
103  MITK_ERROR << "mitk::ExtractSliceFilter: No Geometry for reslicing available." << std::endl;
104  itkExceptionMacro("mitk::ExtractSliceFilter: No Geometry for reslicing available.");
105  return;
106  }
107 
108  const TimeGeometry *inputTimeGeometry = this->GetInput()->GetTimeGeometry();
109  if ((inputTimeGeometry == nullptr) || (inputTimeGeometry->CountTimeSteps() <= 0))
110  {
111  itkWarningMacro(<< "Error reading input image TimeGeometry.");
112  return;
113  }
114 
115  // is it a valid timeStep?
116  if (inputTimeGeometry->IsValidTimeStep(m_TimeStep) == false)
117  {
118  itkWarningMacro(<< "This is not a valid timestep: " << m_TimeStep);
119  return;
120  }
121 
122  // check if there is something to display.
123  if (!input->IsVolumeSet(m_TimeStep))
124  {
125  itkWarningMacro(<< "No volume data existent at given timestep " << m_TimeStep);
126  return;
127  }
128 
129  /*================#BEGIN setup vtkImageReslice properties================*/
130  Point3D origin;
131  Vector3D right, bottom, normal;
132  double widthInMM, heightInMM;
133  Vector2D extent;
134 
135  const PlaneGeometry *planeGeometry = dynamic_cast<const PlaneGeometry *>(m_WorldGeometry);
136  // Code for curved planes, mostly taken 1:1 from imageVtkMapper2D and not tested yet.
137  // Do we have an AbstractTransformGeometry?
138  // This is the case for AbstractTransformGeometry's (e.g. a ThinPlateSplineCurvedGeometry )
139  const mitk::AbstractTransformGeometry *abstractGeometry =
140  dynamic_cast<const AbstractTransformGeometry *>(m_WorldGeometry);
141 
142  if (abstractGeometry != nullptr)
143  {
144  m_ResliceTransform = abstractGeometry;
145 
146  extent[0] = abstractGeometry->GetParametricExtent(0);
147  extent[1] = abstractGeometry->GetParametricExtent(1);
148 
149  widthInMM = abstractGeometry->GetParametricExtentInMM(0);
150  heightInMM = abstractGeometry->GetParametricExtentInMM(1);
151 
152  m_OutPutSpacing[0] = widthInMM / extent[0];
153  m_OutPutSpacing[1] = heightInMM / extent[1];
154 
155  origin = abstractGeometry->GetPlane()->GetOrigin();
156 
157  right = abstractGeometry->GetPlane()->GetAxisVector(0);
158  right.Normalize();
159 
160  bottom = abstractGeometry->GetPlane()->GetAxisVector(1);
161  bottom.Normalize();
162 
163  normal = abstractGeometry->GetPlane()->GetNormal();
164  normal.Normalize();
165 
166  // Use a combination of the InputGeometry *and* the possible non-rigid
167  // AbstractTransformGeometry for reslicing the 3D Image
168  vtkSmartPointer<vtkGeneralTransform> composedResliceTransform = vtkSmartPointer<vtkGeneralTransform>::New();
169  composedResliceTransform->Identity();
170  composedResliceTransform->Concatenate(
171  inputTimeGeometry->GetGeometryForTimeStep(m_TimeStep)->GetVtkTransform()->GetLinearInverse());
172  composedResliceTransform->Concatenate(abstractGeometry->GetVtkAbstractTransform());
173 
174  m_Reslicer->SetResliceTransform(composedResliceTransform);
175 
176  // Set background level to BLACK instead of translucent, to avoid
177  // boundary artifacts (see PlaneGeometryDataVtkMapper3D)
178  // Note: Backgroundlevel was hardcoded before to -1023
179  m_Reslicer->SetBackgroundLevel(m_BackgroundLevel);
180  }
181  else
182  {
183  if (planeGeometry != nullptr)
184  {
185  // if the worldGeomatry is a PlaneGeometry everything is straight forward
186 
187  origin = planeGeometry->GetOrigin();
188  right = planeGeometry->GetAxisVector(0);
189  bottom = planeGeometry->GetAxisVector(1);
190  normal = planeGeometry->GetNormal();
191 
192  if (m_InPlaneResampleExtentByGeometry)
193  {
194  // Resampling grid corresponds to the current world geometry. This
195  // means that the spacing of the output 2D image depends on the
196  // currently selected world geometry, and *not* on the image itself.
197  extent[0] = m_WorldGeometry->GetExtent(0);
198  extent[1] = m_WorldGeometry->GetExtent(1);
199  }
200  else
201  {
202  // Resampling grid corresponds to the input geometry. This means that
203  // the spacing of the output 2D image is directly derived from the
204  // associated input image, regardless of the currently selected world
205  // geometry.
206  Vector3D rightInIndex, bottomInIndex;
207  inputTimeGeometry->GetGeometryForTimeStep(m_TimeStep)->WorldToIndex(right, rightInIndex);
208  inputTimeGeometry->GetGeometryForTimeStep(m_TimeStep)->WorldToIndex(bottom, bottomInIndex);
209  extent[0] = rightInIndex.GetNorm();
210  extent[1] = bottomInIndex.GetNorm();
211  }
212 
213  // Get the extent of the current world geometry and calculate resampling
214  // spacing therefrom.
215  widthInMM = m_WorldGeometry->GetExtentInMM(0);
216  heightInMM = m_WorldGeometry->GetExtentInMM(1);
217 
218  m_OutPutSpacing[0] = widthInMM / extent[0];
219  m_OutPutSpacing[1] = heightInMM / extent[1];
220 
221  right.Normalize();
222  bottom.Normalize();
223  normal.Normalize();
224 
225  /*
226  * Transform the origin to center based coordinates.
227  * Note:
228  * This is needed besause vtk's origin is center based too (!!!) ( see 'The VTK book' page 88 )
229  * and the worldGeometry surrouding the image is no imageGeometry. So the worldGeometry
230  * has its origin at the corner of the voxel and needs to be transformed.
231  */
232  origin += right * (m_OutPutSpacing[0] * 0.5);
233  origin += bottom * (m_OutPutSpacing[1] * 0.5);
234 
235  // set the tranform for reslicing.
236  // Use inverse transform of the input geometry for reslicing the 3D image.
237  // This is needed if the image volume already transformed
238  if (m_ResliceTransform.IsNotNull())
239  m_Reslicer->SetResliceTransform(m_ResliceTransform->GetVtkTransform()->GetLinearInverse());
240 
241  // Set background level to TRANSLUCENT (see PlaneGeometryDataVtkMapper3D),
242  // else the background of the image turns out gray
243  // Note: Backgroundlevel was hardcoded to -32768
244  m_Reslicer->SetBackgroundLevel(m_BackgroundLevel);
245  }
246  else
247  {
248  itkExceptionMacro("mitk::ExtractSliceFilter: No fitting geometry for reslice axis!");
249  return;
250  }
251  }
252 
253  if (m_ResliceTransform.IsNotNull())
254  {
255  // if the resliceTransform is set the reslice axis are recalculated.
256  // Thus the geometry information is not fitting. Therefor a unitSpacingFilter
257  // is used to set up a global spacing of 1 and compensate the transform.
258  vtkSmartPointer<vtkImageChangeInformation> unitSpacingImageFilter =
260  unitSpacingImageFilter->ReleaseDataFlagOn();
261 
262  unitSpacingImageFilter->SetOutputSpacing(1.0, 1.0, 1.0);
263  unitSpacingImageFilter->SetInputData(input->GetVtkImageData(m_TimeStep));
264 
265  m_Reslicer->SetInputConnection(unitSpacingImageFilter->GetOutputPort());
266  }
267  else
268  {
269  // if no transform is set the image can be used directly
270  m_Reslicer->SetInputData(input->GetVtkImageData(m_TimeStep));
271  }
272 
273  /*setup the plane where vktImageReslice extracts the slice*/
274 
275  // ResliceAxesOrigin is the anchor point of the plane
276  double originInVtk[3];
277  itk2vtk(origin, originInVtk);
278  m_Reslicer->SetResliceAxesOrigin(originInVtk);
279 
280  // the cosines define the plane: x and y are the direction vectors, n is the planes normal
281  // this specifies a matrix 3x3
282  // x1 y1 n1
283  // x2 y2 n2
284  // x3 y3 n3
285  double cosines[9];
286 
287  vnl2vtk(right.GetVnlVector(), cosines); // x
288 
289  vnl2vtk(bottom.GetVnlVector(), cosines + 3); // y
290 
291  vnl2vtk(normal.GetVnlVector(), cosines + 6); // n
292 
293  m_Reslicer->SetResliceAxesDirectionCosines(cosines);
294 
295  // we only have one slice, not a volume
296  m_Reslicer->SetOutputDimensionality(m_OutputDimension);
297 
298  // set the interpolation mode for slicing
299  switch (this->m_InterpolationMode)
300  {
301  case RESLICE_NEAREST:
302  m_Reslicer->SetInterpolationModeToNearestNeighbor();
303  break;
304  case RESLICE_LINEAR:
305  m_Reslicer->SetInterpolationModeToLinear();
306  break;
307  case RESLICE_CUBIC:
308  m_Reslicer->SetInterpolationModeToCubic();
309  break;
310  default:
311  // the default interpolation used by mitk
312  m_Reslicer->SetInterpolationModeToNearestNeighbor();
313  }
314 
315  /*========== BEGIN setup extent of the slice ==========*/
316  int xMin, xMax, yMin, yMax;
317 
318  xMin = yMin = 0;
319  xMax = static_cast<int>(extent[0]);
320  yMax = static_cast<int>(extent[1]);
321 
322  if (m_WorldGeometry->GetReferenceGeometry())
323  {
324  double sliceBounds[6];
325  for (auto &sliceBound : sliceBounds)
326  {
327  sliceBound = 0.0;
328  }
329 
330  if (this->GetClippedPlaneBounds(m_WorldGeometry->GetReferenceGeometry(), planeGeometry, sliceBounds))
331  {
332  // Calculate output extent (integer values)
333  xMin = static_cast<int>(sliceBounds[0] / m_OutPutSpacing[0] + 0.5);
334  xMax = static_cast<int>(sliceBounds[1] / m_OutPutSpacing[0] + 0.5);
335  yMin = static_cast<int>(sliceBounds[2] / m_OutPutSpacing[1] + 0.5);
336  yMax = static_cast<int>(sliceBounds[3] / m_OutPutSpacing[1] + 0.5);
337  } // ELSE we use the default values
338  }
339 
340  // Set the output extents! First included pixel index and last included pixel index
341  // xMax and yMax are one after the last pixel. so they have to be decremented by 1.
342  // In case we have a 2D image, xMax or yMax might be 0. in this case, do not decrement, but take 0.
343 
344  m_Reslicer->SetOutputExtent(xMin, std::max(0, xMax - 1), yMin, std::max(0, yMax - 1), m_ZMin, m_ZMax);
345  /*========== END setup extent of the slice ==========*/
346 
347  m_Reslicer->SetOutputOrigin(0.0, 0.0, 0.0);
348 
349  m_Reslicer->SetOutputSpacing(m_OutPutSpacing[0], m_OutPutSpacing[1], m_ZSpacing);
350 
351  // TODO check the following lines, they are responsible whether vtk error outputs appear or not
352  m_Reslicer->UpdateWholeExtent(); // this produces a bad allocation error for 2D images
353  // m_Reslicer->GetOutput()->UpdateInformation();
354  // m_Reslicer->GetOutput()->SetUpdateExtentToWholeExtent();
355 
356  // start the pipeline
357  m_Reslicer->Update();
358  /*================ #END setup vtkImageReslice properties================*/
359 
360  if (m_VtkOutputRequested)
361  {
362  // no conversion to mitk
363  // no mitk geometry will be set, as the output is vtkImageData only!!!
364  // no image component will be extracted, as the caller might need the whole multi-component image as vtk output
365  return;
366  }
367  else
368  {
369  auto reslicedImage = vtkSmartPointer<vtkImageData>::New();
370  reslicedImage = m_Reslicer->GetOutput();
371 
372  if (nullptr == reslicedImage)
373  {
374  itkWarningMacro(<< "Reslicer returned empty image");
375  return;
376  }
377 
378  /*================ #BEGIN Extract component from image slice ================*/
379  int numberOfScalarComponent = reslicedImage->GetNumberOfScalarComponents();
380  if (numberOfScalarComponent > 1 && static_cast<unsigned int>(numberOfScalarComponent) >= m_Component)
381  {
382  // image has more than one component, extract the correct component information with the given 'component' parameter
383  auto vectorComponentExtractor = vtkSmartPointer<vtkImageExtractComponents>::New();
384  vectorComponentExtractor->SetInputData(reslicedImage);
385  vectorComponentExtractor->SetComponents(m_Component);
386  vectorComponentExtractor->Update();
387 
388  reslicedImage = vectorComponentExtractor->GetOutput();
389  }
390  /*================ #END Extract component from image slice ================*/
391 
392  /*================ #BEGIN Convert the slice to an mitk::Image ================*/
393  mitk::Image::Pointer resultImage = GetOutput();
394 
395  // initialize resultimage with the specs of the vtkImageData object returned from vtkImageReslice
396  if (reslicedImage->GetDataDimension() == 1)
397  {
398  // If original image was 2D, the slice might have an y extent of 0.
399  // Still i want to ensure here that Image is 2D
400  resultImage->Initialize(reslicedImage, 1, -1, -1, 1);
401  }
402  else
403  {
404  resultImage->Initialize(reslicedImage);
405  }
406 
407  // transfer the voxel data
408  resultImage->SetVolume(reslicedImage->GetScalarPointer());
409 
410  // set the geometry from current worldgeometry for the reusultimage
411  // this is needed that the image has the correct mitk geometry
412  // the originalGeometry is the Geometry of the result slice
413 
414  // mitk::AffineGeometryFrame3D::Pointer originalGeometryAGF = m_WorldGeometry->Clone();
415  // PlaneGeometry::Pointer originalGeometry = dynamic_cast<PlaneGeometry*>( originalGeometryAGF.GetPointer() );
416  PlaneGeometry::Pointer originalGeometry = m_WorldGeometry->Clone();
417 
418  originalGeometry->GetIndexToWorldTransform()->SetMatrix(m_WorldGeometry->GetIndexToWorldTransform()->GetMatrix());
419 
420  // the origin of the worldGeometry is transformed to center based coordinates to be an imageGeometry
421  Point3D sliceOrigin = originalGeometry->GetOrigin();
422 
423  sliceOrigin += right * (m_OutPutSpacing[0] * 0.5);
424  sliceOrigin += bottom * (m_OutPutSpacing[1] * 0.5);
425 
426  // a worldGeometry is no imageGeometry, thus it is manually set to true
427  originalGeometry->ImageGeometryOn();
428 
429  /*At this point we have to adjust the geometry because the origin isn't correct.
430  The wrong origin is related to the rotation of the current world geometry plane.
431  This causes errors on transferring world to index coordinates. We just shift the
432  origin in each direction about the amount of the expanding (needed while rotating
433  the plane).
434  */
435  Vector3D axis0 = originalGeometry->GetAxisVector(0);
436  Vector3D axis1 = originalGeometry->GetAxisVector(1);
437  axis0.Normalize();
438  axis1.Normalize();
439 
440  // adapt the origin. Note that for orthogonal planes the minima are '0' and thus the origin stays the same.
441  sliceOrigin += (axis0 * (xMin * m_OutPutSpacing[0])) + (axis1 * (yMin * m_OutPutSpacing[1]));
442 
443  originalGeometry->SetOrigin(sliceOrigin);
444 
445  originalGeometry->Modified();
446 
447  resultImage->SetGeometry(originalGeometry);
448 
449  /*the bounds as well as the extent of the worldGeometry are not adapted correctly during crosshair rotation.
450  This is only a quick fix and has to be evaluated.
451  The new bounds are set via the max values of the calculated slice extent. It will look like [ 0, x, 0, y, 0, 1].
452  */
454  boundsCopy[0] = boundsCopy[2] = boundsCopy[4] = 0;
455  boundsCopy[5] = 1;
456  boundsCopy[1] = xMax - xMin;
457  boundsCopy[3] = yMax - yMin;
458  resultImage->GetGeometry()->SetBounds(boundsCopy);
459 
460  /*================ #END Convert the slice to an mitk::Image ================*/
461  }
462 }
463 
465 {
466  if (!m_WorldGeometry || !this->GetInput())
467  return false;
468 
469  return this->GetClippedPlaneBounds(
470  m_WorldGeometry->GetReferenceGeometry(), dynamic_cast<const PlaneGeometry *>(m_WorldGeometry), bounds);
471 }
472 
474  const PlaneGeometry *planeGeometry,
475  double *bounds)
476 {
477  bool b = mitk::PlaneClipping::CalculateClippedPlaneBounds(boundingGeometry, planeGeometry, bounds);
478 
479  return b;
480 }
const Point3D GetOrigin() const
Get the origin, e.g. the upper-left corner of the plane.
ResliceInterpolation m_InterpolationMode
#define MITK_ERROR
Definition: mitkLogMacros.h:24
double ScalarType
static bool CalculateClippedPlaneBounds(const BaseGeometry *boundingGeometry, const PlaneGeometry *planeGeometry, double *bounds)
Calculate the bounding box of the resliced image. This is necessary for arbitrarily rotated planes in...
virtual bool IsVolumeSet(int t=0, int n=0) const override
Check whether volume at time t in channel n is set.
Definition: mitkImage.cpp:606
virtual vtkImageData * GetVtkImageData(int t=0, int n=0)
Get a volume at a specific time t of channel n as a vtkImageData.
Definition: mitkImage.cpp:221
Vector3D GetNormal() const
Normal of the plane.
virtual const PlaneGeometry * GetPlane()
Get the rectangular area that is used for transformation by m_VtkAbstractTransform and therewith defi...
Vector3D GetAxisVector(unsigned int direction) const
Get vector along bounding-box in the specified direction in mm.
mitk::ScalarType * GetOutputSpacing()
Get the spacing of the slice. returns mitk::ScalarType[2].
virtual void GenerateOutputInformation() override
virtual void GenerateData() override
A version of GenerateData() specific for image processing filters.
void vnl2vtk(const vnl_vector< Tin > &in, Tout *out)
ExtractSliceFilter(vtkImageReslice *reslicer=nullptr)
Image class for storing images.
Definition: mitkImage.h:76
Describes a geometry defined by an vtkAbstractTransform and a plane.
BaseGeometry::ConstPointer m_ResliceTransform
static T max(T x, T y)
Definition: svm.cpp:70
void itk2vtk(const Tin &in, Tout &out)
mitk::ScalarType * m_OutPutSpacing
mitk::ScalarType GetParametricExtent(int direction) const
Get the parametric extent.
virtual BaseGeometry::Pointer GetGeometryForTimeStep(TimeStepType timeStep) const =0
Returns the geometry which corresponds to the given time step.
bool GetClippedPlaneBounds(double bounds[6])
Get the bounding box of the slice [xMin, xMax, yMin, yMax, zMin, zMax] The method uses the input of t...
virtual mitk::ScalarType GetParametricExtentInMM(int direction) const
virtual vtkAbstractTransform * GetVtkAbstractTransform() const
Get the vtkAbstractTransform (stored in m_VtkAbstractTransform)
Describes a two-dimensional, rectangular plane.
virtual void GenerateInputRequestedRegion() override
vtkSmartPointer< vtkImageReslice > m_Reslicer
virtual bool IsValidTimeStep(TimeStepType timeStep) const =0
Test for the given time step if a geometry is availible.
BaseGeometry Describes the geometry of a data object.
BoundingBoxType::BoundsArrayType BoundsArrayType
static itkEventMacro(BoundingShapeInteractionEvent, itk::AnyEvent) class MITKBOUNDINGSHAPE_EXPORT BoundingShapeInteractor Pointer New()
Basic interaction methods for mitk::GeometryData.