#include "SkullRemovalService.h" #include "connectITKVTK.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { // VTK CT data in this codebase is typically signed 16-bit (short). // Import using the exact scalar type to avoid itk::VTKImageImport runtime errors, // then cast to float for the processing steps that benefit from it. using InputPixelType = short; using InternalPixelType = float; constexpr unsigned int Dimension = 3; using InputImageType = itk::Image; using InternalImageType = itk::Image; using MaskPixelType = unsigned char; using MaskImageType = itk::Image; using ImportFilterType = itk::VTKImageImport; using CastToFloatFilterType = itk::CastImageFilter; using CastBackToInputFilterType = itk::CastImageFilter; using CurvatureFlowImageFilterType = itk::CurvatureFlowImageFilter; using ConnectedThresholdImageFilterType = itk::ConnectedThresholdImageFilter; using StructuringElementType = itk::FlatStructuringElement; using DilateFilterType = itk::BinaryDilateImageFilter; using ErodeFilterType = itk::BinaryErodeImageFilter; using MaskNegatedFilterType = itk::MaskNegatedImageFilter; } // namespace vtkImageData* SkullRemovalService::run(vtkImageData* inputVolume, double thr_low, double thr_up, std::atomic_bool* abortFlag, std::function progressCb) { if (!inputVolume) throw std::runtime_error("SkullRemovalService::run: inputVolume is null"); auto isAborted = [&]() -> bool { return abortFlag && abortFlag->load(); }; if (progressCb) progressCb("Skull masking: preparing pipeline", 0.0); if (isAborted()) return nullptr; // VTK -> ITK vtkSmartPointer vtkExporter = vtkSmartPointer::New(); vtkExporter->SetInputData(inputVolume); auto itkImporter = ImportFilterType::New(); ConnectVTKToITK(vtkExporter.GetPointer(), itkImporter); // Pipeline (mirrors old gSkullRemoval::runFilter in a pure form) auto castToFloat = CastToFloatFilterType::New(); auto smoothing = CurvatureFlowImageFilterType::New(); auto connectedThreshold = ConnectedThresholdImageFilterType::New(); auto binaryDilateFilter = DilateFilterType::New(); auto binaryErodeFilter = ErodeFilterType::New(); auto maskNegatedFilter = MaskNegatedFilterType::New(); smoothing->SetNumberOfIterations(10); smoothing->SetTimeStep(0.125); castToFloat->SetInput(itkImporter->GetOutput()); smoothing->SetInput(castToFloat->GetOutput()); connectedThreshold->SetInput(smoothing->GetOutput()); connectedThreshold->SetLower(static_cast(thr_low)); connectedThreshold->SetUpper(static_cast(thr_up)); connectedThreshold->SetReplaceValue(255); // Seed at volume center // connectedThreshold->UpdateOutputInformation(); // Make sure upstream information is propagated connectedThreshold->UpdateOutputInformation(); // Use the image that DEFINITELY has region info propagated auto in = smoothing->GetOutput(); // or connectedThreshold->GetInput() const auto region = in->GetLargestPossibleRegion(); const auto size = region.GetSize(); const auto start = region.GetIndex(); InternalImageType::IndexType seed; seed[0] = start[0] + static_cast(size[0] / 2); seed[1] = start[1] + static_cast(size[1] / 2); seed[2] = start[2] + static_cast(size[2] / 2); connectedThreshold->SetSeed(seed); // InternalImageType::Pointer in = castToFloat->GetOutput(); // const auto region = in->GetBufferedRegion(); // const auto size = region.GetSize(); // const auto start = region.GetIndex(); // InternalImageType::IndexType seed; // seed[0] = start[0] + static_cast(size[0] / 2); // seed[1] = start[1] + static_cast(size[1] / 2); // seed[2] = start[2] + static_cast(size[2] / 2); // connectedThreshold->SetSeed(seed); // ITK 5.x expects a RadiusType (itk::Size) for FlatStructuringElement::Ball StructuringElementType::RadiusType radius; radius.Fill(10); const auto kernel = StructuringElementType::Ball(radius); binaryDilateFilter->SetKernel(kernel); binaryErodeFilter->SetKernel(kernel); binaryDilateFilter->SetDilateValue(255); binaryErodeFilter->SetErodeValue(255); binaryDilateFilter->SetInput(connectedThreshold->GetOutput()); binaryErodeFilter->SetInput(binaryDilateFilter->GetOutput()); maskNegatedFilter->SetInput1(castToFloat->GetOutput()); maskNegatedFilter->SetInput2(binaryErodeFilter->GetOutput()); if (progressCb) progressCb("Skull masking: running", 0.5); if (isAborted()) return nullptr; try { maskNegatedFilter->Update(); } catch (const itk::ExceptionObject& ex) { if (isAborted()) return nullptr; throw std::runtime_error(std::string("SkullRemovalService: ITK exception: ") + ex.GetDescription()); } if (isAborted()) return nullptr; // ITK -> VTK (cast back to the original scalar type expected by the rest of the app) auto castBackToInput = CastBackToInputFilterType::New(); castBackToInput->SetInput(maskNegatedFilter->GetOutput()); vtkSmartPointer vtkImporter = vtkSmartPointer::New(); auto itkExporter = itk::VTKImageExport::New(); itkExporter->SetInput(castBackToInput->GetOutput()); ConnectITKToVTK(itkExporter, vtkImporter.GetPointer()); vtkImporter->Update(); vtkImageData* out = vtkImageData::New(); out->DeepCopy(vtkImporter->GetOutput()); if (progressCb) progressCb("Skull masking: done", 1.0); return out; }