commit ec1796cc9e872b004bb9684bd2b0020dfcebc4c8 Author: Alexander Rossmanith Date: Fri Aug 7 20:26:59 2026 +0530 initial commit diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..500a014 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 2.8) + +project(FollicleSeparator) + +find_package(ITK REQUIRED) +include(${ITK_USE_FILE}) + +file(GLOB_RECURSE HEADERS "src/*.h") +file(GLOB_RECURSE INLINES "src/*.inl") +file(GLOB_RECURSE SOURCES "src/*.cpp") + +add_executable(FollicleSeparator ${HEADERS} ${INLINES} ${SOURCES}) + +target_link_libraries(FollicleSeparator ${ITK_LIBRARIES}) diff --git a/src/FollicleSeparator.cpp b/src/FollicleSeparator.cpp new file mode 100644 index 0000000..81ab246 --- /dev/null +++ b/src/FollicleSeparator.cpp @@ -0,0 +1,157 @@ +#pragma warning(push) +#pragma warning(disable:4996) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#pragma warning(pop) + +#include +using namespace std; + +/// +/// Binary object splitting using +/// watershed segmentation applied to the distance transform +/// +int main(int argc, char* argv[]) +{ + try + { + const int DIM = 3; + typedef itk::Image UInt8Volume; // Binary Mask + typedef itk::Image Int16Volume; // Input Volume + typedef itk::Image UInt16Volume; // Label Volume + typedef itk::Image Float32Volume; // Distance + + // Read labeled volume + + itk::ImageFileReader::Pointer pReader = itk::ImageFileReader::New(); + { + itk::TimeProbe clock; + clock.Start(); + + cout << "Read input from labeledVolume.vtk" << endl; + pReader->SetFileName("labeledVolume.vtk"); + pReader->Update(); + + clock.Stop(); + cout << "ImageFileReader: " << clock.GetTotal() << "s" << endl; + } + + // Binarize input volume: Follicles = 0, Background = 255 + + itk::BinaryThresholdImageFilter::Pointer pBinarizationFilter = + itk::BinaryThresholdImageFilter::New(); + { + itk::TimeProbe clock; + clock.Start(); + + pBinarizationFilter->SetInput(pReader->GetOutput()); + pBinarizationFilter->SetLowerThreshold(0); + pBinarizationFilter->SetUpperThreshold(0); + pBinarizationFilter->SetInsideValue(255); + pBinarizationFilter->SetOutsideValue(0); + pBinarizationFilter->Update(); + + clock.Stop(); + cout << "BinaryThresholdImageFilter: " << clock.GetTotal() << "s" << endl; + } + + // Compute signed distance from background pixels + // Inside the follicle: positive + // Outside of the follicle: negative + + itk::SignedMaurerDistanceMapImageFilter::Pointer pSignedMaurer = + itk::SignedMaurerDistanceMapImageFilter::New(); + { + itk::TimeProbe clock; + clock.Start(); + + pSignedMaurer->SetInput(pBinarizationFilter->GetOutput()); + + pSignedMaurer->Update(); + + clock.Stop(); + cout << "SignedMaurerDistanceMapImageFilter: " << clock.GetTotal() << "s" << endl; + } + + // Invert distance map: [0 ... max distance] -> [255 ... 0] + // background pixels will have intensity 255 + // pixels inside the follicle far away from the border will have low intensities + itk::IntensityWindowingImageFilter::Pointer pConvertToUInt8 = + itk::IntensityWindowingImageFilter::New(); + { + itk::TimeProbe clock; + clock.Start(); + + itk::MinimumMaximumImageCalculator::Pointer pMinMaxDistance = + itk::MinimumMaximumImageCalculator::New(); + pMinMaxDistance->SetImage(pSignedMaurer->GetOutput()); + pMinMaxDistance->ComputeMaximum(); + float maxDistance = pMinMaxDistance->GetMaximum(); + + pConvertToUInt8->SetInput(pSignedMaurer->GetOutput()); + pConvertToUInt8->SetWindowMinimum(0.0); + pConvertToUInt8->SetWindowMaximum(maxDistance); + pConvertToUInt8->SetOutputMinimum(255); + pConvertToUInt8->SetOutputMaximum(0); + pConvertToUInt8->Update(); + + clock.Stop(); + cout << "ConvertToUInt8: " << clock.GetTotal() << "s" << endl; + + itk::ImageFileWriter::Pointer pWriter = itk::ImageFileWriter::New(); + pWriter->SetInput(pConvertToUInt8->GetOutput()); + pWriter->SetFileName("labeledVolume.distanceMap.vtk"); + pWriter->Update(); + } + + // Watershed transformation of the distance map + itk::MorphologicalWatershedImageFilter::Pointer pWatershed = itk::MorphologicalWatershedImageFilter::New(); + { + itk::TimeProbe clock; + clock.Start(); + + pWatershed->SetLevel(25); // high level -> low Number of regions + pWatershed->SetMarkWatershedLine(false); + pWatershed->SetInput(pConvertToUInt8->GetOutput()); + pWatershed->Update(); + + clock.Stop(); + cout << "WatershedImageFilter: " << clock.GetTotal() << "s" << endl; + } + + // Mask out background voxels from the watershed result + itk::MaskNegatedImageFilter::Pointer pMaskImageFilter = + itk::MaskNegatedImageFilter::New(); + { + pMaskImageFilter->SetInput(pWatershed->GetOutput()); + pMaskImageFilter->SetMaskImage(pBinarizationFilter->GetOutput()); + pMaskImageFilter->Update(); + } + + itk::MinimumMaximumImageCalculator::Pointer pMaxLabelCalculator = + itk::MinimumMaximumImageCalculator::New(); + pMaxLabelCalculator->SetImage(pMaskImageFilter->GetOutput()); + pMaxLabelCalculator->ComputeMaximum(); + cout << "Number of labels: " << pMaxLabelCalculator->GetMaximum() << endl; + + cout << "Write result to labeledVolume.watershed.vtk" << endl; + itk::ImageFileWriter::Pointer pWriter = itk::ImageFileWriter::New(); + pWriter->SetInput(pMaskImageFilter->GetOutput()); + pWriter->SetFileName("labeledVolume.watershed.vtk"); + pWriter->Update(); + } + catch (const itk::ExceptionObject& ex) + { + std::cerr << ex.GetDescription() << std::endl; + return 1; + } + + return 0; +}