diff --git a/include/rtkFDKConeBeamReconstructionFilter.h b/include/rtkFDKConeBeamReconstructionFilter.h index 85bc1104f..7d2bfd5ad 100644 --- a/include/rtkFDKConeBeamReconstructionFilter.h +++ b/include/rtkFDKConeBeamReconstructionFilter.h @@ -23,8 +23,7 @@ #include "rtkConfiguration.h" #include "rtkFDKBackProjectionImageFilter.h" #include "rtkFFTRampImageFilter.h" - -#include +#include "rtkSubRegionViewImageFilter.h" namespace rtk { @@ -38,8 +37,8 @@ namespace rtk * - rtk::FFTRampImageFilter for ramp filtering, * - rtk::FDKBackProjectionImageFilter for backprojection. * The input stack of projections is processed piece by piece (the size is - * controlled with ProjectionSubsetSize) via the use of itk::ExtractImageFilter - * to extract sub-stacks. + * controlled with ProjectionSubsetSize) by extracting sub-stacks directly + * from the input buffer pointer (zero-copy). * * \dot * digraph FDKConeBeamReconstructionFilter { @@ -76,7 +75,7 @@ class ITK_TEMPLATE_EXPORT FDKConeBeamReconstructionFilter : public itk::InPlaceI using OutputImageType = TOutputImage; /** Typedefs of each subfilter of this composite filter */ - using ExtractFilterType = itk::ExtractImageFilter; + using ExtractFilterType = rtk::SubRegionViewImageFilter; using WeightFilterType = rtk::FDKWeightProjectionFilter; using RampFilterType = rtk::FFTRampImageFilter; using BackProjectionFilterType = rtk::FDKBackProjectionImageFilter; diff --git a/include/rtkFDKConeBeamReconstructionFilter.hxx b/include/rtkFDKConeBeamReconstructionFilter.hxx index aac8d6d25..fd4989b4a 100644 --- a/include/rtkFDKConeBeamReconstructionFilter.hxx +++ b/include/rtkFDKConeBeamReconstructionFilter.hxx @@ -41,7 +41,6 @@ FDKConeBeamReconstructionFilter::FDKCo m_RampFilter->SetInput(m_WeightFilter->GetOutput()); // Default parameters - m_ExtractFilter->SetDirectionCollapseToSubmatrix(); m_WeightFilter->InPlaceOn(); // Default to one projection per subset when FFTW is not available @@ -92,16 +91,19 @@ FDKConeBeamReconstructionFilter::Gener // We only set the first sub-stack at that point, the rest will be // requested in the GenerateData function - typename ExtractFilterType::InputImageRegionType projRegion; + typename InputImageType::RegionType projRegion; projRegion = this->GetInput(1)->GetLargestPossibleRegion(); unsigned int firstStackSize = std::min(m_ProjectionSubsetSize, (unsigned int)projRegion.GetSize(Dimension - 1)); projRegion.SetSize(Dimension - 1, firstStackSize); + + m_ExtractFilter->SetInput(this->GetInput(1)); m_ExtractFilter->SetExtractionRegion(projRegion); + if (rtk::IsContiguousSubRegion(this->GetInput(1), projRegion)) + m_WeightFilter->InPlaceOff(); // Run composite filter update m_BackProjectionFilter->SetInput(0, this->GetInput(0)); m_BackProjectionFilter->SetInPlace(this->GetInPlace()); - m_ExtractFilter->SetInput(this->GetInput(1)); m_BackProjectionFilter->UpdateOutputInformation(); // Update output information @@ -117,13 +119,11 @@ FDKConeBeamReconstructionFilter::Gener { const unsigned int Dimension = this->InputImageDimension; - // The backprojection works on a small stack of projections, not the full stack - typename ExtractFilterType::InputImageRegionType subsetRegion; + typename InputImageType::RegionType subsetRegion; subsetRegion = this->GetInput(1)->GetLargestPossibleRegion(); unsigned int nProj = subsetRegion.GetSize(Dimension - 1); + unsigned int baseIndex = subsetRegion.GetIndex(Dimension - 1); - // The progress accumulator tracks the progress of the pipeline - // Each filter is equally weighted across all iterations of the stack auto progress = itk::ProgressAccumulator::New(); progress->SetMiniPipelineFilter(this); auto frac = (1.0f / 3) / itk::Math::ceil(double(nProj) / m_ProjectionSubsetSize); @@ -133,22 +133,23 @@ FDKConeBeamReconstructionFilter::Gener for (unsigned int i = 0; i < nProj; i += m_ProjectionSubsetSize) { - // After the first bp update, we need to use its output as input. + subsetRegion.SetIndex(Dimension - 1, baseIndex + i); + subsetRegion.SetSize(Dimension - 1, std::min(m_ProjectionSubsetSize, nProj - i)); + m_ExtractFilter->SetInput(this->GetInput(1)); + m_ExtractFilter->SetExtractionRegion(subsetRegion); + if (rtk::IsContiguousSubRegion(this->GetInput(1), subsetRegion)) + m_WeightFilter->InPlaceOff(); + if (i) { typename TInputImage::Pointer pimg = m_BackProjectionFilter->GetOutput(); pimg->DisconnectPipeline(); m_BackProjectionFilter->SetInput(pimg); - // Change projection subset - subsetRegion.SetIndex(Dimension - 1, i); - subsetRegion.SetSize(Dimension - 1, std::min(m_ProjectionSubsetSize, nProj - i)); - m_ExtractFilter->SetExtractionRegion(subsetRegion); - - // This is required to reset the full pipeline m_BackProjectionFilter->GetOutput()->UpdateOutputInformation(); m_BackProjectionFilter->GetOutput()->PropagateRequestedRegion(); } + m_BackProjectionFilter->Update(); } diff --git a/include/rtkFDKVarianceReconstructionFilter.h b/include/rtkFDKVarianceReconstructionFilter.h index 4e3b7a8b9..b0f61a985 100644 --- a/include/rtkFDKVarianceReconstructionFilter.h +++ b/include/rtkFDKVarianceReconstructionFilter.h @@ -23,8 +23,7 @@ #include "rtkConfiguration.h" #include "rtkFDKBackProjectionImageFilter.h" #include "rtkFFTVarianceRampImageFilter.h" - -#include +#include "rtkSubRegionViewImageFilter.h" namespace rtk { @@ -68,7 +67,7 @@ class ITK_TEMPLATE_EXPORT FDKVarianceReconstructionFilter : public itk::InPlaceI using OutputImageType = TOutputImage; /** Typedefs of each subfilter of this composite filter */ - using ExtractFilterType = itk::ExtractImageFilter; + using ExtractFilterType = rtk::SubRegionViewImageFilter; using WeightFilterType = rtk::FDKWeightProjectionFilter; using VarianceRampFilterType = rtk::FFTVarianceRampImageFilter; using BackProjectionFilterType = rtk::FDKBackProjectionImageFilter; diff --git a/include/rtkFDKVarianceReconstructionFilter.hxx b/include/rtkFDKVarianceReconstructionFilter.hxx index 0dde3f13f..50fac578f 100644 --- a/include/rtkFDKVarianceReconstructionFilter.hxx +++ b/include/rtkFDKVarianceReconstructionFilter.hxx @@ -44,7 +44,6 @@ FDKVarianceReconstructionFilter::FDKVa m_VarianceRampFilter->SetInput(m_WeightFilter2->GetOutput()); // Default parameters - m_ExtractFilter->SetDirectionCollapseToSubmatrix(); m_WeightFilter1->InPlaceOn(); m_WeightFilter2->InPlaceOn(); @@ -83,6 +82,9 @@ FDKVarianceReconstructionFilter::Gener m_ExtractFilter->SetInput(this->GetInput(1)); m_BackProjectionFilter->GetOutput()->SetRequestedRegion(this->GetOutput()->GetRequestedRegion()); m_BackProjectionFilter->GetOutput()->PropagateRequestedRegion(); + + typename Superclass::InputImagePointer inputPtr1 = const_cast(this->GetInput(1)); + inputPtr1->SetRequestedRegion(this->GetInput(1)->GetLargestPossibleRegion()); } template @@ -97,16 +99,19 @@ FDKVarianceReconstructionFilter::Gener // We only set the first sub-stack at that point, the rest will be // requested in the GenerateData function - typename ExtractFilterType::InputImageRegionType projRegion; + typename InputImageType::RegionType projRegion; projRegion = this->GetInput(1)->GetLargestPossibleRegion(); unsigned int firstStackSize = std::min(m_ProjectionSubsetSize, (unsigned int)projRegion.GetSize(Dimension - 1)); projRegion.SetSize(Dimension - 1, firstStackSize); + + m_ExtractFilter->SetInput(this->GetInput(1)); m_ExtractFilter->SetExtractionRegion(projRegion); + if (rtk::IsContiguousSubRegion(this->GetInput(1), projRegion)) + m_WeightFilter1->InPlaceOff(); // Run composite filter update m_BackProjectionFilter->SetInput(0, this->GetInput(0)); m_BackProjectionFilter->SetInPlace(this->GetInPlace()); - m_ExtractFilter->SetInput(this->GetInput(1)); m_BackProjectionFilter->UpdateOutputInformation(); // Update output information @@ -123,9 +128,10 @@ FDKVarianceReconstructionFilter::Gener const unsigned int Dimension = this->InputImageDimension; // The backprojection works on a small stack of projections, not the full stack - typename ExtractFilterType::InputImageRegionType subsetRegion; + typename InputImageType::RegionType subsetRegion; subsetRegion = this->GetInput(1)->GetLargestPossibleRegion(); unsigned int nProj = subsetRegion.GetSize(Dimension - 1); + unsigned int baseIndex = subsetRegion.GetIndex(Dimension - 1); // The progress accumulator tracks the progress of the pipeline // Each filter is equally weighted across all iterations of the stack @@ -146,15 +152,19 @@ FDKVarianceReconstructionFilter::Gener pimg->DisconnectPipeline(); m_BackProjectionFilter->SetInput(pimg); - // Change projection subset - subsetRegion.SetIndex(Dimension - 1, i); - subsetRegion.SetSize(Dimension - 1, std::min(m_ProjectionSubsetSize, nProj - i)); - m_ExtractFilter->SetExtractionRegion(subsetRegion); - // This is required to reset the full pipeline m_BackProjectionFilter->GetOutput()->UpdateOutputInformation(); m_BackProjectionFilter->GetOutput()->PropagateRequestedRegion(); } + + // Always create the substack for the current subset + subsetRegion.SetIndex(Dimension - 1, baseIndex + i); + subsetRegion.SetSize(Dimension - 1, std::min(m_ProjectionSubsetSize, nProj - i)); + m_ExtractFilter->SetInput(this->GetInput(1)); + m_ExtractFilter->SetExtractionRegion(subsetRegion); + if (rtk::IsContiguousSubRegion(this->GetInput(1), subsetRegion)) + m_WeightFilter1->InPlaceOff(); + m_BackProjectionFilter->Update(); } diff --git a/include/rtkOSEMConeBeamReconstructionFilter.h b/include/rtkOSEMConeBeamReconstructionFilter.h index 55c346d5f..25b58642b 100644 --- a/include/rtkOSEMConeBeamReconstructionFilter.h +++ b/include/rtkOSEMConeBeamReconstructionFilter.h @@ -26,10 +26,10 @@ #include #include #include -#include #include #include "rtkConstantImageSource.h" +#include "rtkSubRegionViewImageFilter.h" #include "rtkIterativeConeBeamReconstructionFilter.h" namespace rtk @@ -45,7 +45,7 @@ namespace rtk * - DivideImageFilter, * - BackProjectionImageFilter. * The input stack of projections is processed piece by piece (the size is - * controlled with ProjectionSubsetSize) via the use of itk::ExtractImageFilter + * controlled with ProjectionSubsetSize) via the use of rtk::SubRegionViewImageFilter * to extract sub-stacks. * * One weighting steps must be applied when processing a given subset: @@ -69,7 +69,7 @@ namespace rtk * * node [shape=box]; * ForwardProject [ label="rtk::ForwardProjectionImageFilter" URL="\ref rtk::ForwardProjectionImageFilter"]; - * Extract [ label="itk::ExtractImageFilter" URL="\ref itk::ExtractImageFilter"]; + * Extract [ label="rtk::SubRegionViewImageFilter" URL="\ref rtk::SubRegionViewImageFilter"]; * Divide1 [ label="itk::DivideImageFilter" URL="\ref itk::DivideImageFilter"]; * Divide [ label="itk::DivideImageFilter" URL="\ref itk::DivideImageFilter"]; * ProjectionZero [ label="rtk::ConstantImageSource (full of zero)" URL="\ref rtk::ConstantImageSource"]; @@ -132,7 +132,7 @@ class ITK_TEMPLATE_EXPORT OSEMConeBeamReconstructionFilter using ProjectionType = TProjectionImage; /** Typedefs of each subfilter of this composite filter */ - using ExtractFilterType = itk::ExtractImageFilter; + using ExtractFilterType = rtk::SubRegionViewImageFilter; using MultiplyFilterType = itk::MultiplyImageFilter; using ForwardProjectionFilterType = rtk::ForwardProjectionImageFilter; using BackProjectionFilterType = rtk::BackProjectionImageFilter; diff --git a/include/rtkOSEMConeBeamReconstructionFilter.hxx b/include/rtkOSEMConeBeamReconstructionFilter.hxx index 080d0e8ae..933d52bb3 100644 --- a/include/rtkOSEMConeBeamReconstructionFilter.hxx +++ b/include/rtkOSEMConeBeamReconstructionFilter.hxx @@ -53,7 +53,6 @@ OSEMConeBeamReconstructionFilter::OSEMConeBeamRe m_DivideVolumeFilter->SetInput1(m_MultiplyFilter->GetOutput()); // Default parameters - m_ExtractFilter->SetDirectionCollapseToSubmatrix(); } template @@ -86,7 +85,7 @@ OSEMConeBeamReconstructionFilter::GenerateOutput // We only set the first sub-stack at that point, the rest will be // requested in the GenerateData function - typename ExtractFilterType::InputImageRegionType projRegion; + typename ProjectionType::RegionType projRegion; // Set forward projection filter m_ForwardProjectionFilter = this->InstantiateForwardProjectionFilter(this->m_CurrentForwardProjectionConfiguration); @@ -97,22 +96,21 @@ OSEMConeBeamReconstructionFilter::GenerateOutput this->InstantiateBackProjectionFilter(this->m_CurrentBackProjectionConfiguration); projRegion = this->GetInput(1)->GetLargestPossibleRegion(); - m_ExtractFilter->SetExtractionRegion(projRegion); - - m_ExtractFilter->SetInput(this->GetInput(1)); - m_ExtractFilter->UpdateOutputInformation(); // Links with the forward and back projection filters should be set here // and not in the constructor, as these filters are set at runtime m_ConstantImageSource->SetInformationFromImage(const_cast(this->GetInput(0))); m_ConstantImageSource->SetConstant(0); - m_OneConstantProjectionStackSource->SetInformationFromImage( - const_cast(m_ExtractFilter->GetOutput())); + m_ExtractFilter->SetInput(this->GetInput(1)); + m_ExtractFilter->SetExtractionRegion(projRegion); + m_ExtractFilter->UpdateOutputInformation(); + if (rtk::IsContiguousSubRegion(this->GetInput(1), projRegion)) + m_DivideProjectionFilter->InPlaceOff(); + m_OneConstantProjectionStackSource->SetInformationFromImage(m_ExtractFilter->GetOutput()); m_OneConstantProjectionStackSource->SetConstant(1); - m_ZeroConstantProjectionStackSource->SetInformationFromImage( - const_cast(m_ExtractFilter->GetOutput())); + m_ZeroConstantProjectionStackSource->SetInformationFromImage(m_ExtractFilter->GetOutput()); m_ZeroConstantProjectionStackSource->SetConstant(0); m_BackProjectionFilter->SetInput(0, m_ConstantImageSource->GetOutput()); @@ -163,9 +161,10 @@ OSEMConeBeamReconstructionFilter::GenerateData() const unsigned int Dimension = this->InputImageDimension; // The backprojection works on one projection at a time - typename ExtractFilterType::InputImageRegionType subsetRegion; + typename ProjectionType::RegionType subsetRegion; subsetRegion = this->GetInput(1)->GetLargestPossibleRegion(); unsigned int nProj = subsetRegion.GetSize(Dimension - 1); + unsigned int baseIndex = subsetRegion.GetIndex(Dimension - 1); subsetRegion.SetSize(Dimension - 1, 1); // Fill and shuffle randomly the projection order. @@ -195,12 +194,25 @@ OSEMConeBeamReconstructionFilter::GenerateData() for (unsigned int i = 0; i < nProj; i++) { // Change projection subset - subsetRegion.SetIndex(Dimension - 1, projOrder[i]); + subsetRegion.SetIndex(Dimension - 1, baseIndex + projOrder[i]); + m_ExtractFilter->SetInput(this->GetInput(1)); m_ExtractFilter->SetExtractionRegion(subsetRegion); m_ExtractFilter->UpdateOutputInformation(); - m_ZeroConstantProjectionStackSource->SetInformationFromImage( - const_cast(m_ExtractFilter->GetOutput())); + // Constant projection for normalization backprojection + typename ConstantProjectionSourceType::Pointer oneNormProj = ConstantProjectionSourceType::New(); + oneNormProj->SetInformationFromImage(m_ExtractFilter->GetOutput()); + oneNormProj->SetConstant(1); + + // Constant projection for forward projection input + typename ConstantProjectionSourceType::Pointer zeroProj = ConstantProjectionSourceType::New(); + zeroProj->SetInformationFromImage(m_ExtractFilter->GetOutput()); + zeroProj->SetConstant(0); + zeroProj->Update(); + + if (rtk::IsContiguousSubRegion(this->GetInput(1), subsetRegion)) + m_DivideProjectionFilter->InPlaceOff(); + m_ForwardProjectionFilter->SetInput(0, zeroProj->GetOutput()); // This is required to reset the full pipeline m_BackProjectionFilter->GetOutput()->UpdateOutputInformation(); @@ -209,8 +221,8 @@ OSEMConeBeamReconstructionFilter::GenerateData() m_BackProjectionFilter->Update(); if (iter == 0 || !m_StoreNormalizationImages) { - m_OneConstantProjectionStackSource->SetInformationFromImage( - const_cast(m_ExtractFilter->GetOutput())); + oneNormProj->SetInformationFromImage(m_ExtractFilter->GetOutput()); + m_BackProjectionNormalizationFilter->SetInput(1, oneNormProj->GetOutput()); m_BackProjectionNormalizationFilter->GetOutput()->UpdateOutputInformation(); m_BackProjectionNormalizationFilter->GetOutput()->PropagateRequestedRegion(); m_BackProjectionNormalizationFilter->Update(); diff --git a/include/rtkSARTConeBeamReconstructionFilter.h b/include/rtkSARTConeBeamReconstructionFilter.h index 5ca4b9822..84079aa1c 100644 --- a/include/rtkSARTConeBeamReconstructionFilter.h +++ b/include/rtkSARTConeBeamReconstructionFilter.h @@ -30,11 +30,12 @@ #include #include #include -#include #include #include #include +#include "rtkSubRegionViewImageFilter.h" + namespace rtk { @@ -48,8 +49,8 @@ namespace rtk * - SubtractImageFilter, * - BackProjectionImageFilter. * The input stack of projections is processed piece by piece (the size is - * controlled with ProjectionSubsetSize) via the use of itk::ExtractImageFilter - * to extract sub-stacks. + * controlled with ProjectionSubsetSize) by extracting sub-stacks directly + * from the input buffer pointer (zero-copy). * * Two weighting steps must be applied when processing a given projection: * - each pixel of the forward projection must be divided by the total length of the @@ -74,7 +75,7 @@ namespace rtk * * node [shape=box]; * ForwardProject [ label="rtk::ForwardProjectionImageFilter" URL="\ref rtk::ForwardProjectionImageFilter"]; - * Extract [ label="itk::ExtractImageFilter" URL="\ref itk::ExtractImageFilter"]; + * Extract [ label="rtk::SubRegionViewImageFilter" URL="\ref rtk::SubRegionViewImageFilter"]; * MultiplyByZero [ label="itk::MultiplyImageFilter (by zero)" URL="\ref itk::MultiplyImageFilter"]; * AfterExtract [label="", fixedsize="false", width=0, height=0, shape=none]; * Subtract [ label="itk::SubtractImageFilter" URL="\ref itk::SubtractImageFilter"]; @@ -85,7 +86,7 @@ namespace rtk * URL="\ref itk::MultiplyImageFilter", style=dashed]; * Displaced [ label="rtk::DisplacedDetectorImageFilter" URL="\ref rtk::DisplacedDetectorImageFilter"]; * ConstantProjectionStack [ label="rtk::ConstantImageSource (0)" URL="\ref rtk::ConstantImageSource"]; - * ExtractConstantProjection [ label="itk::ExtractImageFilter" URL="\ref itk::ExtractImageFilter"]; + * ExtractConstantProjection [ label="rtk::SubRegionViewImageFilter" URL="\ref rtk::SubRegionViewImageFilter"]; * RayBox [ label="rtk::RayBoxIntersectionImageFilter" URL="\ref rtk::RayBoxIntersectionImageFilter"]; * ConstantVolume [ label="rtk::ConstantImageSource (0)" URL="\ref rtk::ConstantImageSource"]; * BackProjection [ label="rtk::BackProjectionImageFilter" URL="\ref rtk::BackProjectionImageFilter"]; @@ -156,7 +157,7 @@ class ITK_TEMPLATE_EXPORT SARTConeBeamReconstructionFilter using ProjectionPixelType = typename ProjectionType::PixelType; /** Typedefs of each subfilter of this composite filter */ - using ExtractFilterType = itk::ExtractImageFilter; + using ExtractFilterType = rtk::SubRegionViewImageFilter; using MultiplyFilterType = itk::MultiplyImageFilter; using ForwardProjectionFilterType = rtk::ForwardProjectionImageFilter; using SubtractFilterType = itk::SubtractImageFilter; @@ -247,7 +248,6 @@ class ITK_TEMPLATE_EXPORT SARTConeBeamReconstructionFilter /** Pointers to each subfilter of this composite filter */ typename ExtractFilterType::Pointer m_ExtractFilter; - typename ExtractFilterType::Pointer m_ExtractFilterRayBox; typename MultiplyFilterType::Pointer m_ZeroMultiplyFilter; typename ForwardProjectionFilterType::Pointer m_ForwardProjectionFilter; typename SubtractFilterType::Pointer m_SubtractFilter; diff --git a/include/rtkSARTConeBeamReconstructionFilter.hxx b/include/rtkSARTConeBeamReconstructionFilter.hxx index f28c30836..e5d982d54 100644 --- a/include/rtkSARTConeBeamReconstructionFilter.hxx +++ b/include/rtkSARTConeBeamReconstructionFilter.hxx @@ -49,7 +49,6 @@ SARTConeBeamReconstructionFilter::SARTConeBeamRe // Create the filters required for correct weighting of the difference // projection - m_ExtractFilterRayBox = ExtractFilterType::New(); m_RayBoxFilter = RayBoxIntersectionFilterType::New(); m_DivideProjectionFilter = DivideProjectionFilterType::New(); m_ConstantProjectionStackSource = ConstantProjectionSourceType::New(); @@ -64,6 +63,7 @@ SARTConeBeamReconstructionFilter::SARTConeBeamRe m_DivisionThreshold = m_DivideVolumeFilter->GetThreshold(); // Permanent internal connections + m_SubtractFilter->InPlaceOn(); m_ZeroMultiplyFilter->SetInput1(itk::NumericTraits::ZeroValue()); m_ZeroMultiplyFilter->SetInput2(m_ExtractFilter->GetOutput()); @@ -72,15 +72,12 @@ SARTConeBeamReconstructionFilter::SARTConeBeamRe m_MultiplyFilter->SetInput1(m_Lambda); m_MultiplyFilter->SetInput2(m_SubtractFilter->GetOutput()); - m_ExtractFilterRayBox->SetInput(m_ConstantProjectionStackSource->GetOutput()); - m_RayBoxFilter->SetInput(m_ExtractFilterRayBox->GetOutput()); + m_RayBoxFilter->SetInput(m_ConstantProjectionStackSource->GetOutput()); m_DivideProjectionFilter->SetInput1(m_MultiplyFilter->GetOutput()); m_DivideProjectionFilter->SetInput2(m_RayBoxFilter->GetOutput()); m_DisplacedDetectorFilter->SetInput(m_DivideProjectionFilter->GetOutput()); // Default parameters - m_ExtractFilter->SetDirectionCollapseToSubmatrix(); - m_ExtractFilterRayBox->SetDirectionCollapseToSubmatrix(); m_DisplacedDetectorFilter->SetPadOnTruncatedSide(false); } @@ -132,11 +129,19 @@ SARTConeBeamReconstructionFilter::GenerateOutput // We only set the first sub-stack at that point, the rest will be // requested in the GenerateData function - typename ExtractFilterType::InputImageRegionType projRegion; + typename ProjectionType::RegionType projRegion; projRegion = this->GetInput(1)->GetLargestPossibleRegion(); + + // Set extract filter input and region + m_ExtractFilter->SetInput(this->GetInput(1)); m_ExtractFilter->SetExtractionRegion(projRegion); - m_ExtractFilterRayBox->SetExtractionRegion(projRegion); + m_ExtractFilter->UpdateOutputInformation(); + if (rtk::IsContiguousSubRegion(this->GetInput(1), projRegion)) + { + m_ZeroMultiplyFilter->InPlaceOff(); + m_SubtractFilter->InPlaceOff(); + } // Set forward projection filter m_ForwardProjectionFilter = this->InstantiateForwardProjectionFilter(this->m_CurrentForwardProjectionConfiguration); @@ -152,8 +157,7 @@ SARTConeBeamReconstructionFilter::GenerateOutput m_ConstantImageSource->SetConstant(0); m_ConstantImageSource->UpdateOutputInformation(); - m_OneConstantProjectionStackSource->SetInformationFromImage( - const_cast(m_ExtractFilter->GetOutput())); + m_OneConstantProjectionStackSource->SetInformationFromImage(m_ExtractFilter->GetOutput()); m_OneConstantProjectionStackSource->SetConstant(1); m_BackProjectionFilter->SetInput(0, m_ConstantImageSource->GetOutput()); @@ -186,7 +190,6 @@ SARTConeBeamReconstructionFilter::GenerateOutput m_ForwardProjectionFilter->SetInput(0, m_ZeroMultiplyFilter->GetOutput()); m_ForwardProjectionFilter->SetInput(1, this->GetInput(0)); - m_ExtractFilter->SetInput(this->GetInput(1)); m_SubtractFilter->SetInput(1, m_ForwardProjectionFilter->GetOutput()); m_ForwardProjectionFilter->SetGeometry(this->m_Geometry); @@ -250,9 +253,10 @@ SARTConeBeamReconstructionFilter::GenerateData() const unsigned int Dimension = this->InputImageDimension; // The backprojection works on one projection at a time - typename ExtractFilterType::InputImageRegionType subsetRegion; + typename ProjectionType::RegionType subsetRegion; subsetRegion = this->GetInput(1)->GetLargestPossibleRegion(); unsigned int nProj = subsetRegion.GetSize(Dimension - 1); + unsigned int baseIndex = subsetRegion.GetIndex(Dimension - 1); subsetRegion.SetSize(Dimension - 1, 1); // Fill and shuffle randomly the projection order. @@ -279,13 +283,21 @@ SARTConeBeamReconstructionFilter::GenerateData() for (unsigned int i = 0; i < nProj; i++) { // Change projection subset - subsetRegion.SetIndex(Dimension - 1, projOrder[i]); + subsetRegion.SetIndex(Dimension - 1, baseIndex + projOrder[i]); + m_ExtractFilter->SetInput(this->GetInput(1)); m_ExtractFilter->SetExtractionRegion(subsetRegion); - m_ExtractFilterRayBox->SetExtractionRegion(subsetRegion); m_ExtractFilter->UpdateOutputInformation(); + if (rtk::IsContiguousSubRegion(this->GetInput(1), subsetRegion)) + { + m_ZeroMultiplyFilter->InPlaceOff(); + m_SubtractFilter->InPlaceOff(); + } - m_OneConstantProjectionStackSource->SetInformationFromImage( - const_cast(m_ExtractFilter->GetOutput())); + // Update constant sources with current projection's metadata + m_OneConstantProjectionStackSource->SetInformationFromImage(m_ExtractFilter->GetOutput()); + m_OneConstantProjectionStackSource->SetConstant(1); + m_ConstantProjectionStackSource->SetInformationFromImage(m_ExtractFilter->GetOutput()); + m_ConstantProjectionStackSource->SetConstant(0); // Set gating weight for the current projection if (m_IsGated) diff --git a/include/rtkSubRegionViewImageFilter.h b/include/rtkSubRegionViewImageFilter.h new file mode 100644 index 000000000..477a8c00b --- /dev/null +++ b/include/rtkSubRegionViewImageFilter.h @@ -0,0 +1,160 @@ +/*========================================================================= + * + * Copyright RTK Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + *=========================================================================*/ + +#ifndef rtkSubRegionViewImageFilter_h +#define rtkSubRegionViewImageFilter_h + +#include +#include +#include +#include + +namespace rtk +{ + +/** Return the row-major element offset of a pixel index within the input's + * buffered pixel buffer. */ +template +typename TImage::SizeType::SizeValueType +ComputePixelOffset(const TImage * input, const itk::Index & index) +{ + constexpr unsigned int Dimension = TImage::ImageDimension; + const auto & inputRegion = input->GetBufferedRegion(); + const auto & inputIndex = inputRegion.GetIndex(); + + typename TImage::SizeType::SizeValueType stride = 1; + typename TImage::SizeType::SizeValueType offset = 0; + for (unsigned int d = 0; d < Dimension; ++d) + { + offset += (index[d] - inputIndex[d]) * stride; + stride *= inputRegion.GetSize()[d]; + } + return offset; +} + +/** Check if a sub-region is a contiguous block of the input pixel buffer + * (i.e. its pixels occupy consecutive memory addresses, so it can be viewed + * without copying). A dimension of size 1 is handled automatically: it is + * contiguous whenever it does not introduce a memory gap. */ +template +bool +IsContiguousSubRegion(const TImage * input, const itk::ImageRegion & region) +{ + // The region must lie inside the buffered region. + if (!input->GetBufferedRegion().IsInside(region)) + return false; + + // Row-major addresses (relative to the input buffer) of the region's first + // and last pixels. The region is contiguous iff its pixels span exactly + // their number of memory addresses between those two addresses. + itk::Index lastIndex = region.GetIndex(); + for (unsigned int d = 0; d < TImage::ImageDimension; ++d) + lastIndex[d] += region.GetSize()[d] - 1; + + return ComputePixelOffset(input, lastIndex) - ComputePixelOffset(input, region.GetIndex()) + 1 == + region.GetNumberOfPixels(); +} + +/** \class SubRegionViewImageFilter + * \brief Extract a sub-region of an image, sharing the buffer when contiguous. + * + * The output is a non-owning "view" of a sub-region of the input image. When + * the extraction region is contiguous (its pixels occupy consecutive memory + * addresses), the output shares the input pixel buffer without copying pixels + * (like a numpy view). Otherwise, a copy is made with itk::ExtractImageFilter. + * + * This filter derives from itk::ImageToImageFilter, not itk::InPlaceImageFilter, + * because it is never an in-place filter: it neither modifies nor consumes its + * input. itk::InPlaceImageFilter would be a poor fit for two reasons: it only + * shares the buffer when the input's buffered region exactly matches the + * output's requested region, which never holds for a sub-region (so it would + * silently fall back to a plain copy), and its semantics are destructive — it + * grafts the whole input onto the output region and releases the input's data + * afterwards. Deriving from itk::ImageToImageFilter instead keeps the input + * intact and usable by other consumers while the output is a smaller view of it. + * + * \warning The output shares the input pixel buffer, so downstream filters + * that operate in place (InPlaceOn) will corrupt the input data. Only use + * this filter in pipelines without in-place consumers (or call InPlaceOff() + * on the downstream filters). + * + * \author Axel Garcia + * + * \ingroup RTK + */ +template +class ITK_TEMPLATE_EXPORT SubRegionViewImageFilter : public itk::ImageToImageFilter +{ +public: + ITK_DISALLOW_COPY_AND_MOVE(SubRegionViewImageFilter); + + /** Standard class type alias. */ + using Self = SubRegionViewImageFilter; + using Superclass = itk::ImageToImageFilter; + using Pointer = itk::SmartPointer; + using ConstPointer = itk::SmartPointer; + using RegionType = itk::ImageRegion; + + /** Standard New method. */ + itkNewMacro(Self); + + /** Runtime information support. */ + itkOverrideGetNameOfClassMacro(SubRegionViewImageFilter); + + /** Set the region to extract from the input image. */ + void + SetExtractionRegion(const RegionType & region) + { + if (m_ExtractionRegion != region) + { + m_ExtractionRegion = region; + this->Modified(); + } + } + itkGetConstReferenceMacro(ExtractionRegion, RegionType); + + /** After Update(), returns true if the output shares the input buffer. */ + itkGetMacro(IsContiguous, bool); + +protected: + SubRegionViewImageFilter() = default; + ~SubRegionViewImageFilter() override = default; + + void + GenerateInputRequestedRegion() override; + + void + GenerateOutputInformation() override; + + void + GenerateData() override; + +private: + RegionType m_ExtractionRegion; + bool m_IsContiguous{ false }; +}; + +} // namespace rtk + +#include "rtkCudaExternTemplates.h" + +#ifndef ITK_MANUAL_INSTANTIATION +# include "rtkSubRegionViewImageFilter.hxx" +#endif + +#endif // rtkSubRegionViewImageFilter_h diff --git a/include/rtkSubRegionViewImageFilter.hxx b/include/rtkSubRegionViewImageFilter.hxx new file mode 100644 index 000000000..6fe644208 --- /dev/null +++ b/include/rtkSubRegionViewImageFilter.hxx @@ -0,0 +1,95 @@ +/*========================================================================= + * + * Copyright RTK Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + *=========================================================================*/ + +#ifndef rtkSubRegionViewImageFilter_hxx +#define rtkSubRegionViewImageFilter_hxx + +namespace rtk +{ + +template +void +SubRegionViewImageFilter::GenerateInputRequestedRegion() +{ + // The output can span the full extraction region, so ask for it all. + auto * input = const_cast(this->GetInput()); + if (input) + { + input->SetRequestedRegion(m_ExtractionRegion); + } +} + +template +void +SubRegionViewImageFilter::GenerateOutputInformation() +{ + const auto * input = this->GetInput(); + auto * output = this->GetOutput(); + + output->SetRegions(m_ExtractionRegion); + output->SetSpacing(input->GetSpacing()); + output->SetOrigin(input->GetOrigin()); + output->SetDirection(input->GetDirection()); + output->SetNumberOfComponentsPerPixel(input->GetNumberOfComponentsPerPixel()); +} + +template +void +SubRegionViewImageFilter::GenerateData() +{ + using PixelType = typename TImage::PixelType; + + const auto * input = this->GetInput(); + auto * output = this->GetOutput(); + + m_IsContiguous = IsContiguousSubRegion(input, m_ExtractionRegion); + + const PixelType * inputPtr = input->GetBufferPointer(); + // Input buffer not available yet: return a metadata-only output. + if (!inputPtr) + return; + + if (m_IsContiguous) + { + // Zero-copy: share the input pixel buffer, starting at the offset of the + // region's first pixel (computed over all dimensions so size-1 dimensions + // are handled: they still contribute their stride to later dimensions). + const typename TImage::SizeType::SizeValueType offset = ComputePixelOffset(input, m_ExtractionRegion.GetIndex()); + const typename TImage::SizeType::SizeValueType numPixels = m_ExtractionRegion.GetNumberOfPixels(); + output->GetPixelContainer()->SetImportPointer(const_cast(inputPtr + offset), numPixels, false); + + // Re-assign pixel container to sync subclass containers (e.g. + // CudaDataManager reads the CPU pointer and marks GPU dirty). + output->SetPixelContainer(output->GetPixelContainer()); + } + else + { + // Non-contiguous: fall back to itk::ExtractImageFilter (a real copy). + using ExtractFilterType = itk::ExtractImageFilter; + typename ExtractFilterType::Pointer extractFilter = ExtractFilterType::New(); + extractFilter->SetInput(input); + extractFilter->SetExtractionRegion(m_ExtractionRegion); + extractFilter->SetDirectionCollapseToSubmatrix(); + extractFilter->Update(); + output->Graft(extractFilter->GetOutput()); + } +} + +} // namespace rtk + +#endif // rtkSubRegionViewImageFilter_hxx diff --git a/src/rtkCudaExternTemplates.cxx b/src/rtkCudaExternTemplates.cxx index 594c9b396..431ed8528 100644 --- a/src/rtkCudaExternTemplates.cxx +++ b/src/rtkCudaExternTemplates.cxx @@ -34,6 +34,7 @@ template class itk::ImageSource, 3 # include "rtkFDKWeightProjectionFilter.h" # include "rtkConstantImageSource.h" # include "rtkInterpolatorWithKnownWeightsImageFilter.h" +# include "rtkSubRegionViewImageFilter.h" template class rtk::BackProjectionImageFilter, itk::CudaImage>; template class rtk::FDKBackProjectionImageFilter, itk::CudaImage>; @@ -42,6 +43,7 @@ template class rtk::DisplacedDetectorImageFilter>; template class rtk::FDKWeightProjectionFilter>; template class rtk::ConstantImageSource>; template class rtk::InterpolatorWithKnownWeightsImageFilter, itk::CudaImage>; +template class rtk::SubRegionViewImageFilter>; namespace rtk { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 18da9b27c..71e429225 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -113,6 +113,9 @@ endif() rtk_add_test(rtkFDKTest rtkfdktest.cxx) rtk_add_cuda_test(rtkFDKCudaTest rtkfdktest.cxx) +rtk_add_test(rtkSubRegionViewImageTest rtksubregionviewimagetest.cxx) +rtk_add_cuda_test(rtkSubRegionViewImageCudaTest rtksubregionviewimagetest.cxx) + rtk_add_cuda_test(rtkFDKProjWeightCompCudaTest rtkfdkprojweightcompcudatest.cxx) rtk_add_test(rtkFBPParallelTest rtkfbpparalleltest.cxx) diff --git a/test/rtksubregionviewimagetest.cxx b/test/rtksubregionviewimagetest.cxx new file mode 100644 index 000000000..54a5421d8 --- /dev/null +++ b/test/rtksubregionviewimagetest.cxx @@ -0,0 +1,203 @@ +#include + +#include "rtkSubRegionViewImageFilter.h" +#include "rtkTest.h" + +#ifdef USE_CUDA +# include +#endif + +/** + * \file rtksubregionviewimagetest.cxx + * + * \brief Functional test for rtk::SubRegionViewImageFilter + * + * This test verifies that the filter extracts a sub-region of an image by + * sharing the input buffer (zero-copy view) when the pixels are contiguous, + * and by copying when they are not. It also checks the size-1 dimension case, + * which is contiguous only when it spans a singleton dimension of the input. + * + * \author Axel Garcia + */ + +constexpr unsigned int Dimension = 3; +using PixelType = float; +#ifdef USE_CUDA +using ImageType = itk::CudaImage; +#else +using ImageType = itk::Image; +#endif + +/* Fill an image with unique values v = i0 + i1*10 + i2*100, using absolute + * indices (local index + indexOffset) so any region can be reproduced. */ +void +FillGradient(ImageType::Pointer image, const ImageType::IndexType & indexOffset) +{ + itk::ImageRegionIteratorWithIndex it(image, image->GetLargestPossibleRegion()); + for (it.GoToBegin(); !it.IsAtEnd(); ++it) + { + const ImageType::IndexType idx = it.GetIndex(); + it.Set(static_cast(idx[0] + indexOffset[0]) + static_cast(idx[1] + indexOffset[1]) * 10.0F + + static_cast(idx[2] + indexOffset[2]) * 100.0F); + } +} + +ImageType::Pointer +CreateGradientImage(const ImageType::SizeType & size, const ImageType::IndexType & indexOffset) +{ + ImageType::Pointer image = ImageType::New(); + image->SetRegions(size); + image->Allocate(); + FillGradient(image, indexOffset); + return image; +} + +int +rtksubregionviewimagetest(int, char *[]) +{ + using ViewFilterType = rtk::SubRegionViewImageFilter; + using RegionType = ImageType::RegionType; + + // Input image with known pixel values + ImageType::Pointer input = CreateGradientImage(itk::MakeSize(4, 5, 6), itk::MakeIndex(0, 0, 0)); + + // ===== Case 1: contiguous region -> zero-copy view ===== + RegionType region; + region.SetIndex(itk::MakeIndex(0, 0, 2)); + region.SetSize(itk::MakeSize(4, 5, 3)); + + auto view = ViewFilterType::New(); + view->SetInput(input); + view->SetExtractionRegion(region); + TRY_AND_EXIT_ON_ITK_EXCEPTION(view->Update()) + + if (!view->GetIsContiguous()) + { + std::cerr << "Case 1 failed: region should be contiguous" << std::endl; + return EXIT_FAILURE; + } + if (view->GetOutput()->GetLargestPossibleRegion() != region) + { + std::cerr << "Case 1 failed: wrong output region" << std::endl; + return EXIT_FAILURE; + } + // The output must share the input buffer at offset 2*4*5 = 40. + ptrdiff_t offset = view->GetOutput()->GetBufferPointer() - input->GetBufferPointer(); + if (offset != 40) + { + std::cerr << "Case 1 failed: expected view offset 40, got " << offset << std::endl; + return EXIT_FAILURE; + } + ImageType::Pointer reference = CreateGradientImage(itk::MakeSize(4, 5, 3), itk::MakeIndex(0, 0, 2)); + CheckImageQuality(view->GetOutput(), reference, 0.001, 100, 432.); + + // ===== Case 2: non-contiguous region -> copy ===== + region.SetIndex(itk::MakeIndex(1, 1, 3)); + region.SetSize(itk::MakeSize(2, 2, 2)); + + auto copy = ViewFilterType::New(); + copy->SetInput(input); + copy->SetExtractionRegion(region); + TRY_AND_EXIT_ON_ITK_EXCEPTION(copy->Update()) + + if (copy->GetIsContiguous()) + { + std::cerr << "Case 2 failed: region should NOT be contiguous" << std::endl; + return EXIT_FAILURE; + } + if (copy->GetOutput()->GetLargestPossibleRegion() != region) + { + std::cerr << "Case 2 failed: wrong output region" << std::endl; + return EXIT_FAILURE; + } + // The copy must not point into the input buffer. + const PixelType * inputPtr = input->GetBufferPointer(); + const PixelType * outputPtr = copy->GetOutput()->GetBufferPointer(); + if (outputPtr >= inputPtr && outputPtr < inputPtr + input->GetLargestPossibleRegion().GetNumberOfPixels()) + { + std::cerr << "Case 2 failed: copy must not share the input buffer" << std::endl; + return EXIT_FAILURE; + } + ImageType::Pointer reference2 = CreateGradientImage(itk::MakeSize(2, 2, 2), itk::MakeIndex(1, 1, 3)); + CheckImageQuality(copy->GetOutput(), reference2, 0.001, 100, 432.); + + // ===== Case 3: input without buffer -> metadata-only output ===== + ImageType::Pointer metaInput = ImageType::New(); // not allocated + metaInput->SetRegions(itk::MakeSize(4, 5, 6)); + + RegionType metaRegion; + metaRegion.SetIndex(itk::MakeIndex(0, 0, 1)); + metaRegion.SetSize(itk::MakeSize(4, 5, 2)); + + auto metadata = ViewFilterType::New(); + metadata->SetInput(metaInput); + metadata->SetExtractionRegion(metaRegion); + TRY_AND_EXIT_ON_ITK_EXCEPTION(metadata->UpdateOutputInformation()) + + if (metadata->GetOutput()->GetLargestPossibleRegion() != metaRegion) + { + std::cerr << "Case 3 failed: wrong output region" << std::endl; + return EXIT_FAILURE; + } +#ifndef USE_CUDA + // For CudaImage, GetBufferPointer() has side effects on CudaDataManager + // that prevent a clean nullptr check, so it is skipped for CUDA. + if (metadata->GetOutput()->GetBufferPointer() != nullptr) + { + std::cerr << "Case 3 failed: expected a null output buffer" << std::endl; + return EXIT_FAILURE; + } +#endif + + // ===== Case 4: size-1 dimension (contiguous) -> zero-copy view ===== + ImageType::Pointer singleton = CreateGradientImage(itk::MakeSize(4, 1, 6), itk::MakeIndex(0, 0, 0)); + + region.SetIndex(itk::MakeIndex(0, 0, 1)); + region.SetSize(itk::MakeSize(4, 1, 2)); + + auto viewSize1 = ViewFilterType::New(); + viewSize1->SetInput(singleton); + viewSize1->SetExtractionRegion(region); + TRY_AND_EXIT_ON_ITK_EXCEPTION(viewSize1->Update()) + + if (!viewSize1->GetIsContiguous()) + { + std::cerr << "Case 4 failed: region should be contiguous" << std::endl; + return EXIT_FAILURE; + } + // Row-major strides for (4,1,6) are 1, 4, 4, so offset = 1*4 = 4. + offset = viewSize1->GetOutput()->GetBufferPointer() - singleton->GetBufferPointer(); + if (offset != 4) + { + std::cerr << "Case 4 failed: expected view offset 4, got " << offset << std::endl; + return EXIT_FAILURE; + } + ImageType::Pointer reference4 = CreateGradientImage(itk::MakeSize(4, 1, 2), itk::MakeIndex(0, 0, 1)); + CheckImageQuality(viewSize1->GetOutput(), reference4, 0.001, 100, 432.); + + // ===== Case 5: size-1 dimension (non-contiguous) -> copy ===== + region.SetIndex(itk::MakeIndex(2, 0, 2)); + region.SetSize(itk::MakeSize(1, 5, 3)); + + auto copySize1 = ViewFilterType::New(); + copySize1->SetInput(input); + copySize1->SetExtractionRegion(region); + TRY_AND_EXIT_ON_ITK_EXCEPTION(copySize1->Update()) + + if (copySize1->GetIsContiguous()) + { + std::cerr << "Case 5 failed: region should NOT be contiguous" << std::endl; + return EXIT_FAILURE; + } + outputPtr = copySize1->GetOutput()->GetBufferPointer(); + if (outputPtr >= inputPtr && outputPtr < inputPtr + input->GetLargestPossibleRegion().GetNumberOfPixels()) + { + std::cerr << "Case 5 failed: copy must not share the input buffer" << std::endl; + return EXIT_FAILURE; + } + ImageType::Pointer reference5 = CreateGradientImage(itk::MakeSize(1, 5, 3), itk::MakeIndex(2, 0, 2)); + CheckImageQuality(copySize1->GetOutput(), reference5, 0.001, 100, 432.); + + std::cout << "\n\nTest PASSED! " << std::endl; + return EXIT_SUCCESS; +}