Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions include/rtkExtractImageSubRegion.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*=========================================================================
*
* 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 rtkExtractImageSubRegion_h
#define rtkExtractImageSubRegion_h

#include <itkImage.h>
#include <itkImageRegion.h>
#include <itkExtractImageFilter.h>

namespace rtk
{

/** \class ExtractImageSubRegion
* \brief Create an image that is a view of a sub-region of another image,
* without copying pixel data when the region is contiguous.
*
* This is a lightweight alternative to itk::ExtractImageFilter for the case
* where input and output image types are the same dimension (no dimension
* collapse). It avoids the overhead of the filter pipeline machinery by
* directly creating an image that shares the same pixel buffer as the input,
* with adjusted metadata (origin, region).
*
* The zero-copy optimization only applies when the extraction region is
* contiguous in memory, i.e. all dimensions except the last span the full
* input extent. When the region is not contiguous, falls back to
* itk::ExtractImageFilter.
*
* When the input buffer is not yet allocated (e.g., during
* GenerateOutputInformation), the output image is created with the correct
* metadata only. When the buffer is available (e.g., during GenerateData),
* the pixel buffer is shared via SetImportPointer for zero-copy access.
*
* Warning: since the output shares the input's pixel buffer, downstream
* filters that operate in-place (InPlaceOn) will corrupt the source data.
* Callers must ensure that no in-place filter modifies this image's buffer.
*
* This is useful in mini-pipelines where a sub-stack of projections is
* repeatedly extracted from a projection stack (e.g., FDK, SART, OSEM).
*
* \author Axel Garcia
*
* \ingroup RTK
*/
/** Check if a sub-region is contiguous in the input buffer (zero-copy possible).
* True when all non-last dimensions match the input exactly. */
template <typename TImage>
bool
IsContiguousSubRegion(const TImage * input, const itk::ImageRegion<TImage::ImageDimension> & region)
{
constexpr unsigned int Dimension = TImage::ImageDimension;
const auto & inputRegion = input->GetLargestPossibleRegion();
for (unsigned int d = 0; d < Dimension - 1; ++d)
{
if (region.GetIndex()[d] != inputRegion.GetIndex()[d] || region.GetSize()[d] != inputRegion.GetSize()[d])
return false;
}
return true;
}

template <typename TImage>
typename TImage::Pointer
ExtractImageSubRegion(const TImage * input, const itk::ImageRegion<TImage::ImageDimension> & extractionRegion)
{
constexpr unsigned int Dimension = TImage::ImageDimension;
using PixelType = typename TImage::PixelType;
using RegionType = itk::ImageRegion<Dimension>;
using SizeType = itk::Size<Dimension>;
using IndexType = itk::Index<Dimension>;
using SpacingType = typename TImage::SpacingType;
using PointType = typename TImage::PointType;
using DirectionType = typename TImage::DirectionType;

const RegionType & inputRegion = input->GetLargestPossibleRegion();
const IndexType & inputIndex = inputRegion.GetIndex();

if (!IsContiguousSubRegion(input, extractionRegion))
{
using ExtractFilterType = itk::ExtractImageFilter<TImage, TImage>;
typename ExtractFilterType::Pointer extractFilter = ExtractFilterType::New();
extractFilter->SetInput(input);
extractFilter->SetExtractionRegion(extractionRegion);
extractFilter->SetDirectionCollapseToSubmatrix();
extractFilter->Update();
return extractFilter->GetOutput();
}

const SpacingType & spacing = input->GetSpacing();
const PointType & inputOrigin = input->GetOrigin();
const DirectionType & direction = input->GetDirection();

// Create output with correct metadata (skips Allocate for CudaImage).
typename TImage::Pointer output = TImage::New();
output->SetRegions(extractionRegion);
output->SetSpacing(spacing);
output->SetOrigin(inputOrigin);
output->SetDirection(direction);

// If the input buffer is available, share it (zero-copy).
// Otherwise, return a metadata-only image.
if (input->GetBufferPointer())
{
const IndexType & extractIndex = extractionRegion.GetIndex();

// Pixels per slice: product of all input sizes except the last.
typename SizeType::SizeValueType sliceSize = 1;
for (unsigned int d = 0; d < Dimension - 1; ++d)
sliceSize *= inputRegion.GetSize()[d];

const long sliceOffset = extractIndex[Dimension - 1] - inputIndex[Dimension - 1];
const PixelType * bufferPtr = input->GetBufferPointer() + sliceOffset * sliceSize;

const typename SizeType::SizeValueType numPixels = extractionRegion.GetNumberOfPixels();
output->GetPixelContainer()->SetImportPointer(const_cast<PixelType *>(bufferPtr), 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());
}

return output;
}

} // namespace rtk

#endif // rtkExtractImageSubRegion_h
15 changes: 6 additions & 9 deletions include/rtkFDKConeBeamReconstructionFilter.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@
#include "rtkConfiguration.h"
#include "rtkFDKBackProjectionImageFilter.h"
#include "rtkFFTRampImageFilter.h"

#include <itkExtractImageFilter.h>
#include "rtkExtractImageSubRegion.h"

