Managing memory in VTKImageToImageFilter

I am sharing my recent experience with VTKImageToImageFilter.

I have a function poly_data_to_binary_image that creates an image from an input vtkPolyData, and using an input ITK image as a reference for dimensions and metadata (origin, spacing, direction).

At the beginning I was creating a vtkSmartPointer<vtkImageData> as an input to VTKImageToImageFilter , but because the vtkImageData was going out of scope, its pointer was deleted, and I ended with a dangling pointer in m_Buffer of the output itk image.

The solution was to instead create a raw pointer vtkImageData * and let m_Buffer of the output ITK image (a itk::ImageContainer) own the pointer, managing its deletion when needed.

Code below, hope somebody (or myself in the future) finds it useful.

BinaryImageType::Pointer poly_data_to_binary_image(
    vtkPolyData *poly_data, const BinaryImageType::Pointer &reference_image) {
  const auto &ref_size = reference_image->GetLargestPossibleRegion().GetSize();
  const auto &ref_origin = reference_image->GetOrigin();
  const auto &ref_spacing = reference_image->GetSpacing();
  const auto &ref_direction = reference_image->GetDirection();

  // Create a raw pointer, its memory will be owned (deleted when needed)
  // by itkImageContainer of the output_itk_image.
  vtkImageData *output_vtk_image = vtkImageData::New();
  output_vtk_image->SetDimensions(ref_size[0], ref_size[1], ref_size[2]);
  output_vtk_image->SetOrigin(ref_origin[0], ref_origin[1], ref_origin[2]);
  output_vtk_image->SetSpacing(ref_spacing[0], ref_spacing[1], ref_spacing[2]);
  output_vtk_image->AllocateScalars(VTK_UNSIGNED_CHAR, 1);

  // vtk pipeline
  vtkSmartPointer<vtkPolyDataToImageStencil> data_to_stencil =
      vtkSmartPointer<vtkPolyDataToImageStencil>::New();
  data_to_stencil->SetInputData(poly_data);
  data_to_stencil->SetInformationInput(output_vtk_image);
  data_to_stencil->Update();

  vtkSmartPointer<vtkImageStencil> stencil_to_vtk_image =
          vtkSmartPointer<vtkImageStencil>::New();
  stencil_to_vtk_image->SetInputData(output_vtk_image);
  stencil_to_vtk_image->SetStencilConnection(
          data_to_stencil->GetOutputPort());
  stencil_to_vtk_image->ReverseStencilOn(); // foreground will be 255
  stencil_to_vtk_image->SetBackgroundValue(255);
  stencil_to_vtk_image->Update();
  // DeepCopy to avoid dangling pointers outside this scope.
  output_vtk_image->DeepCopy(stencil_to_vtk_image->GetOutput());

  itk::VTKImageToImageFilter<BinaryImageType>::Pointer vtk_to_itk_filter =
      itk::VTKImageToImageFilter<BinaryImageType>::New();
  vtk_to_itk_filter->SetInput(output_vtk_image);
  vtk_to_itk_filter->Update();
  BinaryImageType::Pointer output_itk_image = vtk_to_itk_filter->GetOutput();
  output_itk_image->SetDirection(reference_image->GetDirection());
  // We let the itk container to own (manage the deletion) of the vtkImageData
  // managed by a raw pointer. If ImageData is managed by a vtkSmartPointer
  // we will have a dangling pointer outside of this function.
  output_itk_image->GetPixelContainer()->ContainerManageMemoryOn();
  return output_itk_image;
}
2 Likes