Apply Morphological Closing on Specific Label Object#

Synopsis#

Apply BinaryMorphologicalClosingFilter on one LabelObject of given LabelMap.

In details:

  • read one Image

  • convert the LabelImage into one LabelMap

  • extract the LabelObject of interest using LabelSelectionLabelMapFilter

  • apply morphological closing operation on the LabelObject of interest

  • merge the processed LabelObject with remaining LabelObjects from LabelSelectionLabelMapFilter

  • make sure there is no overlapping LabelObject with using LabelUniqueLabelMapFilter

  • convert the LabelMap to LabelImage

  • write the corresponding LabelImage

Results#

Input image

Input image#

Output image

Output image#

Code#

C++#

#include "itkImageFileReader.h"
#include "itkImageFileWriter.h"
#include "itkLabelImageToLabelMapFilter.h"
#include "itkLabelMapToLabelImageFilter.h"
#include "itkLabelSelectionLabelMapFilter.h"
#include "itkMergeLabelMapFilter.h"
#include "itkObjectByObjectLabelMapFilter.h"
#include "itkLabelUniqueLabelMapFilter.h"
#include "itkFlatStructuringElement.h"
#include "itkBinaryMorphologicalClosingImageFilter.h"

int
main(int argc, char * argv[])
{
  if (argc != 5)
  {
    std::cerr << "Usage: " << std::endl;
    std::cerr << argv[0];
    std::cerr << " <InputFileName> <OutputFileName> <label> <radius>";
    std::cerr << std::endl;
    return EXIT_FAILURE;
  }

  constexpr unsigned int Dimension = 2;

  using PixelType = unsigned char;
  using ImageType = itk::Image<PixelType, Dimension>;

  const char *       inputFileName = argv[1];
  const char *       outputFileName = argv[2];
  const auto         label = static_cast<PixelType>(std::stoi(argv[3]));
  const unsigned int radiusValue = std::stoi(argv[4]);

  const auto input = itk::ReadImage<ImageType>(inputFileName);

  using LabelObjectType = itk::LabelObject<PixelType, Dimension>;
  using LabelMapType = itk::LabelMap<LabelObjectType>;

  using LabelImageToLabelMapFilterType = itk::LabelImageToLabelMapFilter<ImageType, LabelMapType>;
  auto labelMapConverter = LabelImageToLabelMapFilterType::New();
  labelMapConverter->SetInput(input);
  labelMapConverter->SetBackgroundValue(itk::NumericTraits<PixelType>::Zero);

  using SelectorType = itk::LabelSelectionLabelMapFilter<LabelMapType>;
  auto selector = SelectorType::New();
  selector->SetInput(labelMapConverter->GetOutput());
  selector->SetLabel(label);

  using StructuringElementType = itk::FlatStructuringElement<Dimension>;
  StructuringElementType::RadiusType radius;
  radius.Fill(radiusValue);

  StructuringElementType structuringElement = StructuringElementType::Ball(radius);

  using MorphologicalFilterType =
    itk::BinaryMorphologicalClosingImageFilter<ImageType, ImageType, StructuringElementType>;
  auto closingFilter = MorphologicalFilterType::New();

  using ObjectByObjectLabelMapFilterType = itk::ObjectByObjectLabelMapFilter<LabelMapType>;
  auto objectByObjectLabelMapFilter = ObjectByObjectLabelMapFilterType::New();
  objectByObjectLabelMapFilter->SetInput(selector->GetOutput(0));
  objectByObjectLabelMapFilter->SetBinaryInternalOutput(true);
  objectByObjectLabelMapFilter->SetFilter(closingFilter);

  using MergeLabelFilterType = itk::MergeLabelMapFilter<LabelMapType>;
  auto merger = MergeLabelFilterType::New();
  merger->SetInput(0, objectByObjectLabelMapFilter->GetOutput(0));
  merger->SetInput(1, selector->GetOutput(1));
  merger->SetMethod(itk::MergeLabelMapFilterEnums::ChoiceMethod::KEEP);

  using UniqueLabelMapFilterType = itk::LabelUniqueLabelMapFilter<LabelMapType>;
  auto unique = UniqueLabelMapFilterType::New();
  unique->SetInput(merger->GetOutput());

  using LabelMapToLabelImageFilterType = itk::LabelMapToLabelImageFilter<LabelMapType, ImageType>;
  auto labelImageConverter = LabelMapToLabelImageFilterType::New();
  labelImageConverter->SetInput(unique->GetOutput());

  try
  {
    itk::WriteImage(labelImageConverter->GetOutput(), outputFileName);
  }
  catch (const itk::ExceptionObject & error)
  {
    std::cerr << "Error: " << error << std::endl;
    return EXIT_FAILURE;
  }

  return EXIT_SUCCESS;
}

Classes demonstrated#

template<typename TInputImage, typename TOutputImage = TInputImage, typename TInputFilter = ImageToImageFilter<Image<unsigned char, TInputImage::ImageDimension>, Image<unsigned char, TOutputImage::ImageDimension>>, class TOutputFilter = typename TInputFilter::Superclass, class TInternalInputImage = typename TInputFilter::InputImageType, class TInternalOutputImage = typename TOutputFilter::OutputImageType>
class ObjectByObjectLabelMapFilter : public itk::LabelMapFilter<TInputImage, TOutputImage>

ObjectByObjectLabelMapFilter applies an image pipeline to all the objects of a label map and produce a new label map.

The image pipeline can simply produce a modified object or produce several objects from the single input object. Several options are provided to handle the different cases.

KeepLabel, which defaults to true, makes the filter try to keep as much as possible the labels of the original objects. If an image pipeline produce several objects the label of the input object is assigned to the first output object. The other output objects get another label not present in the input image. When KeepLabel is set to false, all the objects are relabeled in the order of apparition during the filter process.

BinaryInternalOutput can be set to true if the image pipeline produce binary output image. In that case, the objects produced are identified with a connected component algorithm before being reinserted in the output label map. InternalForegroundValue can be set to a specific value which represent the foreground value in the binary image.

PadSize and ConstrainPaddingToImage can be used to extend the size of the image to process passed to the image pipeline. This is useful if the image pipeline is known to be able to enlarge the object. The padding can be constrained to the input label map region by setting ConstrainPaddingToImage to true - this parameter can make a difference for the algorithm with a different behavior on the border of the image. By default, the image is padded by 1 pixel and constrained to the image region.

This implementation was taken from the Insight Journal paper:

https://www.insight-journal.org/browse/publication/176
Note

: When applying a single filter, input and output filters are the same; while applying a pipeline, input and output filters are different, may not even be of the same type. It is the responsibility of the user to connect the pipeline properly outside of this filter.

Author

Gaetan Lehmann. Biologie du Developpement et de la Reproduction, INRA de Jouy-en-Josas, France.

ITK Sphinx Examples:

See itk::ObjectByObjectLabelMapFilter for additional documentation.