namespace rtk
{
Expand All @@ -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 {
Expand Down Expand Up @@ -76,7 +75,6 @@ class ITK_TEMPLATE_EXPORT FDKConeBeamReconstructionFilter : public itk::InPlaceI
using OutputImageType = TOutputImage;

/** Typedefs of each subfilter of this composite filter */
using ExtractFilterType = itk::ExtractImageFilter<InputImageType, OutputImageType>;
using WeightFilterType = rtk::FDKWeightProjectionFilter<InputImageType, OutputImageType>;
using RampFilterType = rtk::FFTRampImageFilter<OutputImageType, OutputImageType, TFFTPrecision>;
using BackProjectionFilterType = rtk::FDKBackProjectionImageFilter<OutputImageType, OutputImageType>;
Expand Down Expand Up @@ -142,10 +140,9 @@ class ITK_TEMPLATE_EXPORT FDKConeBeamReconstructionFilter : public itk::InPlaceI
{}

/** Pointers to each subfilter of this composite filter */
typename ExtractFilterType::Pointer m_ExtractFilter;
typename WeightFilterType::Pointer m_WeightFilter;
typename RampFilterType::Pointer m_RampFilter;
BackProjectionFilterPointer m_BackProjectionFilter;
typename WeightFilterType::Pointer m_WeightFilter;
typename RampFilterType::Pointer m_RampFilter;
BackProjectionFilterPointer m_BackProjectionFilter;

private:
/** Number of projections processed at a time. */
Expand Down
45 changes: 27 additions & 18 deletions include/rtkFDKConeBeamReconstructionFilter.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,14 @@ FDKConeBeamReconstructionFilter<TInputImage, TOutputImage, TFFTPrecision>::FDKCo
this->SetNumberOfRequiredInputs(2);

// Create each filter of the composite filter
m_ExtractFilter = ExtractFilterType::New();
m_WeightFilter = WeightFilterType::New();
m_RampFilter = RampFilterType::New();
this->SetBackProjectionFilter(BackProjectionFilterType::New());

// Permanent internal connections
m_WeightFilter->SetInput(m_ExtractFilter->GetOutput());
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
Expand Down Expand Up @@ -76,9 +73,11 @@ FDKConeBeamReconstructionFilter<TInputImage, TOutputImage, TFFTPrecision>::Gener
// SR: is this useful?
m_BackProjectionFilter->SetInput(0, this->GetInput(0));
m_BackProjectionFilter->SetInPlace(this->GetInPlace());
m_ExtractFilter->SetInput(this->GetInput(1));
m_BackProjectionFilter->GetOutput()->SetRequestedRegion(this->GetOutput()->GetRequestedRegion());
m_BackProjectionFilter->GetOutput()->PropagateRequestedRegion();

typename Superclass::InputImagePointer inputPtr1 = const_cast<TInputImage *>(this->GetInput(1));
inputPtr1->SetRequestedRegion(this->GetInput(1)->GetLargestPossibleRegion());
}

template <class TInputImage, class TOutputImage, class TFFTPrecision>
Expand All @@ -87,21 +86,29 @@ FDKConeBeamReconstructionFilter<TInputImage, TOutputImage, TFFTPrecision>::Gener
{
const unsigned int Dimension = this->InputImageDimension;

// Trigger upstream update, filters like DisplacedDetector can change regions.
typename Superclass::InputImagePointer inputPtr1 = const_cast<TInputImage *>(this->GetInput(1));
inputPtr1->UpdateOutputInformation();

m_WeightFilter->SetGeometry(m_Geometry);
m_BackProjectionFilter->SetGeometry(m_Geometry);

// 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->SetExtractionRegion(projRegion);

// Create a zero-copy view of the first sub-stack
typename InputImageType::Pointer subStack = rtk::ExtractImageSubRegion(this->GetInput(1), projRegion);
m_WeightFilter->SetInput(subStack);
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
Expand All @@ -117,13 +124,14 @@ FDKConeBeamReconstructionFilter<TInputImage, TOutputImage, TFFTPrecision>::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 Superclass::InputImagePointer inputPtr1 = const_cast<TInputImage *>(this->GetInput(1));
inputPtr1->Update();

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);
Expand All @@ -133,22 +141,23 @@ FDKConeBeamReconstructionFilter<TInputImage, TOutputImage, TFFTPrecision>::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));
typename InputImageType::Pointer subStack = rtk::ExtractImageSubRegion(this->GetInput(1), subsetRegion);
m_WeightFilter->SetInput(subStack);
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();
}

Expand Down
5 changes: 1 addition & 4 deletions include/rtkFDKVarianceReconstructionFilter.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@
#include "rtkConfiguration.h"
#include "rtkFDKBackProjectionImageFilter.h"
#include "rtkFFTVarianceRampImageFilter.h"

#include <itkExtractImageFilter.h>
#include "rtkExtractImageSubRegion.h"

namespace rtk
{
Expand Down Expand Up @@ -68,7 +67,6 @@ class ITK_TEMPLATE_EXPORT FDKVarianceReconstructionFilter : public itk::InPlaceI
using OutputImageType = TOutputImage;

/** Typedefs of each subfilter of this composite filter */
using ExtractFilterType = itk::ExtractImageFilter<InputImageType, OutputImageType>;
using WeightFilterType = rtk::FDKWeightProjectionFilter<InputImageType, OutputImageType>;
using VarianceRampFilterType = rtk::FFTVarianceRampImageFilter<OutputImageType, OutputImageType, TFFTPrecision>;
using BackProjectionFilterType = rtk::FDKBackProjectionImageFilter<OutputImageType, OutputImageType>;
Expand Down Expand Up @@ -127,7 +125,6 @@ class ITK_TEMPLATE_EXPORT FDKVarianceReconstructionFilter : public itk::InPlaceI
{}

/** Pointers to each subfilter of this composite filter */
typename ExtractFilterType::Pointer m_ExtractFilter;
typename WeightFilterType::Pointer m_WeightFilter1;
typename WeightFilterType::Pointer m_WeightFilter2;
typename VarianceRampFilterType::Pointer m_VarianceRampFilter;
Expand Down
Loading
Loading