From f878f46159694708ab99e46fbe414162c1492dba Mon Sep 17 00:00:00 2001 From: Bradley Lowekamp Date: Fri, 10 May 2019 15:30:30 -0400 Subject: [PATCH 01/16] ENH: Add base class for generic streaming processes The StreamingProcesObject provide a pipeline mechanics to update the input's pipeline with a sequence of regions and generically execute them in a StreamedGenerateData method. --- .../include/itkStreamingImageFilter.hxx | 2 +- .../include/itkStreamingProcessObject.h | 114 ++++++++ Modules/Core/Common/src/CMakeLists.txt | 1 + Modules/Core/Common/src/itkProcessObject.cxx | 2 +- .../Common/src/itkStreamingProcessObject.cxx | 265 ++++++++++++++++++ 5 files changed, 382 insertions(+), 2 deletions(-) create mode 100644 Modules/Core/Common/include/itkStreamingProcessObject.h create mode 100644 Modules/Core/Common/src/itkStreamingProcessObject.cxx diff --git a/Modules/Core/Common/include/itkStreamingImageFilter.hxx b/Modules/Core/Common/include/itkStreamingImageFilter.hxx index 21f59b8c6d4..ec36c6a402d 100644 --- a/Modules/Core/Common/include/itkStreamingImageFilter.hxx +++ b/Modules/Core/Common/include/itkStreamingImageFilter.hxx @@ -72,7 +72,7 @@ StreamingImageFilter< TInputImage, TOutputImage > /** * Give the subclass a chance to indicate that it will provide - * more data then required for the output. This can happen, for + * more data than required for the output. This can happen, for * example, when a source can only produce the whole output. * Although this is being called for a specific output, the source * may need to enlarge all outputs. diff --git a/Modules/Core/Common/include/itkStreamingProcessObject.h b/Modules/Core/Common/include/itkStreamingProcessObject.h new file mode 100644 index 00000000000..83246dcb42e --- /dev/null +++ b/Modules/Core/Common/include/itkStreamingProcessObject.h @@ -0,0 +1,114 @@ +/*========================================================================= + * + * Copyright Insight Software 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 + * + * http://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 itkStreamingProcessObject_h +#define itkStreamingProcessObject_h + +#include "itkProcessObject.h" + +namespace itk +{ + +/** \class Base class interface to process data on multiple requested input chunks + * + * Streaming allows the data to be split into chunks and processed + * separately. The StreamingProcessObject class extends functionally + * to execute the primary input's pipeline multiple times over + * different requested regions. After each requested region is + * generated by the upstream pipeline the StreamedGenerateData method + * is called. + * + * \ingroup ITKSystemObjects + * \ingroup DataProcessing + * \ingroup ITKCommon + */ +class ITKCommon_EXPORT StreamingProcessObject + : public ProcessObject +{ +public: + ITK_DISALLOW_COPY_AND_ASSIGN(StreamingProcessObject); + + /** Standard class typedefs. */ + using Self = StreamingProcessObject; + using Superclass = ProcessObject; + using Pointer = SmartPointer< Self >; + using ConstPointer = SmartPointer< const Self >; + + /** Run-time type information (and related methods). */ + itkTypeMacro(StreamingProcessObject,ProcessObject); + + /** Override PropagateRequestedRegion from ProcessObject + * Since inside UpdateOutputData we iterate over streaming pieces + * we don't need to proapage up the pipeline + */ + virtual void PropagateRequestedRegion(DataObject *output) override; + + virtual void GenerateData( void ) override; + + /** Override UpdateOutputData() from ProcessObject to divide upstream + * updates into pieces. */ + virtual void UpdateOutputData(DataObject *output) override; + + /** The current requested region number during execution. The value + * -1, is used when the pipeline is not currently being updated. */ + virtual int GetCurrentRequestNumber( ) const; + + virtual void ResetPipeline() override; + +protected: + StreamingProcessObject( ); + ~StreamingProcessObject( ) override; + + void PrintSelf(std::ostream& os, Indent indent) const override; + + /** \brief Return the actual number of regions to request upstream. + * + * This method can be overloaded to return one, when a derived + * filter is unable stream. + */ + virtual unsigned int GetNumberOfInputRequestedRegions( void ) = 0; + + + /** \brief For each streamed region, propagate request to all inputs. + * + * Derived classes should overload this method to compute regions + * splits and propagate to the particular DataObject types used for + * the input. + */ + virtual void GenerateNthInputRequestedRegion( unsigned int inputRequestedRegionNumber ) = 0; + + /** This method will be called multiple times for each requested + * region generated by the input. */ + virtual void StreamedGenerateData( unsigned int inputRequestedRegionNumber ) = 0; + + /** Called before the input's first requested region is set or + * updated. + */ + virtual void BeforeStreamedGenerateData( void ); + + /** Called after all requested regions have been process. + */ + virtual void AfterStreamedGenerateData( void ); + +private: + + int m_CurrentRequestNumber{-1}; +}; + +} // end namespace itk + +#endif //itkStreamingProcessObject_h diff --git a/Modules/Core/Common/src/CMakeLists.txt b/Modules/Core/Common/src/CMakeLists.txt index 0f1a9d438cc..42ea54e162c 100644 --- a/Modules/Core/Common/src/CMakeLists.txt +++ b/Modules/Core/Common/src/CMakeLists.txt @@ -72,6 +72,7 @@ set(ITKCommon_SRCS itkNumericTraitsTensorPixel2.cxx itkNumericTraitsFixedArrayPixel2.cxx itkProcessObject.cxx + itkStreamingProcessObject.cxx itkSpatialOrientationAdapter.cxx itkRealTimeInterval.cxx itkOctreeNode.cxx diff --git a/Modules/Core/Common/src/itkProcessObject.cxx b/Modules/Core/Common/src/itkProcessObject.cxx index d20eced099d..ba1fb012e11 100644 --- a/Modules/Core/Common/src/itkProcessObject.cxx +++ b/Modules/Core/Common/src/itkProcessObject.cxx @@ -1565,7 +1565,7 @@ ::PropagateRequestedRegion(DataObject *output) /** * Give the subclass a chance to indicate that it will provide - * more data then required for the output. This can happen, for + * more data than required for the output. This can happen, for * example, when a source can only produce the whole output. * Although this is being called for a specific output, the source * may need to enlarge all outputs. diff --git a/Modules/Core/Common/src/itkStreamingProcessObject.cxx b/Modules/Core/Common/src/itkStreamingProcessObject.cxx new file mode 100644 index 00000000000..1e07d1b2079 --- /dev/null +++ b/Modules/Core/Common/src/itkStreamingProcessObject.cxx @@ -0,0 +1,265 @@ +/*========================================================================= + * + * Copyright Insight Software 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 + * + * http://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. + * + *=========================================================================*/ + +#include "itkStreamingProcessObject.h" + +namespace itk +{ + + +void StreamingProcessObject::GenerateData( void ) +{ + // The m_Updating flag used in StreamingProcessObject::UpdateOutputData + // should prevent recursive execution. + + this->BeforeStreamedGenerateData(); + + + // + // Determine number of pieces to divide the input. This will be the + // minimum of what the user specified via SetNumberOfStreamDivisions() + // and what the Splitter thinks is a reasonable value. + // + unsigned int numberOfInputRequestRegion = this->GetNumberOfInputRequestedRegions(); + + // + // Loop over the number of pieces, execute the upstream pipeline on each + // piece, and execute StreamedGenerateData on each region + // + for (unsigned int piece = 0; piece < numberOfInputRequestRegion && !this->GetAbortGenerateData(); piece++) + { + this->m_CurrentRequestNumber = piece; + + this->GenerateNthInputRequestedRegion( piece ); + + // + // Now that we know the input requested region, propagate this + // through all the inputs. + // ; + for (auto &inputName: this->GetInputNames()) + { + if (this->GetInput(inputName)) + { + this->GetInput(inputName)->PropagateRequestedRegion(); + } + } + + // + // Propagate the update call - make sure everything we + // might rely on is up-to-date + // Must call PropagateRequestedRegion before UpdateOutputData if multiple + // inputs since they may lead back to the same data object. + m_Updating = true; + for (auto &inputName: this->GetInputNames()) + { + if (this->GetInput(inputName)) + { + if ( inputName != this->GetPrimaryInputName() && this->GetNumberOfInputs() > 1) + { + this->GetInput(inputName)->PropagateRequestedRegion(); + } + this->GetInput(inputName)->UpdateOutputData(); + } + } + + // + try + { + this->StreamedGenerateData( piece ); + this->UpdateProgress( float( piece ) / numberOfInputRequestRegion); + } + catch( ProcessAborted & excp ) + { + this->InvokeEvent( AbortEvent() ); + this->ResetPipeline(); + this->RestoreInputReleaseDataFlags(); + throw excp; + } + catch( ... ) + { + this->ResetPipeline(); + this->RestoreInputReleaseDataFlags(); + throw; + } + } + + + this->AfterStreamedGenerateData(); +} + + +void StreamingProcessObject::UpdateOutputData(DataObject *itkNotUsed(output)) +{ + + // + //prevent chasing our tail + // + if (this->m_Updating) + { + return; + } + + + // + // Prepare all the outputs. This may deallocate previous bulk data. + // + this->PrepareOutputs(); + + /* + * Cache the state of any ReleaseDataFlag's on the inputs. While the + * filter is executing, we need to set the ReleaseDataFlag's on the + * inputs to false in case the current filter is implemented using a + * mini-pipeline (which will try to release the inputs). After the + * filter finishes, we restore the state of the ReleaseDataFlag's + * before the call to ReleaseInputs(). + */ + this->CacheInputReleaseDataFlags(); + + /* + * Make sure we have the necessary inputs + */ + unsigned int ninputs = this->GetNumberOfValidRequiredInputs(); + if (ninputs < this->GetNumberOfRequiredInputs()) + { + itkExceptionMacro(<< "At least " << static_cast( this->GetNumberOfRequiredInputs() ) << " inputs are required but only " << ninputs << " are specified."); + return; + } + + this->SetAbortGenerateData( false ); + this->SetProgress(0.0); + this->m_Updating = true; + + /* + * Tell all Observers that the filter is starting + */ + this->InvokeEvent( StartEvent() ); + + this->Self::GenerateData( ); + /* + * If we ended due to aborting, push the progress up to 1.0 (since + * it probably didn't end there) + */ + if ( this->GetAbortGenerateData() ) + { + this->UpdateProgress(1.0); + } + + // Notify end event observers + this->InvokeEvent( EndEvent() ); + + + /* + * Now we have to mark the data as up to data. + */ + for (auto &outputName : this->GetOutputNames()) + { + if (this->GetOutput(outputName)) + { + this->GetOutput(outputName)->DataHasBeenGenerated(); + } + } + + /* DO NOT Restore the state of any input ReleaseDataFlags + */ + //this->RestoreInputReleaseDataFlags(); + + /** + * Release any inputs if marked for release + */ + this->ReleaseInputs(); + + // Mark that we are no longer updating the data in this filter + this->m_Updating = false; + +} + + +int StreamingProcessObject::GetCurrentRequestNumber( ) const +{ + return m_CurrentRequestNumber; +} + + +void StreamingProcessObject::ResetPipeline() +{ + Superclass::ResetPipeline(); + m_CurrentRequestNumber = -1; +} + + +StreamingProcessObject::StreamingProcessObject( void ) = default; + + +StreamingProcessObject::~StreamingProcessObject( ) = default; + + +void StreamingProcessObject::PrintSelf(std::ostream& os, Indent indent) const +{ + Superclass::PrintSelf(os,indent); + + os << indent << "Current Request Number: " << this->m_CurrentRequestNumber << std::endl; +} + + +void StreamingProcessObject::PropagateRequestedRegion(DataObject *output) +{ + + /** + * check flag to avoid executing forever if there is a loop + */ + if (this->m_Updating) + { + return; + } + + /** + * Give the subclass a chance to indicate that it will provide + * more data than required for the output. This can happen, for + * example, when a source can only produce the whole output. + * Although this is being called for a specific output, the source + * may need to enlarge all outputs. + */ + this->EnlargeOutputRequestedRegion( output ); + + + /** + * Give the subclass a chance to define how to set the requested + * regions for each of its outputs, given this output's requested + * region. The default implementation is to make all the output + * requested regions the same. A subclass may need to override this + * method if each output is a different resolution. + */ + this->GenerateOutputRequestedRegion( output ); + + // we don't call GenerateInputRequestedRegion since the requested + // regions are managed when the pipeline is execute + + // we don't call inputs PropagateRequestedRegion either + // because the pipeline managed later +} + + +void StreamingProcessObject::BeforeStreamedGenerateData( void ) +{ +} + +void StreamingProcessObject::AfterStreamedGenerateData( void ) +{ +} + +} // end namespace itk From ac08bc122d925087e16d282f01a7ebff6cdad6b6 Mon Sep 17 00:00:00 2001 From: Bradley Lowekamp Date: Fri, 10 May 2019 15:33:39 -0400 Subject: [PATCH 02/16] ENH: Add ImageSink base filter class Adding the ImageSink base class which provides support for streaming and multi-threading on an input image. Generally this is used when transforming an image into something like computing statistics, or a RLE of an image. --- Modules/Core/Common/include/itkImageSink.h | 143 +++++++++++++ Modules/Core/Common/include/itkImageSink.hxx | 202 +++++++++++++++++++ 2 files changed, 345 insertions(+) create mode 100644 Modules/Core/Common/include/itkImageSink.h create mode 100644 Modules/Core/Common/include/itkImageSink.hxx diff --git a/Modules/Core/Common/include/itkImageSink.h b/Modules/Core/Common/include/itkImageSink.h new file mode 100644 index 00000000000..b97a67a89ce --- /dev/null +++ b/Modules/Core/Common/include/itkImageSink.h @@ -0,0 +1,143 @@ +/*========================================================================= + * + * Copyright Insight Software 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 + * + * http://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 itkImageSink_h +#define itkImageSink_h + +#include "itkStreamingProcessObject.h" +#include "itkImage.h" +#include "itkImageRegionSplitterBase.h" +#include "itkImageRegionSplitterSlowDimension.h" + +namespace itk +{ + +/** \class ImageSink + * + * ImageSink is the base class for process objects which consume image + * data. This class defaults to having at least one input of the + * templated image type. The framework enables derived algorithms to + * stream the input image as it's being consumed by the algorithm. + * + * The framework provides multi-threading of the streamed image regions. + * The input image's pipeline is updated multiple times with the + * streaming requested regions, then the fulfilled requested region + * are split again for multi-threading. + * + * By default, the NumberOfStreamDivisions is 1 (no streaming). + * Derived implementations must change the access specification for + * this method to be public to expose the streaming feature. + * + * + * \ingroup ITKSystemObjects + * \ingroup DataProcessing + * + * \ingroup ITKCommon +**/ +template +class ImageSink + : public StreamingProcessObject +{ +public: + ITK_DISALLOW_COPY_AND_ASSIGN(ImageSink); + + /** Standard class type aliases. */ + using Self = ImageSink; + using Superclass = StreamingProcessObject; + using Pointer = SmartPointer< Self >; + using ConstPointer = SmartPointer< const Self >; + + /** Run-time type information (and related methods). */ + itkTypeMacro( ImageSink, StreamingProcessObject ); + + /** Smart Pointer type to a DataObject. */ + using DataObjectPointer = DataObject::Pointer; + + /** Some convenient type alias. */ + using InputImageType = TInputImage; + using InputImagePointer = typename InputImageType::Pointer; + using InputImageRegionType = typename InputImageType::RegionType; + using InputImagePixelType = typename InputImageType::PixelType; + + /** SmartPointer to a region splitting object */ + using SplitterType = ImageRegionSplitterBase; + using RegionSplitterPointer = typename SplitterType::Pointer; + + using DataObjectIdentifierType = typename Superclass::DataObjectIdentifierType; + + /** Dimension of input images. */ + itkStaticConstMacro(InputImageDimension, unsigned int, + InputImageType::ImageDimension); + + + using Superclass::SetInput; + /** Set/Get the image input of this process object. */ + virtual void SetInput(const InputImageType *input); + + virtual const InputImageType * GetInput(void) const; + + virtual const InputImageType *GetInput(unsigned int idx) const; + + virtual const InputImageType *GetInput(const DataObjectIdentifierType & key) const; + + virtual void Update( ) override; + +protected: + ImageSink(); + ~ImageSink() = default; + + virtual void PrintSelf(std::ostream & os, Indent indent) const override; + + virtual unsigned int GetNumberOfInputRequestedRegions () override; + + virtual void GenerateNthInputRequestedRegion (unsigned int inputRequestedRegionNumber) override; + + virtual void AllocateOutputs( ) {} + + void BeforeStreamedGenerateData( ) override {this->AllocateOutputs();} + + virtual void StreamedGenerateData( unsigned int inputRequestedRegionNumber) override; + + virtual void ThreadedStreamedGenerateData( const InputImageRegionType &inputRegionForChunk ) = 0; + + + /** Set the number of pieces to divide the input. The upstream pipeline + * will be executed this many times. */ + itkSetMacro(NumberOfStreamDivisions, unsigned int); + + /** Get the number of pieces to divide the input. The upstream pipeline + * will be executed this many times. */ + itkGetConstMacro(NumberOfStreamDivisions, unsigned int); + + /** Set/Get Helper class for dividing the input into regions for + * streaming */ + itkSetObjectMacro(RegionSplitter, SplitterType); + itkGetObjectMacro(RegionSplitter, SplitterType); + + +private: + + unsigned int m_NumberOfStreamDivisions; + RegionSplitterPointer m_RegionSplitter; + InputImageRegionType m_CurrentInputRegion; +}; + +} + +#include "itkImageSink.hxx" + +#endif // itkImageSink_h diff --git a/Modules/Core/Common/include/itkImageSink.hxx b/Modules/Core/Common/include/itkImageSink.hxx new file mode 100644 index 00000000000..4616f8ddb17 --- /dev/null +++ b/Modules/Core/Common/include/itkImageSink.hxx @@ -0,0 +1,202 @@ +/*========================================================================= + * + * Copyright Insight Software 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 + * + * http://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 itkImageSink_hxx +#define itkImageSink_hxx + +#include "itkImageSink.h" +#include "itkProgressTransformer.h" + +namespace itk +{ + +template +ImageSink +::ImageSink() + : m_NumberOfStreamDivisions{1} +{ + // create default region splitter + m_RegionSplitter = ImageRegionSplitterSlowDimension::New(); + + // Modify superclass default values, can be overridden by subclasses + this->SetNumberOfRequiredInputs(1); +} + + +template +void +ImageSink +::SetInput(const InputImageType *input) +{ + // Process object is not const-correct so the const_cast is required here + this->ProcessObject::SetNthInput( 0, const_cast< InputImageType * >( input ) ); +} + + +template +const typename ImageSink::InputImageType * +ImageSink +::GetInput(void) const +{ + return itkDynamicCastInDebugMode< const TInputImage * >( this->ProcessObject::GetPrimaryInput() ); +} + + +template +const typename ImageSink::InputImageType * +ImageSink +::GetInput(unsigned int idx) const +{ + const auto * in = dynamic_cast< const TInputImage * > + ( this->ProcessObject::GetInput(idx) ); + + if ( in == nullptr && this->ProcessObject::GetInput(idx) != nullptr ) + { + itkWarningMacro (<< "Unable to convert input number " << idx << " to type " << typeid( InputImageType ).name () ); + } + return in; +} + + +template +const typename ImageSink::InputImageType * +ImageSink +::GetInput(const DataObjectIdentifierType & key) const +{ + const auto * in = dynamic_cast< const TInputImage * > + ( this->ProcessObject::GetInput(key)); + + if ( in == nullptr && this->ProcessObject::GetInput(key) != nullptr ) + { + itkWarningMacro (<< "Unable to convert input \"" << key << "\" to type " << typeid( InputImageType ).name () ); + } + return in; +} + + +template +void +ImageSink +::Update( ) +{ + this->UpdateOutputInformation(); + // if output 1, the just call it + // this->PropagateRequestedRegion( NULL ); + this->UpdateOutputData( NULL ); +} + + +template +void +ImageSink +::PrintSelf(std::ostream & os, Indent indent) const +{ + Superclass::PrintSelf( os, indent ); + os << indent << "NumberOfStreamDivisions: " << this->m_NumberOfStreamDivisions << std::endl; + os << indent << "RegionSplitter: " << this->m_RegionSplitter << std::endl; +} + + +template +unsigned int +ImageSink +::GetNumberOfInputRequestedRegions (void) +{ + const InputImageType* inputPtr = const_cast< InputImageType * >( this->GetInput() ); + InputImageRegionType inputImageRegion = inputPtr->GetLargestPossibleRegion(); + + return this->GetRegionSplitter()->GetNumberOfSplits( inputImageRegion, this->m_NumberOfStreamDivisions ); +} + + +template +void +ImageSink +::GenerateNthInputRequestedRegion (unsigned int inputRequestedRegionNumber) +{ + Superclass::GenerateInputRequestedRegion(); + + InputImageType* inputPtr = const_cast< InputImageType * >( this->GetInput() ); + InputImageRegionType inputImageRegion = inputPtr->GetLargestPossibleRegion(); + + + this->GetRegionSplitter()->GetSplit( inputRequestedRegionNumber, + this->GetNumberOfInputRequestedRegions( ), + inputImageRegion ); + m_CurrentInputRegion = inputImageRegion; + + itkDebugMacro( "Generating " << inputRequestedRegionNumber << " chunk as " << m_CurrentInputRegion ); + + + for (auto &inputName: this->GetInputNames()) + { + if ( this->ProcessObject::GetInput(inputName) ) + { + // Check whether the input is an image of the appropriate + // dimension (use ProcessObject's version of the GetInput() + // method sink it returns the input as a pointer to a + // DataObject as opposed to the subclass version which + // static_casts the input to an TInputImage). + using ImageBaseType = ImageBase< InputImageDimension >; + typename ImageBaseType::ConstPointer constInput = + dynamic_cast< ImageBaseType const * >( this->GetInput(inputName) ); + + // If not an image, skip it, and let a subclass of + // ImageToImageFilter handle this input. + if ( constInput.IsNull() ) + { + continue; + } + + // Input is an image, cast away the constness so we can set + // the requested region. + InputImagePointer input = const_cast< TInputImage * >( this->GetInput(inputName) ); + + // copy the requested region of the first input to the others + InputImageRegionType inputRegion; + input->SetRequestedRegion( m_CurrentInputRegion ); + } + } +} + + +template +void +ImageSink +::StreamedGenerateData( unsigned int inputRequestedRegionNumber ) +{ + + this->GetMultiThreader()->SetNumberOfWorkUnits( this->GetNumberOfWorkUnits() ); + + // calculate the progress range for this streamed chunk + const ThreadIdType total = this->GetNumberOfInputRequestedRegions(); + const float oldProgress = float(inputRequestedRegionNumber)/(total); + const float newProgress = float(inputRequestedRegionNumber+1)/(total); + ProgressTransformer pt( oldProgress, newProgress, this ); + + + this->GetMultiThreader()->template ParallelizeImageRegion( + this->m_CurrentInputRegion, + [this](const InputImageRegionType & inputRegionForThread) + { this->ThreadedStreamedGenerateData(inputRegionForThread); }, + pt.GetProcessObject()); + +} + +} + +#endif // itkImageSink_hxx From fdccea41c79d9f0a75bf5f93b854bf95655ab205 Mon Sep 17 00:00:00 2001 From: Bradley Lowekamp Date: Fri, 10 May 2019 15:38:34 -0400 Subject: [PATCH 03/16] ENH: Use streaming base class for LabelStatisticsImageFilter Add functionality, to compute the label statistics of labeled image in a streamed or out-of-core way. For example, from a very large segmented image the bounding boxes can be computed to determine a region of interest to load. --- .../include/itkLabelStatisticsImageFilter.h | 22 +++++++------- .../include/itkLabelStatisticsImageFilter.hxx | 29 +++---------------- 2 files changed, 14 insertions(+), 37 deletions(-) diff --git a/Modules/Filtering/ImageStatistics/include/itkLabelStatisticsImageFilter.h b/Modules/Filtering/ImageStatistics/include/itkLabelStatisticsImageFilter.h index 89ff9dedd4a..c15e9fdd52b 100644 --- a/Modules/Filtering/ImageStatistics/include/itkLabelStatisticsImageFilter.h +++ b/Modules/Filtering/ImageStatistics/include/itkLabelStatisticsImageFilter.h @@ -18,7 +18,7 @@ #ifndef itkLabelStatisticsImageFilter_h #define itkLabelStatisticsImageFilter_h -#include "itkImageToImageFilter.h" +#include "itkImageSink.h" #include "itkNumericTraits.h" #include "itkSimpleDataObjectDecorator.h" #include "itkHistogram.h" @@ -57,14 +57,14 @@ namespace itk */ template< typename TInputImage, typename TLabelImage > class ITK_TEMPLATE_EXPORT LabelStatisticsImageFilter: - public ImageToImageFilter< TInputImage, TInputImage > + public ImageSink< TInputImage > { public: ITK_DISALLOW_COPY_AND_ASSIGN(LabelStatisticsImageFilter); /** Standard Self type alias */ using Self = LabelStatisticsImageFilter; - using Superclass = ImageToImageFilter< TInputImage, TInputImage >; + using Superclass = ImageSink< TInputImage >; using Pointer = SmartPointer< Self >; using ConstPointer = SmartPointer< const Self >; @@ -322,6 +322,11 @@ class ITK_TEMPLATE_EXPORT LabelStatisticsImageFilter: void SetHistogramParameters(const int numBins, RealType lowerBound, RealType upperBound); + // Change the acces from protected to public to expose streaming option + using Superclass::SetNumberOfStreamDivisions; + using Superclass::GetNumberOfStreamDivisions; + + #ifdef ITK_USE_CONCEPT_CHECKING // Begin concept checking itkConceptMacro( InputHasNumericTraitsCheck, @@ -334,18 +339,11 @@ class ITK_TEMPLATE_EXPORT LabelStatisticsImageFilter: ~LabelStatisticsImageFilter() override = default; void PrintSelf(std::ostream & os, Indent indent) const override; - /** Pass the input through unmodified. Do this by Grafting in the - AllocateOutputs method. */ - void AllocateOutputs() override; - /** Do final mean and variance computation from data accumulated in threads. */ - void AfterThreadedGenerateData() override; - - void DynamicThreadedGenerateData( const RegionType & ) override; + void AfterStreamedGenerateData() override; - // Override since the filter produces all of its output - void EnlargeOutputRequestedRegion(DataObject *data) override; + void ThreadedStreamedGenerateData( const RegionType & ) override; private: diff --git a/Modules/Filtering/ImageStatistics/include/itkLabelStatisticsImageFilter.hxx b/Modules/Filtering/ImageStatistics/include/itkLabelStatisticsImageFilter.hxx index dd23c3ddc55..6caa0df7bbf 100644 --- a/Modules/Filtering/ImageStatistics/include/itkLabelStatisticsImageFilter.hxx +++ b/Modules/Filtering/ImageStatistics/include/itkLabelStatisticsImageFilter.hxx @@ -38,29 +38,6 @@ LabelStatisticsImageFilter< TInputImage, TLabelImage > m_ValidLabelValues.clear(); } -template< typename TInputImage, typename TLabelImage > -void -LabelStatisticsImageFilter< TInputImage, TLabelImage > -::EnlargeOutputRequestedRegion(DataObject *data) -{ - Superclass::EnlargeOutputRequestedRegion(data); - data->SetRequestedRegionToLargestPossibleRegion(); -} - -template< typename TInputImage, typename TLabelImage > -void -LabelStatisticsImageFilter< TInputImage, TLabelImage > -::AllocateOutputs() -{ - // Pass the input through as the output - InputImagePointer image = - const_cast< TInputImage * >( this->GetInput() ); - - this->GraftOutput(image); - - // Nothing that needs to be allocated for the remaining outputs -} - template< typename TInputImage, typename TLabelImage > void LabelStatisticsImageFilter< TInputImage, TLabelImage > @@ -140,8 +117,10 @@ LabelStatisticsImageFilter< TInputImage, TLabelImage > template< typename TInputImage, typename TLabelImage > void LabelStatisticsImageFilter< TInputImage, TLabelImage > -::AfterThreadedGenerateData() +::AfterStreamedGenerateData() { + Superclass::AfterStreamedGenerateData(); + // compute the remainder of the statistics for ( auto &mapValue : m_LabelStatistics ) { @@ -186,7 +165,7 @@ LabelStatisticsImageFilter< TInputImage, TLabelImage > template< typename TInputImage, typename TLabelImage > void LabelStatisticsImageFilter< TInputImage, TLabelImage > -::DynamicThreadedGenerateData(const RegionType & outputRegionForThread) +::ThreadedStreamedGenerateData(const RegionType & outputRegionForThread) { MapType localStatistics; From 29aa8e19614bcf10c53aa0a8d50ccfe14c6e2434 Mon Sep 17 00:00:00 2001 From: Bradley Lowekamp Date: Tue, 14 May 2019 14:46:21 -0400 Subject: [PATCH 04/16] ENH: Use named input in LabelStatisticsImageFilter --- .../include/itkLabelStatisticsImageFilter.h | 13 ++----------- .../include/itkLabelStatisticsImageFilter.hxx | 3 ++- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/Modules/Filtering/ImageStatistics/include/itkLabelStatisticsImageFilter.h b/Modules/Filtering/ImageStatistics/include/itkLabelStatisticsImageFilter.h index c15e9fdd52b..63efd600310 100644 --- a/Modules/Filtering/ImageStatistics/include/itkLabelStatisticsImageFilter.h +++ b/Modules/Filtering/ImageStatistics/include/itkLabelStatisticsImageFilter.h @@ -252,17 +252,8 @@ class ITK_TEMPLATE_EXPORT LabelStatisticsImageFilter: } /** Set the label image */ - void SetLabelInput(const TLabelImage *input) - { - // Process object is not const-correct so the const casting is required. - this->SetNthInput( 1, const_cast< TLabelImage * >( input ) ); - } - - /** Get the label image */ - const LabelImageType * GetLabelInput() const - { - return itkDynamicCastInDebugMode< LabelImageType * >( const_cast< DataObject * >( this->ProcessObject::GetInput(1) ) ); - } + itkSetInputMacro(LabelInput, TLabelImage); + itkGetInputMacro(LabelInput, TLabelImage); /** Does the specified label exist? Can only be called after a call * a call to Update(). */ diff --git a/Modules/Filtering/ImageStatistics/include/itkLabelStatisticsImageFilter.hxx b/Modules/Filtering/ImageStatistics/include/itkLabelStatisticsImageFilter.hxx index 6caa0df7bbf..23051477bb7 100644 --- a/Modules/Filtering/ImageStatistics/include/itkLabelStatisticsImageFilter.hxx +++ b/Modules/Filtering/ImageStatistics/include/itkLabelStatisticsImageFilter.hxx @@ -29,7 +29,8 @@ template< typename TInputImage, typename TLabelImage > LabelStatisticsImageFilter< TInputImage, TLabelImage > ::LabelStatisticsImageFilter() { - this->SetNumberOfRequiredInputs(2); + Self::AddRequiredInputName("LabelInput"); + m_UseHistograms = false; m_NumBins.SetSize(1); m_NumBins[0] = 256; From 0efe8db5868141dcb8991dc8f8e4a4e61ce704f8 Mon Sep 17 00:00:00 2001 From: Bradley Lowekamp Date: Fri, 10 May 2019 15:42:48 -0400 Subject: [PATCH 05/16] ENH: Add streaming support to StatisticsImageFilter --- .../include/itkBilateralImageFilter.hxx | 7 +-- .../include/itkNormalizeImageFilter.hxx | 2 +- .../include/itkStatisticsImageFilter.h | 26 +++++------ .../include/itkStatisticsImageFilter.hxx | 46 ++++++------------- 4 files changed, 33 insertions(+), 48 deletions(-) diff --git a/Modules/Filtering/ImageFeature/include/itkBilateralImageFilter.hxx b/Modules/Filtering/ImageFeature/include/itkBilateralImageFilter.hxx index 7518f16eea6..6d1d8b4fa6d 100644 --- a/Modules/Filtering/ImageFeature/include/itkBilateralImageFilter.hxx +++ b/Modules/Filtering/ImageFeature/include/itkBilateralImageFilter.hxx @@ -204,13 +204,14 @@ BilateralImageFilter< TInputImage, TOutputImage > // Build a lookup table for the range gaussian + auto localInput = TInputImage::New(); + localInput->Graft( this->GetInput() ); + // First, determine the min and max intensity range typename StatisticsImageFilter< TInputImage >::Pointer statistics = StatisticsImageFilter< TInputImage >::New(); - statistics->SetInput(inputImage); - statistics->GetOutput() - ->SetRequestedRegion( this->GetOutput()->GetRequestedRegion() ); + statistics->SetInput(localInput); statistics->Update(); // Now create the lookup table whose domain runs from 0.0 to diff --git a/Modules/Filtering/ImageIntensity/include/itkNormalizeImageFilter.hxx b/Modules/Filtering/ImageIntensity/include/itkNormalizeImageFilter.hxx index f2b56daf05f..dbcac84aa6f 100644 --- a/Modules/Filtering/ImageIntensity/include/itkNormalizeImageFilter.hxx +++ b/Modules/Filtering/ImageIntensity/include/itkNormalizeImageFilter.hxx @@ -72,7 +72,7 @@ NormalizeImageFilter< TInputImage, TOutputImage > // Gather statistics m_StatisticsFilter->SetInput( this->GetInput() ); - m_StatisticsFilter->GetOutput()->SetRequestedRegion( this->GetOutput()->GetRequestedRegion() ); + //m_StatisticsFilter->GetOutput()->SetRequestedRegion( this->GetOutput()->GetRequestedRegion() ); m_StatisticsFilter->Update(); // Set the parameters for Shift diff --git a/Modules/Filtering/ImageStatistics/include/itkStatisticsImageFilter.h b/Modules/Filtering/ImageStatistics/include/itkStatisticsImageFilter.h index 6f1684ca1ee..26872fee336 100644 --- a/Modules/Filtering/ImageStatistics/include/itkStatisticsImageFilter.h +++ b/Modules/Filtering/ImageStatistics/include/itkStatisticsImageFilter.h @@ -18,7 +18,7 @@ #ifndef itkStatisticsImageFilter_h #define itkStatisticsImageFilter_h -#include "itkImageToImageFilter.h" +#include "itkImageSink.h" #include "itkNumericTraits.h" #include "itkArray.h" #include "itkSimpleDataObjectDecorator.h" @@ -52,14 +52,14 @@ namespace itk */ template< typename TInputImage > class ITK_TEMPLATE_EXPORT StatisticsImageFilter: - public ImageToImageFilter< TInputImage, TInputImage > + public ImageSink< TInputImage > { public: ITK_DISALLOW_COPY_AND_ASSIGN(StatisticsImageFilter); /** Standard Self type alias */ using Self = StatisticsImageFilter; - using Superclass = ImageToImageFilter< TInputImage, TInputImage >; + using Superclass = ImageSink< TInputImage >; using Pointer = SmartPointer< Self >; using ConstPointer = SmartPointer< const Self >; @@ -139,10 +139,16 @@ class ITK_TEMPLATE_EXPORT StatisticsImageFilter: const RealObjectType * GetSumOfSquaresOutput() const; + // Change the acces from protected to public to expose streaming option + using Superclass::SetNumberOfStreamDivisions; + using Superclass::GetNumberOfStreamDivisions; + /** Make a DataObject of the correct type to be used as the specified * output. */ using DataObjectPointerArraySizeType = ProcessObject::DataObjectPointerArraySizeType; - using Superclass::MakeOutput; + + using ProcessObject::MakeOutput; + DataObjectPointer MakeOutput(DataObjectPointerArraySizeType idx) override; #ifdef ITK_USE_CONCEPT_CHECKING @@ -163,19 +169,13 @@ class ITK_TEMPLATE_EXPORT StatisticsImageFilter: void AllocateOutputs() override; /** Initialize some accumulators before the threads run. */ - void BeforeThreadedGenerateData() override; + void BeforeStreamedGenerateData() override; /** Do final mean and variance computation from data accumulated in threads. */ - void AfterThreadedGenerateData() override; - - void DynamicThreadedGenerateData( const RegionType &) override; - - // Override since the filter needs all the data for the algorithm - void GenerateInputRequestedRegion() override; + void AfterStreamedGenerateData() override; - // Override since the filter produces all of its output - void EnlargeOutputRequestedRegion(DataObject *data) override; + void ThreadedStreamedGenerateData( const RegionType &) override; private: CompensatedSummation m_ThreadSum; diff --git a/Modules/Filtering/ImageStatistics/include/itkStatisticsImageFilter.hxx b/Modules/Filtering/ImageStatistics/include/itkStatisticsImageFilter.hxx index 3b10072a349..51e930ab412 100644 --- a/Modules/Filtering/ImageStatistics/include/itkStatisticsImageFilter.hxx +++ b/Modules/Filtering/ImageStatistics/include/itkStatisticsImageFilter.hxx @@ -35,20 +35,22 @@ StatisticsImageFilter< TInputImage > { // first output is a copy of the image, DataObject created by superclass // + auto outputPointer = this->MakeOutput(0); + this->ProcessObject::SetNumberOfRequiredOutputs(1); + this->ProcessObject::SetNthOutput( 0, outputPointer ); + // allocate the data objects for the outputs which are // just decorators around pixel types for ( int i = 1; i < 3; ++i ) { - typename PixelObjectType::Pointer output = - static_cast< PixelObjectType * >( this->MakeOutput(i).GetPointer() ); + auto output = this->MakeOutput(i); this->ProcessObject::SetNthOutput( i, output.GetPointer() ); } // allocate the data objects for the outputs which are // just decorators around real types for ( int i = 3; i < 8; ++i ) { - typename RealObjectType::Pointer output = - static_cast< RealObjectType * >( this->MakeOutput(i).GetPointer() ); + auto output = this->MakeOutput(i); this->ProcessObject::SetNthOutput( i, output.GetPointer() ); } @@ -204,29 +206,6 @@ StatisticsImageFilter< TInputImage > return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(7) ); } -template< typename TInputImage > -void -StatisticsImageFilter< TInputImage > -::GenerateInputRequestedRegion() -{ - Superclass::GenerateInputRequestedRegion(); - if ( this->GetInput() ) - { - InputImagePointer image = - const_cast< typename Superclass::InputImageType * >( this->GetInput() ); - image->SetRequestedRegionToLargestPossibleRegion(); - } -} - -template< typename TInputImage > -void -StatisticsImageFilter< TInputImage > -::EnlargeOutputRequestedRegion(DataObject *data) -{ - Superclass::EnlargeOutputRequestedRegion(data); - data->SetRequestedRegionToLargestPossibleRegion(); -} - template< typename TInputImage > void StatisticsImageFilter< TInputImage > @@ -236,7 +215,8 @@ StatisticsImageFilter< TInputImage > InputImagePointer image = const_cast< TInputImage * >( this->GetInput() ); - this->GraftOutput(image); + // TODO + // this->GraftOutput(image); // Nothing that needs to be allocated for the remaining outputs } @@ -244,8 +224,10 @@ StatisticsImageFilter< TInputImage > template< typename TInputImage > void StatisticsImageFilter< TInputImage > -::BeforeThreadedGenerateData() +::BeforeStreamedGenerateData() { + Superclass::BeforeStreamedGenerateData(); + // Resize the thread temporaries m_Count = NumericTraits< SizeValueType >::ZeroValue(); m_SumOfSquares = NumericTraits< RealType >::ZeroValue(); @@ -258,8 +240,10 @@ StatisticsImageFilter< TInputImage > template< typename TInputImage > void StatisticsImageFilter< TInputImage > -::AfterThreadedGenerateData() +::AfterStreamedGenerateData() { + Superclass::AfterStreamedGenerateData(); + const SizeValueType count = m_Count; const RealType sumOfSquares(m_SumOfSquares); const PixelType minimum = m_ThreadMin; @@ -284,7 +268,7 @@ StatisticsImageFilter< TInputImage > template< typename TInputImage > void StatisticsImageFilter< TInputImage > -::DynamicThreadedGenerateData(const RegionType & regionForThread) +::ThreadedStreamedGenerateData(const RegionType & regionForThread) { CompensatedSummation sum = NumericTraits< RealType >::ZeroValue(); From f02a1de8f1077eb6f1b75636fb65f3da2c2f4bfe Mon Sep 17 00:00:00 2001 From: Bradley Lowekamp Date: Mon, 13 May 2019 14:52:30 -0400 Subject: [PATCH 06/16] ENH: Add testing for streaming with StatisticsImageFilter --- .../ImageStatistics/test/CMakeLists.txt | 6 +++- .../test/itkStatisticsImageFilterTest.cxx | 29 +++++++++++++++---- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/Modules/Filtering/ImageStatistics/test/CMakeLists.txt b/Modules/Filtering/ImageStatistics/test/CMakeLists.txt index cda8f2bf8a8..aec53140846 100644 --- a/Modules/Filtering/ImageStatistics/test/CMakeLists.txt +++ b/Modules/Filtering/ImageStatistics/test/CMakeLists.txt @@ -36,8 +36,12 @@ itkLabelOverlapMeasuresImageFilterTest.cxx CreateTestDriver(ITKImageStatistics "${ITKImageStatistics-Test_LIBRARIES}" "${ITKImageStatisticsTests}") -itk_add_test(NAME itkStatisticsImageFilterTest +itk_add_test(NAME itkStatisticsImageFilterTest_1 COMMAND ITKImageStatisticsTestDriver itkStatisticsImageFilterTest) +itk_add_test(NAME itkStatisticsImageFilterTest_2 + COMMAND ITKImageStatisticsTestDriver itkStatisticsImageFilterTest 1) +itk_add_test(NAME itkStatisticsImageFilterTest_3 + COMMAND ITKImageStatisticsTestDriver itkStatisticsImageFilterTest 63) itk_add_test(NAME itkLabelStatisticsImageFilterTest COMMAND ITKImageStatisticsTestDriver itkLabelStatisticsImageFilterTest DATA{${ITK_DATA_ROOT}/Input/peppers.png} DATA{${ITK_DATA_ROOT}/Baseline/Algorithms/OtsuMultipleThresholdsImageFilterTest.png}) diff --git a/Modules/Filtering/ImageStatistics/test/itkStatisticsImageFilterTest.cxx b/Modules/Filtering/ImageStatistics/test/itkStatisticsImageFilterTest.cxx index dcf77ec0108..b25af325027 100644 --- a/Modules/Filtering/ImageStatistics/test/itkStatisticsImageFilterTest.cxx +++ b/Modules/Filtering/ImageStatistics/test/itkStatisticsImageFilterTest.cxx @@ -24,14 +24,27 @@ #include "itkRandomImageSource.h" #include "itkSimpleFilterWatcher.h" #include "itkMath.h" +#include "itkTestingMacros.h" -int itkStatisticsImageFilterTest(int, char* [] ) + +int itkStatisticsImageFilterTest(int argc, char *argv[]) { - std::cout << "itkStatisticsImageFilterTest Start" << std::endl; + std::cout << "itkStatisticsImageFilterTest [numberOfStreamDivisions]" << std::endl; int status = 0; + + + unsigned int numberOfStreamDivisions = 1; + + if (argc > 1) + { + numberOfStreamDivisions = std::max( std::stoi( argv[1] ), 1 ); + } + using FloatImage = itk::Image; + itk::Statistics::MersenneTwisterRandomVariateGenerator::GetInstance()->SetSeed( 987 ); + FloatImage::Pointer image = FloatImage::New(); FloatImage::RegionType region; FloatImage::SizeType size; size.Fill(64); @@ -55,7 +68,9 @@ int itkStatisticsImageFilterTest(int, char* [] ) itk::SimpleFilterWatcher filterWatch(filter); filter->SetInput (image); - filter->UpdateLargestPossibleRegion(); + filter->SetNumberOfStreamDivisions( numberOfStreamDivisions ); + + TRY_EXPECT_NO_EXCEPTION(filter->Update()); if ( itk::Math::NotAlmostEquals( filter->GetMinimum(), fillValue) ) { @@ -95,7 +110,7 @@ int itkStatisticsImageFilterTest(int, char* [] ) using SourceType = itk::RandomImageSource; SourceType::Pointer source = SourceType::New(); - FloatImage::SizeValueType randomSize[3] = {17, 8, 20}; + FloatImage::SizeValueType randomSize[3] = {17, 8, 241}; source->SetSize(randomSize); float minValue = -100.0; @@ -105,7 +120,8 @@ int itkStatisticsImageFilterTest(int, char* [] ) source->SetMax( static_cast< FloatImage::PixelType >( maxValue ) ); filter->SetInput(source->GetOutput()); - filter->UpdateLargestPossibleRegion(); + filter->SetNumberOfStreamDivisions( numberOfStreamDivisions); + TRY_EXPECT_NO_EXCEPTION(filter->UpdateLargestPossibleRegion()); double expectedSigma = std::sqrt((maxValue-minValue)*(maxValue-minValue)/12.0); double epsilon = (maxValue - minValue) * .001; @@ -141,7 +157,8 @@ int itkStatisticsImageFilterTest(int, char* [] ) using DFilterType = itk::StatisticsImageFilter; DFilterType::Pointer dfilter = DFilterType::New(); dfilter->SetInput(dImage); - dfilter->UpdateLargestPossibleRegion(); + dfilter->SetNumberOfStreamDivisions( numberOfStreamDivisions ); + TRY_EXPECT_NO_EXCEPTION(dfilter->UpdateLargestPossibleRegion()); double testMean = dfilter->GetMean(); double testVariance = dfilter->GetVariance(); double diff = itk::Math::abs(testMean - knownMean); From 884fe6e013ced8c69fc18c0d8c17b42db2ed4d4d Mon Sep 17 00:00:00 2001 From: Bradley Lowekamp Date: Mon, 13 May 2019 17:56:51 -0400 Subject: [PATCH 07/16] ENH: Remove output image, use standard decorator macros Update the StatisticImageFilter to use the standard output decorator macro. Additionally, the output image is not longer grafted in the pipeline to the output. --- .../include/itkStatisticsImageFilter.h | 73 ++---- .../include/itkStatisticsImageFilter.hxx | 217 +++--------------- 2 files changed, 52 insertions(+), 238 deletions(-) diff --git a/Modules/Filtering/ImageStatistics/include/itkStatisticsImageFilter.h b/Modules/Filtering/ImageStatistics/include/itkStatisticsImageFilter.h index 26872fee336..7f4fdfd5684 100644 --- a/Modules/Filtering/ImageStatistics/include/itkStatisticsImageFilter.h +++ b/Modules/Filtering/ImageStatistics/include/itkStatisticsImageFilter.h @@ -91,53 +91,25 @@ class ITK_TEMPLATE_EXPORT StatisticsImageFilter: using PixelObjectType = SimpleDataObjectDecorator< PixelType >; /** Return the computed Minimum. */ - PixelType GetMinimum() const - { return this->GetMinimumOutput()->Get(); } - PixelObjectType * GetMinimumOutput(); - - const PixelObjectType * GetMinimumOutput() const; + itkGetDecoratedOutputMacro(Minimum, PixelType); /** Return the computed Maximum. */ - PixelType GetMaximum() const - { return this->GetMaximumOutput()->Get(); } - PixelObjectType * GetMaximumOutput(); - - const PixelObjectType * GetMaximumOutput() const; + itkGetDecoratedOutputMacro(Maximum, PixelType); /** Return the computed Mean. */ - RealType GetMean() const - { return this->GetMeanOutput()->Get(); } - RealObjectType * GetMeanOutput(); - - const RealObjectType * GetMeanOutput() const; + itkGetDecoratedOutputMacro(Mean, RealType); /** Return the computed Standard Deviation. */ - RealType GetSigma() const - { return this->GetSigmaOutput()->Get(); } - RealObjectType * GetSigmaOutput(); - - const RealObjectType * GetSigmaOutput() const; + itkGetDecoratedOutputMacro(Sigma, RealType); /** Return the computed Variance. */ - RealType GetVariance() const - { return this->GetVarianceOutput()->Get(); } - RealObjectType * GetVarianceOutput(); - - const RealObjectType * GetVarianceOutput() const; + itkGetDecoratedOutputMacro(Variance, RealType); /** Return the compute Sum. */ - RealType GetSum() const - { return this->GetSumOutput()->Get(); } - RealObjectType * GetSumOutput(); - - const RealObjectType * GetSumOutput() const; + itkGetDecoratedOutputMacro(Sum, RealType); /** Return the compute Sum of Squares. */ - RealType GetSumOfSquares() const - { return this->GetSumOfSquaresOutput()->Get(); } - RealObjectType * GetSumOfSquaresOutput(); - - const RealObjectType * GetSumOfSquaresOutput() const; + itkGetDecoratedOutputMacro(SumOfSquares, RealType); // Change the acces from protected to public to expose streaming option using Superclass::SetNumberOfStreamDivisions; @@ -145,11 +117,9 @@ class ITK_TEMPLATE_EXPORT StatisticsImageFilter: /** Make a DataObject of the correct type to be used as the specified * output. */ - using DataObjectPointerArraySizeType = ProcessObject::DataObjectPointerArraySizeType; - - using ProcessObject::MakeOutput; - - DataObjectPointer MakeOutput(DataObjectPointerArraySizeType idx) override; + using DataObjectIdentifierType = ProcessObject::DataObjectIdentifierType; + using Superclass::MakeOutput; + DataObjectPointer MakeOutput( const DataObjectIdentifierType & name ) override; #ifdef ITK_USE_CONCEPT_CHECKING // Begin concept checking @@ -163,27 +133,30 @@ class ITK_TEMPLATE_EXPORT StatisticsImageFilter: ~StatisticsImageFilter() override = default; void PrintSelf(std::ostream & os, Indent indent) const override; - /** Pass the input through unmodified. Do this by Grafting in the - * AllocateOutputs method. - */ - void AllocateOutputs() override; - /** Initialize some accumulators before the threads run. */ void BeforeStreamedGenerateData() override; - /** Do final mean and variance computation from data accumulated in threads. + /** Set outputs to computed values from all regions */ void AfterStreamedGenerateData() override; void ThreadedStreamedGenerateData( const RegionType &) override; + itkSetDecoratedOutputMacro(Minimum, PixelType); + itkSetDecoratedOutputMacro(Maximum, PixelType); + itkSetDecoratedOutputMacro(Mean, RealType); + itkSetDecoratedOutputMacro(Sigma, RealType); + itkSetDecoratedOutputMacro(Variance, RealType); + itkSetDecoratedOutputMacro(Sum, RealType); + itkSetDecoratedOutputMacro(SumOfSquares, RealType); + private: - CompensatedSummation m_ThreadSum; - CompensatedSummation m_SumOfSquares; + CompensatedSummation m_ThreadSum{1}; + CompensatedSummation m_SumOfSquares{1}; SizeValueType m_Count{1}; - PixelType m_ThreadMin; - PixelType m_ThreadMax; + PixelType m_ThreadMin{1}; + PixelType m_ThreadMax{1}; std::mutex m_Mutex; }; // end of class diff --git a/Modules/Filtering/ImageStatistics/include/itkStatisticsImageFilter.hxx b/Modules/Filtering/ImageStatistics/include/itkStatisticsImageFilter.hxx index 51e930ab412..1c8bc9f6dd6 100644 --- a/Modules/Filtering/ImageStatistics/include/itkStatisticsImageFilter.hxx +++ b/Modules/Filtering/ImageStatistics/include/itkStatisticsImageFilter.hxx @@ -28,197 +28,38 @@ namespace itk template< typename TInputImage > StatisticsImageFilter< TInputImage > ::StatisticsImageFilter() - :m_ThreadSum(1), - m_SumOfSquares(1), - m_ThreadMin(1), - m_ThreadMax(1) { - // first output is a copy of the image, DataObject created by superclass - // - auto outputPointer = this->MakeOutput(0); - this->ProcessObject::SetNumberOfRequiredOutputs(1); - this->ProcessObject::SetNthOutput( 0, outputPointer ); + this->SetNumberOfRequiredInputs(1); - // allocate the data objects for the outputs which are - // just decorators around pixel types - for ( int i = 1; i < 3; ++i ) - { - auto output = this->MakeOutput(i); - this->ProcessObject::SetNthOutput( i, output.GetPointer() ); - } - // allocate the data objects for the outputs which are - // just decorators around real types - for ( int i = 3; i < 8; ++i ) - { - auto output = this->MakeOutput(i); - this->ProcessObject::SetNthOutput( i, output.GetPointer() ); - } - - this->GetMinimumOutput()->Set( NumericTraits< PixelType >::max() ); - this->GetMaximumOutput()->Set( NumericTraits< PixelType >::NonpositiveMin() ); - this->GetMeanOutput()->Set( NumericTraits< RealType >::max() ); - this->GetSigmaOutput()->Set( NumericTraits< RealType >::max() ); - this->GetVarianceOutput()->Set( NumericTraits< RealType >::max() ); - this->GetSumOutput()->Set(NumericTraits< RealType >::ZeroValue()); - this->GetSumOfSquaresOutput()->Set(NumericTraits< RealType >::ZeroValue()); + Self::SetMinimum( NumericTraits< PixelType >::max() ); + Self::SetMaximum( NumericTraits< PixelType >::NonpositiveMin() ); + Self::SetMean( NumericTraits< RealType >::max() ); + Self::SetSigma( NumericTraits< RealType >::max() ); + Self::SetVariance( NumericTraits< RealType >::max() ); + Self::SetSum( NumericTraits< RealType >::ZeroValue() ); + Self::SetSumOfSquares( NumericTraits< RealType >::ZeroValue() ); + Self::SetPrimaryOutputName("Minimum"); } template< typename TInputImage > DataObject::Pointer StatisticsImageFilter< TInputImage > -::MakeOutput(DataObjectPointerArraySizeType output) +::MakeOutput( const DataObjectIdentifierType & name) { - switch ( output ) + if (name == "Minimum" || + name == "Maximum" ) { - case 0: - return TInputImage::New().GetPointer(); - break; - case 1: - return PixelObjectType::New().GetPointer(); - break; - case 2: - return PixelObjectType::New().GetPointer(); - break; - case 3: - case 4: - case 5: - case 6: - case 7: - return RealObjectType::New().GetPointer(); - break; - default: - // might as well make an image - return TInputImage::New().GetPointer(); - break; + return PixelObjectType::New(); } -} - -template< typename TInputImage > -typename StatisticsImageFilter< TInputImage >::PixelObjectType * -StatisticsImageFilter< TInputImage > -::GetMinimumOutput() -{ - return static_cast< PixelObjectType * >( this->ProcessObject::GetOutput(1) ); -} - -template< typename TInputImage > -const typename StatisticsImageFilter< TInputImage >::PixelObjectType * -StatisticsImageFilter< TInputImage > -::GetMinimumOutput() const -{ - return static_cast< const PixelObjectType * >( this->ProcessObject::GetOutput(1) ); -} - -template< typename TInputImage > -typename StatisticsImageFilter< TInputImage >::PixelObjectType * -StatisticsImageFilter< TInputImage > -::GetMaximumOutput() -{ - return static_cast< PixelObjectType * >( this->ProcessObject::GetOutput(2) ); -} - -template< typename TInputImage > -const typename StatisticsImageFilter< TInputImage >::PixelObjectType * -StatisticsImageFilter< TInputImage > -::GetMaximumOutput() const -{ - return static_cast< const PixelObjectType * >( this->ProcessObject::GetOutput(2) ); -} - -template< typename TInputImage > -typename StatisticsImageFilter< TInputImage >::RealObjectType * -StatisticsImageFilter< TInputImage > -::GetMeanOutput() -{ - return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(3) ); -} - -template< typename TInputImage > -const typename StatisticsImageFilter< TInputImage >::RealObjectType * -StatisticsImageFilter< TInputImage > -::GetMeanOutput() const -{ - return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(3) ); -} - -template< typename TInputImage > -typename StatisticsImageFilter< TInputImage >::RealObjectType * -StatisticsImageFilter< TInputImage > -::GetSigmaOutput() -{ - return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(4) ); -} - -template< typename TInputImage > -const typename StatisticsImageFilter< TInputImage >::RealObjectType * -StatisticsImageFilter< TInputImage > -::GetSigmaOutput() const -{ - return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(4) ); -} - -template< typename TInputImage > -typename StatisticsImageFilter< TInputImage >::RealObjectType * -StatisticsImageFilter< TInputImage > -::GetVarianceOutput() -{ - return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(5) ); -} - -template< typename TInputImage > -const typename StatisticsImageFilter< TInputImage >::RealObjectType * -StatisticsImageFilter< TInputImage > -::GetVarianceOutput() const -{ - return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(5) ); -} - -template< typename TInputImage > -typename StatisticsImageFilter< TInputImage >::RealObjectType * -StatisticsImageFilter< TInputImage > -::GetSumOutput() -{ - return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(6) ); -} - -template< typename TInputImage > -const typename StatisticsImageFilter< TInputImage >::RealObjectType * -StatisticsImageFilter< TInputImage > -::GetSumOutput() const -{ - return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(6) ); -} - - -template< typename TInputImage > -typename StatisticsImageFilter< TInputImage >::RealObjectType * -StatisticsImageFilter< TInputImage > -::GetSumOfSquaresOutput() -{ - return static_cast< RealObjectType * >( this->ProcessObject::GetOutput(7) ); -} - -template< typename TInputImage > -const typename StatisticsImageFilter< TInputImage >::RealObjectType * -StatisticsImageFilter< TInputImage > -::GetSumOfSquaresOutput() const -{ - return static_cast< const RealObjectType * >( this->ProcessObject::GetOutput(7) ); -} - -template< typename TInputImage > -void -StatisticsImageFilter< TInputImage > -::AllocateOutputs() -{ - // Pass the input through as the output - InputImagePointer image = - const_cast< TInputImage * >( this->GetInput() ); - - // TODO - // this->GraftOutput(image); - - // Nothing that needs to be allocated for the remaining outputs + if ( name == "Mean" || + name == "Sigma" || + name == "Variance" || + name == "Sum" || + name == "SumOfSquares" ) + { + return RealObjectType::New(); + } + return Superclass::MakeOutput(name); } template< typename TInputImage > @@ -256,13 +97,13 @@ StatisticsImageFilter< TInputImage > const RealType sigma = std::sqrt(variance); // Set the outputs - this->GetMinimumOutput()->Set(minimum); - this->GetMaximumOutput()->Set(maximum); - this->GetMeanOutput()->Set(mean); - this->GetSigmaOutput()->Set(sigma); - this->GetVarianceOutput()->Set(variance); - this->GetSumOutput()->Set(sum); - this->GetSumOfSquaresOutput()->Set(sumOfSquares); + this->SetMinimum(minimum); + this->SetMaximum(maximum); + this->SetMean(mean); + this->SetSigma(sigma); + this->SetVariance(variance); + this->SetSum(sum); + this->SetSumOfSquares(sumOfSquares); } template< typename TInputImage > From cbaddfaf885862118b4c7e112892132705f2e4de Mon Sep 17 00:00:00 2001 From: Bradley Lowekamp Date: Mon, 13 May 2019 17:58:48 -0400 Subject: [PATCH 08/16] ENH: Refactor min/max filter to use ImageSink Additionally, the standard output decorator macros are used to reduce code. --- .../include/itkMinimumMaximumImageFilter.h | 41 ++---- .../include/itkMinimumMaximumImageFilter.hxx | 117 +++--------------- 2 files changed, 28 insertions(+), 130 deletions(-) diff --git a/Modules/Filtering/ImageStatistics/include/itkMinimumMaximumImageFilter.h b/Modules/Filtering/ImageStatistics/include/itkMinimumMaximumImageFilter.h index dc9aeae3099..8ee0c4070c4 100644 --- a/Modules/Filtering/ImageStatistics/include/itkMinimumMaximumImageFilter.h +++ b/Modules/Filtering/ImageStatistics/include/itkMinimumMaximumImageFilter.h @@ -18,7 +18,7 @@ #ifndef itkMinimumMaximumImageFilter_h #define itkMinimumMaximumImageFilter_h -#include "itkImageToImageFilter.h" +#include "itkImageSink.h" #include "itkSimpleDataObjectDecorator.h" #include @@ -43,18 +43,17 @@ namespace itk */ template< typename TInputImage > class ITK_TEMPLATE_EXPORT MinimumMaximumImageFilter: - public ImageToImageFilter< TInputImage, TInputImage > + public ImageSink< TInputImage > { public: ITK_DISALLOW_COPY_AND_ASSIGN(MinimumMaximumImageFilter); /** Extract dimension from input image. */ static constexpr unsigned int InputImageDimension = TInputImage::ImageDimension; - static constexpr unsigned int OutputImageDimension = TInputImage::ImageDimension; /** Standard class type aliases. */ using Self = MinimumMaximumImageFilter; - using Superclass = ImageToImageFilter< TInputImage, TInputImage >; + using Superclass = ImageSink< TInputImage >; using Pointer = SmartPointer< Self >; using ConstPointer = SmartPointer< const Self >; @@ -82,24 +81,16 @@ class ITK_TEMPLATE_EXPORT MinimumMaximumImageFilter: using PixelObjectType = SimpleDataObjectDecorator< PixelType >; /** Return the computed Minimum. */ - PixelType GetMinimum() const - { return this->GetMinimumOutput()->Get(); } - PixelObjectType * GetMinimumOutput(); - - const PixelObjectType * GetMinimumOutput() const; + itkGetDecoratedOutputMacro(Minimum, PixelType); /** Return the computed Maximum. */ - PixelType GetMaximum() const - { return this->GetMaximumOutput()->Get(); } - PixelObjectType * GetMaximumOutput(); - - const PixelObjectType * GetMaximumOutput() const; + itkGetDecoratedOutputMacro(Maximum, PixelType); /** Make a DataObject of the correct type to be used as the specified * output. */ - using DataObjectPointerArraySizeType = ProcessObject::DataObjectPointerArraySizeType; + using DataObjectIdentifierType = ProcessObject::DataObjectIdentifierType; using Superclass::MakeOutput; - DataObjectPointer MakeOutput(DataObjectPointerArraySizeType idx) override; + DataObjectPointer MakeOutput( const DataObjectIdentifierType & name ) override; #ifdef ITK_USE_CONCEPT_CHECKING // Begin concept checking @@ -117,24 +108,18 @@ class ITK_TEMPLATE_EXPORT MinimumMaximumImageFilter: ~MinimumMaximumImageFilter() override = default; void PrintSelf(std::ostream & os, Indent indent) const override; - /** Pass the input through unmodified. Do this by Grafting in the - AllocateOutputs method. */ - void AllocateOutputs() override; - - /** Initialize some accumulators before the threads run. */ - void BeforeThreadedGenerateData() override; + /** Initialize some accumulators before any chunks are processes */ + void BeforeStreamedGenerateData() override; /** Do final mean and variance computation from data accumulated in threads. */ - void AfterThreadedGenerateData() override; + void AfterStreamedGenerateData() override; - void DynamicThreadedGenerateData( const RegionType & ) override; + void ThreadedStreamedGenerateData( const RegionType & ) override; - // Override since the filter needs all the data for the algorithm - void GenerateInputRequestedRegion() override; - // Override since the filter produces all of its output - void EnlargeOutputRequestedRegion(DataObject *data) override; + itkSetDecoratedOutputMacro(Minimum, PixelType); + itkSetDecoratedOutputMacro(Maximum, PixelType); private: PixelType m_ThreadMin; diff --git a/Modules/Filtering/ImageStatistics/include/itkMinimumMaximumImageFilter.hxx b/Modules/Filtering/ImageStatistics/include/itkMinimumMaximumImageFilter.hxx index 29710ac2550..f399255e8aa 100644 --- a/Modules/Filtering/ImageStatistics/include/itkMinimumMaximumImageFilter.hxx +++ b/Modules/Filtering/ImageStatistics/include/itkMinimumMaximumImageFilter.hxx @@ -31,119 +31,30 @@ template< typename TInputImage > MinimumMaximumImageFilter< TInputImage > ::MinimumMaximumImageFilter() { - this->SetNumberOfRequiredOutputs(3); - // first output is a copy of the image, DataObject created by - // superclass - // - // allocate the data objects for the remaining outputs which are - // just decorators around floating point types - for ( int i = 1; i < 3; ++i ) - { - typename PixelObjectType::Pointer output = - static_cast< PixelObjectType * >( this->MakeOutput(i).GetPointer() ); - this->ProcessObject::SetNthOutput( i, output.GetPointer() ); - } - - this->GetMinimumOutput()->Set( NumericTraits< PixelType >::max() ); - this->GetMaximumOutput()->Set( NumericTraits< PixelType >::NonpositiveMin() ); - + Self::SetMinimum( NumericTraits< PixelType >::max() ); + Self::SetMaximum( NumericTraits< PixelType >::NonpositiveMin() ); + Self::SetPrimaryOutputName("Minimum"); } template< typename TInputImage > DataObject::Pointer MinimumMaximumImageFilter< TInputImage > -::MakeOutput(DataObjectPointerArraySizeType output) -{ - switch ( output ) - { - case 0: - return TInputImage::New().GetPointer(); - break; - case 1: - case 2: - return PixelObjectType::New().GetPointer(); - break; - default: - // might as well make an image - return TInputImage::New().GetPointer(); - break; - } -} - -template< typename TInputImage > -typename MinimumMaximumImageFilter< TInputImage >::PixelObjectType * -MinimumMaximumImageFilter< TInputImage > -::GetMinimumOutput() -{ - return static_cast< PixelObjectType * >( this->ProcessObject::GetOutput(1) ); -} - -template< typename TInputImage > -const typename MinimumMaximumImageFilter< TInputImage >::PixelObjectType * -MinimumMaximumImageFilter< TInputImage > -::GetMinimumOutput() const -{ - return static_cast< const PixelObjectType * >( this->ProcessObject::GetOutput(1) ); -} - -template< typename TInputImage > -typename MinimumMaximumImageFilter< TInputImage >::PixelObjectType * -MinimumMaximumImageFilter< TInputImage > -::GetMaximumOutput() -{ - return static_cast< PixelObjectType * >( this->ProcessObject::GetOutput(2) ); -} - -template< typename TInputImage > -const typename MinimumMaximumImageFilter< TInputImage >::PixelObjectType * -MinimumMaximumImageFilter< TInputImage > -::GetMaximumOutput() const -{ - return static_cast< const PixelObjectType * >( this->ProcessObject::GetOutput(2) ); -} - -template< typename TInputImage > -void -MinimumMaximumImageFilter< TInputImage > -::GenerateInputRequestedRegion() +::MakeOutput(const DataObjectIdentifierType & name) { - Superclass::GenerateInputRequestedRegion(); - if ( this->GetInput() ) + if ( name == "Minimum" || name == "Maximum" ) { - InputImagePointer image = - const_cast< typename Superclass::InputImageType * >( this->GetInput() ); - image->SetRequestedRegionToLargestPossibleRegion(); + return PixelObjectType::New(); } + return Superclass::MakeOutput(name); } template< typename TInputImage > void MinimumMaximumImageFilter< TInputImage > -::EnlargeOutputRequestedRegion(DataObject *data) +::BeforeStreamedGenerateData() { - Superclass::EnlargeOutputRequestedRegion(data); - data->SetRequestedRegionToLargestPossibleRegion(); -} + Superclass::BeforeStreamedGenerateData(); -template< typename TInputImage > -void -MinimumMaximumImageFilter< TInputImage > -::AllocateOutputs() -{ - // Pass the input through as the output - InputImagePointer image = - const_cast< TInputImage * >( this->GetInput() ); - - this->GraftOutput(image); - - // Nothing that needs to be allocated for the remaining outputs -} - -template< typename TInputImage > -void -MinimumMaximumImageFilter< TInputImage > -::BeforeThreadedGenerateData() -{ m_ThreadMin = NumericTraits< PixelType >::max(); m_ThreadMax = NumericTraits< PixelType >::NonpositiveMin(); } @@ -151,16 +62,18 @@ MinimumMaximumImageFilter< TInputImage > template< typename TInputImage > void MinimumMaximumImageFilter< TInputImage > -::AfterThreadedGenerateData() +::AfterStreamedGenerateData() { - this->GetMinimumOutput()->Set(m_ThreadMin); - this->GetMaximumOutput()->Set(m_ThreadMax); + Superclass::AfterStreamedGenerateData(); + + this->SetMinimum(m_ThreadMin); + this->SetMaximum(m_ThreadMax); } template< typename TInputImage > void MinimumMaximumImageFilter< TInputImage > -::DynamicThreadedGenerateData(const RegionType & regionForThread) +::ThreadedStreamedGenerateData(const RegionType & regionForThread) { if ( regionForThread.GetNumberOfPixels() == 0 ) { From c3fcfe2016932df20e26e5531a4f7439c1502548 Mon Sep 17 00:00:00 2001 From: Bradley Lowekamp Date: Tue, 14 May 2019 11:33:54 -0400 Subject: [PATCH 09/16] ENH: Add ImageSink::VerifyInputInformation And the same change from ImageToImgeFilter to verify that input images are congruent in physical space by default. --- Modules/Core/Common/ITKKWStyleOverwrite.txt | 1 + Modules/Core/Common/include/itkImageSink.h | 49 ++++++++++- Modules/Core/Common/include/itkImageSink.hxx | 87 ++++++++++++++++++- .../Common/include/itkImageToImageFilter.h | 4 +- 4 files changed, 137 insertions(+), 4 deletions(-) diff --git a/Modules/Core/Common/ITKKWStyleOverwrite.txt b/Modules/Core/Common/ITKKWStyleOverwrite.txt index 3731bd05528..95683a5bc74 100644 --- a/Modules/Core/Common/ITKKWStyleOverwrite.txt +++ b/Modules/Core/Common/ITKKWStyleOverwrite.txt @@ -21,6 +21,7 @@ itkVectorImage\.h Template Disable itkSparseImageTest\.cxx InternalVariables Disable itkSmartPointer\.h IfNDefDefine Disable itkImageToImageFilter.\h InternalVariables Disable +itkImageSink.\h InternalVariables Disable itkTimeStamp\.cxx Namespace Disable itkObjectFactoryBase\.cxx Namespace Disable itkNeighborhood\.hxx SemicolonSpace Disable diff --git a/Modules/Core/Common/include/itkImageSink.h b/Modules/Core/Common/include/itkImageSink.h index b97a67a89ce..98ec3426845 100644 --- a/Modules/Core/Common/include/itkImageSink.h +++ b/Modules/Core/Common/include/itkImageSink.h @@ -22,6 +22,7 @@ #include "itkImage.h" #include "itkImageRegionSplitterBase.h" #include "itkImageRegionSplitterSlowDimension.h" +#include "itkImageToImageFilterCommon.h" namespace itk { @@ -50,7 +51,8 @@ namespace itk **/ template class ImageSink - : public StreamingProcessObject + : public StreamingProcessObject, + private ImageToImageFilterCommon { public: ITK_DISALLOW_COPY_AND_ASSIGN(ImageSink); @@ -96,6 +98,42 @@ class ImageSink virtual void Update( ) override; + + /** get/set the Coordinate tolerance + * This tolerance is used when comparing the space defined + * by the input images. ITK has a requirement that multiple input + * images be congruent in space by default. + */ + itkSetMacro(CoordinateTolerance,double); + itkGetConstMacro(CoordinateTolerance,double); + + /** get/set the direction tolerance + * This tolerance is used to make sure that all input + * images are oriented the same before performing the filter's + * transformations. + */ + itkSetMacro(DirectionTolerance,double); + itkGetConstMacro(DirectionTolerance,double); + + /** get/set the global default direction tolerance + * + * This value is used to initialize the DirectionTolerance upon + * class construction of \b any Image filters. This has no + * effect on currently constructed classes. + */ + using ImageToImageFilterCommon::SetGlobalDefaultDirectionTolerance; + using ImageToImageFilterCommon::GetGlobalDefaultDirectionTolerance; + + + /** get/set the global default coordinate tolerance + * + * This value is used to initialize the CoordinateTolerance upon + * class construction of \b any ImageToImage filter. This has no + * effect on currently constructed classes. + */ + using ImageToImageFilterCommon::SetGlobalDefaultCoordinateTolerance; + using ImageToImageFilterCommon::GetGlobalDefaultCoordinateTolerance; + protected: ImageSink(); ~ImageSink() = default; @@ -108,6 +146,8 @@ class ImageSink virtual void AllocateOutputs( ) {} + void VerifyInputInformation() ITKv5_CONST override; + void BeforeStreamedGenerateData( ) override {this->AllocateOutputs();} virtual void StreamedGenerateData( unsigned int inputRequestedRegionNumber) override; @@ -134,6 +174,13 @@ class ImageSink unsigned int m_NumberOfStreamDivisions; RegionSplitterPointer m_RegionSplitter; InputImageRegionType m_CurrentInputRegion; + + /** + * Tolerances for checking whether input images are defined to + * occupy the same physical space. + */ + double m_CoordinateTolerance; + double m_DirectionTolerance; }; } diff --git a/Modules/Core/Common/include/itkImageSink.hxx b/Modules/Core/Common/include/itkImageSink.hxx index 4616f8ddb17..1e11220a363 100644 --- a/Modules/Core/Common/include/itkImageSink.hxx +++ b/Modules/Core/Common/include/itkImageSink.hxx @@ -20,6 +20,7 @@ #include "itkImageSink.h" #include "itkProgressTransformer.h" +#include "itkInputDataObjectConstIterator.h" namespace itk { @@ -27,7 +28,9 @@ namespace itk template ImageSink ::ImageSink() - : m_NumberOfStreamDivisions{1} + : m_NumberOfStreamDivisions{1}, + m_CoordinateTolerance{Self::GetGlobalDefaultCoordinateTolerance()}, + m_DirectionTolerance{Self::GetGlobalDefaultDirectionTolerance()} { // create default region splitter m_RegionSplitter = ImageRegionSplitterSlowDimension::New(); @@ -108,6 +111,8 @@ ImageSink Superclass::PrintSelf( os, indent ); os << indent << "NumberOfStreamDivisions: " << this->m_NumberOfStreamDivisions << std::endl; os << indent << "RegionSplitter: " << this->m_RegionSplitter << std::endl; + os << indent << "CoordinateTolerance: " << this->m_CoordinateTolerance << std::endl; + os << indent << "DirectionTolerance: " << this->m_DirectionTolerance << std::endl; } @@ -174,6 +179,86 @@ ImageSink } +template +void +ImageSink +::VerifyInputInformation() ITKv5_CONST +{ + using ImageBaseType = const ImageBase< InputImageDimension >; + + ImageBaseType *inputPtr1 = nullptr; + InputDataObjectConstIterator it(this); + + for(; !it.IsAtEnd(); ++it ) + { + // Check whether the output is an image of the appropriate + // dimension (use ProcessObject's version of the GetInput() + // method since it returns the input as a pointer to a + // DataObject as opposed to the subclass version which + // static_casts the input to an TInputImage). + inputPtr1 = dynamic_cast< ImageBaseType * >( it.GetInput() ); + + if ( inputPtr1 ) + { + break; + } + } + + for (; !it.IsAtEnd(); ++it ) + { + auto * inputPtrN = dynamic_cast< ImageBaseType * >( it.GetInput() ); + + // Physical space computation only matters if we're using two + // images, and not an image and a constant. + if( inputPtrN ) + { + // check that the image occupy the same physical space, and that + // each index is at the same physical location + + // tolerance for origin and spacing depends on the size of pixel + // tolerance for directions a fraction of the unit cube. + const SpacePrecisionType coordinateTol + = std::abs(this->m_CoordinateTolerance * inputPtr1->GetSpacing()[0]); // use first dimension spacing + + if ( !inputPtr1->GetOrigin().GetVnlVector().is_equal(inputPtrN->GetOrigin().GetVnlVector(), coordinateTol) || + !inputPtr1->GetSpacing().GetVnlVector().is_equal(inputPtrN->GetSpacing().GetVnlVector(), coordinateTol) || + !inputPtr1->GetDirection().GetVnlMatrix().as_ref().is_equal(inputPtrN->GetDirection().GetVnlMatrix(), this->m_DirectionTolerance) ) + { + std::ostringstream originString, spacingString, directionString; + if ( !inputPtr1->GetOrigin().GetVnlVector().is_equal(inputPtrN->GetOrigin().GetVnlVector(), coordinateTol) ) + { + originString.setf( std::ios::scientific ); + originString.precision( 7 ); + originString << "InputImage Origin: " << inputPtr1->GetOrigin() + << ", InputImage" << it.GetName() << " Origin: " << inputPtrN->GetOrigin() << std::endl; + originString << "\tTolerance: " << coordinateTol << std::endl; + } + if ( !inputPtr1->GetSpacing().GetVnlVector().is_equal(inputPtrN->GetSpacing().GetVnlVector(), coordinateTol) ) + { + spacingString.setf( std::ios::scientific ); + spacingString.precision( 7 ); + spacingString << "InputImage Spacing: " << inputPtr1->GetSpacing() + << ", InputImage" << it.GetName() << " Spacing: " << inputPtrN->GetSpacing() << std::endl; + spacingString << "\tTolerance: " << coordinateTol << std::endl; + } + if ( !inputPtr1->GetDirection().GetVnlMatrix().as_ref().is_equal(inputPtrN->GetDirection().GetVnlMatrix(), this->m_DirectionTolerance) ) + { + directionString.setf( std::ios::scientific ); + directionString.precision( 7 ); + directionString << "InputImage Direction: " << inputPtr1->GetDirection() + << ", InputImage" << it.GetName() << " Direction: " << inputPtrN->GetDirection() << std::endl; + directionString << "\tTolerance: " << this->m_DirectionTolerance << std::endl; + } + itkExceptionMacro(<< "Inputs do not occupy the same physical space! " + << std::endl + << originString.str() << spacingString.str() + << directionString.str() ); + } + } + } + +} + template void ImageSink diff --git a/Modules/Core/Common/include/itkImageToImageFilter.h b/Modules/Core/Common/include/itkImageToImageFilter.h index 8b6bc71df61..95728145cb2 100644 --- a/Modules/Core/Common/include/itkImageToImageFilter.h +++ b/Modules/Core/Common/include/itkImageToImageFilter.h @@ -173,8 +173,8 @@ class ITK_TEMPLATE_EXPORT ImageToImageFilter:public ImageSource< TOutputImage >, /** get/set the Coordinate tolerance * This tolerance is used when comparing the space defined - * by the input images. With ITK 4.x a requirement has - * been added that both input images be congruent in space. + * by the input images. ITK has a requirement that multiple input + * images be congruent in space by default. */ itkSetMacro(CoordinateTolerance,double); itkGetConstMacro(CoordinateTolerance,double); From 8f66190d5eb7f9c4159e5185745cfe5b69e4bf3f Mon Sep 17 00:00:00 2001 From: Bradley Lowekamp Date: Tue, 14 May 2019 11:41:55 -0400 Subject: [PATCH 10/16] ENH: Add KWStyle override for ImageStatistics module --- Modules/Filtering/ImageStatistics/ITKKWStyleOverwrite.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Modules/Filtering/ImageStatistics/ITKKWStyleOverwrite.txt diff --git a/Modules/Filtering/ImageStatistics/ITKKWStyleOverwrite.txt b/Modules/Filtering/ImageStatistics/ITKKWStyleOverwrite.txt new file mode 100644 index 00000000000..d63f88ba943 --- /dev/null +++ b/Modules/Filtering/ImageStatistics/ITKKWStyleOverwrite.txt @@ -0,0 +1,3 @@ +itkStatisticsImageFilter\.h InternalVariables Disable +test/.*\.cxx Namespace Disable +Examples/.*\.cxx Namespace Disable From 9bb0f0cc4bc65b80461ca0027c252dc6d16440b4 Mon Sep 17 00:00:00 2001 From: Bradley Lowekamp Date: Tue, 14 May 2019 14:39:03 -0400 Subject: [PATCH 11/16] ENH: Use streaming sink base class for image to histogram filters Change the base class from ImageTransformer to ImageSink and expose SetNumberOfStreamDivisions method to enable streamed generation of Histograms. Also updated the way decorated input are created to automatically create the decorator inside the Set method. If the automatic computation of the minimum and maximum is enabled then streaming is disabled by the overloaded GetNumberOfInputRequestedRegions method. --- .../include/itkImageToHistogramFilter.h | 25 +++-- .../include/itkImageToHistogramFilter.hxx | 102 +++++++++--------- .../include/itkMaskedImageToHistogramFilter.h | 5 +- .../itkMaskedImageToHistogramFilter.hxx | 4 +- 4 files changed, 73 insertions(+), 63 deletions(-) diff --git a/Modules/Numerics/Statistics/include/itkImageToHistogramFilter.h b/Modules/Numerics/Statistics/include/itkImageToHistogramFilter.h index b964f392af3..dd1b9cc044b 100644 --- a/Modules/Numerics/Statistics/include/itkImageToHistogramFilter.h +++ b/Modules/Numerics/Statistics/include/itkImageToHistogramFilter.h @@ -21,7 +21,7 @@ #include #include "itkHistogram.h" -#include "itkImageTransformer.h" +#include "itkImageSink.h" #include "itkSimpleDataObjectDecorator.h" #include "itkProgressReporter.h" @@ -41,19 +41,20 @@ namespace Statistics */ template< typename TImage > -class ITK_TEMPLATE_EXPORT ImageToHistogramFilter:public ImageTransformer +class ITK_TEMPLATE_EXPORT ImageToHistogramFilter + : public ImageSink { public: ITK_DISALLOW_COPY_AND_ASSIGN(ImageToHistogramFilter); /** Standard type alias */ using Self = ImageToHistogramFilter; - using Superclass = ImageTransformer; + using Superclass = ImageSink; using Pointer = SmartPointer< Self >; using ConstPointer = SmartPointer< const Self >; /** Run-time type information (and related methods). */ - itkTypeMacro(ImageToHistogramFilter, ImageTransformer); + itkTypeMacro(ImageToHistogramFilter, ImageSink); /** standard New() method support */ itkNewMacro(Self); @@ -117,21 +118,29 @@ class ITK_TEMPLATE_EXPORT ImageToHistogramFilter:public ImageTransformer * pipeline of another filter. */ virtual void GraftOutput(DataObject *output); + + // Change the acces from protected to public to expose streaming option + using Superclass::SetNumberOfStreamDivisions; + using Superclass::GetNumberOfStreamDivisions; + protected: ImageToHistogramFilter(); ~ImageToHistogramFilter() override = default; void PrintSelf(std::ostream & os, Indent indent) const override; - void GenerateData() override; - void BeforeThreadedGenerateData() override; - void AfterThreadedGenerateData() override; + void BeforeStreamedGenerateData() override; + void AfterStreamedGenerateData() override; /** Method that construct the outputs */ using DataObjectPointerArraySizeType = ProcessObject::DataObjectPointerArraySizeType; using Superclass::MakeOutput; DataObject::Pointer MakeOutput(DataObjectPointerArraySizeType) override; - virtual void ThreadedComputeHistogram(const RegionType &); + /* Override method to disable streaming when the minimum and + * maximum need to be computed. */ + unsigned int GetNumberOfInputRequestedRegions () override; + + virtual void ThreadedStreamedGenerateData(const RegionType &) override; virtual void ThreadedComputeMinimumAndMaximum( const RegionType & inputRegionForThread ); diff --git a/Modules/Numerics/Statistics/include/itkImageToHistogramFilter.hxx b/Modules/Numerics/Statistics/include/itkImageToHistogramFilter.hxx index f85fc9e9f5e..3438fa29d27 100644 --- a/Modules/Numerics/Statistics/include/itkImageToHistogramFilter.hxx +++ b/Modules/Numerics/Statistics/include/itkImageToHistogramFilter.hxx @@ -36,21 +36,18 @@ ImageToHistogramFilter< TImage > // same default values as in the HistogramGenerator - typename SimpleDataObjectDecorator::Pointer marginalScale = - SimpleDataObjectDecorator::New(); - marginalScale->Set(100); - this->ProcessObject::SetInput( "MarginalScale", marginalScale ); + this->Self::SetMarginalScale(100); - SimpleDataObjectDecorator::Pointer autoMinMax = SimpleDataObjectDecorator::New(); - if( typeid(ValueType) == typeid(signed char) || typeid(ValueType) == typeid(unsigned char) ) + if( typeid(ValueType) == typeid(signed char) || + typeid(ValueType) == typeid(unsigned char) ) { - autoMinMax->Set(false); + this->Self::SetAutoMinimumMaximum(false); } else { - autoMinMax->Set(true); + this->Self::SetAutoMinimumMaximum(true); } - this->ProcessObject::SetInput( "AutoMinimumMaximum", autoMinMax ); + } template< typename TImage > @@ -66,8 +63,8 @@ const typename ImageToHistogramFilter< TImage >::HistogramType * ImageToHistogramFilter< TImage > ::GetOutput() const { - const HistogramType *output = - itkDynamicCastInDebugMode< const HistogramType * >( this->ProcessObject::GetOutput(0) ); + auto *output = + itkDynamicCastInDebugMode< const HistogramType * >( this->ProcessObject::GetPrimaryOutput() ); return output; } @@ -77,11 +74,14 @@ typename ImageToHistogramFilter< TImage >::HistogramType * ImageToHistogramFilter< TImage > ::GetOutput() { - auto * output = static_cast< HistogramType * >( this->ProcessObject::GetOutput(0) ); + + auto *output = + itkDynamicCastInDebugMode< HistogramType * >( this->ProcessObject::GetPrimaryOutput() ); return output; } + template< typename TImage > void ImageToHistogramFilter< TImage > @@ -94,20 +94,42 @@ ImageToHistogramFilter< TImage > output->Graft(graft); } + +template< typename TImage > +unsigned int +ImageToHistogramFilter< TImage > +::GetNumberOfInputRequestedRegions () +{ + // If we need to compute the minimum and maximum we don't stream + if (this->GetAutoMinimumMaximumInput() && this->GetAutoMinimumMaximum()) + { + return 1; + } + + return Superclass::GetNumberOfInputRequestedRegions(); +} + + template< typename TImage > void ImageToHistogramFilter< TImage > -::GenerateData() +::BeforeStreamedGenerateData() { - this->UpdateProgress(0.0f); - this->AllocateOutputs(); - this->BeforeThreadedGenerateData(); + Superclass::BeforeStreamedGenerateData(); - HistogramType *outputHistogram = this->GetOutput(); + const unsigned int nbOfComponents = this->GetInput()->GetNumberOfComponentsPerPixel(); + m_Minimum = HistogramMeasurementVectorType( nbOfComponents ); + m_Maximum = HistogramMeasurementVectorType( nbOfComponents ); + + m_Minimum.Fill( NumericTraits::max() ); + m_Maximum.Fill( NumericTraits::NonpositiveMin() ); + + m_MergeHistogram = nullptr; + + HistogramType *outputHistogram = this->GetOutput(); outputHistogram->SetClipBinsAtEnds(true); // the parameter needed to initialize the histogram - const unsigned int nbOfComponents = this->GetInput()->GetNumberOfComponentsPerPixel(); HistogramSizeType size(nbOfComponents); if( this->GetHistogramSizeInput() ) { @@ -120,10 +142,10 @@ ImageToHistogramFilter< TImage > size.Fill(256); } - this->UpdateProgress(0.01f); - if( this->GetAutoMinimumMaximumInput() && this->GetAutoMinimumMaximum() ) { + itkAssertInDebugAndIgnoreInReleaseMacro( (this->GetInput()->GetRequestedRegion() == this->GetInput()->GetLargestPossibleRegion()) ); + // we have to compute the minimum and maximum values this->GetMultiThreader()-> template ParallelizeImageRegion( this->GetInput()->GetRequestedRegion(), @@ -131,7 +153,6 @@ ImageToHistogramFilter< TImage > {this->ThreadedComputeMinimumAndMaximum(inputRegionForThread);}, this); - this->UpdateProgress(0.3f); this->ApplyMarginalScale( m_Minimum, m_Maximum, size ); @@ -160,37 +181,16 @@ ImageToHistogramFilter< TImage > outputHistogram->SetMeasurementVectorSize(nbOfComponents); outputHistogram->Initialize(size, m_Minimum, m_Maximum); - this->GetMultiThreader()-> template ParallelizeImageRegion( - this->GetInput()->GetRequestedRegion(), - [this](const RegionType & inputRegionForThread) - {this->ThreadedComputeHistogram(inputRegionForThread);}, - this); - this->UpdateProgress(0.8f); - - this->AfterThreadedGenerateData(); - this->UpdateProgress(1.0f); } -template< typename TImage > -void -ImageToHistogramFilter< TImage > -::BeforeThreadedGenerateData() -{ - const unsigned int nbOfComponents = this->GetInput()->GetNumberOfComponentsPerPixel(); - m_Minimum = HistogramMeasurementVectorType( nbOfComponents ); - m_Maximum = HistogramMeasurementVectorType( nbOfComponents ); - - m_Minimum.Fill( NumericTraits::max() ); - m_Maximum.Fill( NumericTraits::NonpositiveMin() ); - - m_MergeHistogram = nullptr; -} template< typename TImage > void ImageToHistogramFilter< TImage > -::AfterThreadedGenerateData() +::AfterStreamedGenerateData() { + Superclass::AfterStreamedGenerateData(); + HistogramType *outputHistogram = this->GetOutput(); outputHistogram->Graft(m_MergeHistogram); m_MergeHistogram = nullptr; @@ -235,7 +235,7 @@ ImageToHistogramFilter< TImage > template< typename TImage > void ImageToHistogramFilter< TImage > -::ThreadedComputeHistogram(const RegionType & inputRegionForThread) +::ThreadedStreamedGenerateData(const RegionType & inputRegionForThread) { const unsigned int nbOfComponents = this->GetInput()->GetNumberOfComponentsPerPixel(); const HistogramType *outputHistogram = this->GetOutput(); @@ -382,15 +382,15 @@ ImageToHistogramFilter< TImage > { Superclass::PrintSelf(os, indent); // m_HistogramBinMinimum - os << indent << "HistogramBinMinimum: " << this->GetHistogramBinMinimumInput() << std::endl; + os << indent << "HistogramBinMinimum: " << this->GetHistogramBinMinimum() << std::endl; // m_HistogramBinMaximum - os << indent << "HistogramBinMaximum: " << this->GetHistogramBinMaximumInput() << std::endl; + os << indent << "HistogramBinMaximum: " << this->GetHistogramBinMaximum() << std::endl; // m_MarginalScale - os << indent << "MarginalScale: " << this->GetMarginalScaleInput() << std::endl; + os << indent << "MarginalScale: " << this->GetMarginalScale() << std::endl; // m_AutoMinimumMaximum - os << indent << "AutoMinimumMaximum: " << this->GetAutoMinimumMaximumInput() << std::endl; + os << indent << "AutoMinimumMaximum: " << this->GetAutoMinimumMaximum() << std::endl; // m_HistogramSize - os << indent << "HistogramSize: " << this->GetHistogramSizeInput() << std::endl; + os << indent << "HistogramSize: " << this->GetHistogramSize() << std::endl; } } // end of namespace Statistics } // end of namespace itk diff --git a/Modules/Numerics/Statistics/include/itkMaskedImageToHistogramFilter.h b/Modules/Numerics/Statistics/include/itkMaskedImageToHistogramFilter.h index 98fb2dee89d..f7a142f314e 100644 --- a/Modules/Numerics/Statistics/include/itkMaskedImageToHistogramFilter.h +++ b/Modules/Numerics/Statistics/include/itkMaskedImageToHistogramFilter.h @@ -38,7 +38,8 @@ namespace Statistics */ template< typename TImage, typename TMaskImage > -class ITK_TEMPLATE_EXPORT MaskedImageToHistogramFilter:public ImageToHistogramFilter +class ITK_TEMPLATE_EXPORT MaskedImageToHistogramFilter + : public ImageToHistogramFilter { public: ITK_DISALLOW_COPY_AND_ASSIGN(MaskedImageToHistogramFilter); @@ -84,7 +85,7 @@ class ITK_TEMPLATE_EXPORT MaskedImageToHistogramFilter:public ImageToHistogramFi MaskedImageToHistogramFilter(); ~MaskedImageToHistogramFilter() override = default; - void ThreadedComputeHistogram( const RegionType & inputRegionForThread ) override; + void ThreadedStreamedGenerateData( const RegionType & inputRegionForThread ) override; void ThreadedComputeMinimumAndMaximum( const RegionType & inputRegionForThread) override; }; } // end of namespace Statistics diff --git a/Modules/Numerics/Statistics/include/itkMaskedImageToHistogramFilter.hxx b/Modules/Numerics/Statistics/include/itkMaskedImageToHistogramFilter.hxx index 486eee0280c..98774aa3b8e 100644 --- a/Modules/Numerics/Statistics/include/itkMaskedImageToHistogramFilter.hxx +++ b/Modules/Numerics/Statistics/include/itkMaskedImageToHistogramFilter.hxx @@ -30,7 +30,7 @@ template< typename TImage, typename TMaskImage > MaskedImageToHistogramFilter< TImage, TMaskImage > ::MaskedImageToHistogramFilter() { -// this->SetNumberOfRequiredInputs(2); + Self::AddRequiredInputName("MaskImage"); this->SetMaskValue( NumericTraits::max() ); } @@ -79,7 +79,7 @@ MaskedImageToHistogramFilter< TImage, TMaskImage > template< typename TImage, typename TMaskImage > void MaskedImageToHistogramFilter< TImage, TMaskImage > -::ThreadedComputeHistogram(const RegionType & inputRegionForThread) +::ThreadedStreamedGenerateData(const RegionType & inputRegionForThread) { const unsigned int nbOfComponents = this->GetInput()->GetNumberOfComponentsPerPixel(); const HistogramType *outputHistogram = this->GetOutput(); From 08fcda0f882475f8b8d2552e624b2580c65796b8 Mon Sep 17 00:00:00 2001 From: Bradley Lowekamp Date: Tue, 14 May 2019 14:55:36 -0400 Subject: [PATCH 12/16] ENH: Deprecate ImageTransformer class The ImageSinc can be used as a replacement. --- .../Deprecated}/include/itkImageTransformer.h | 2 +- .../Deprecated}/include/itkImageTransformer.hxx | 0 .../Deprecated}/wrapping/itkImageTransformer.wrap | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename Modules/{Core/Common => Compatibility/Deprecated}/include/itkImageTransformer.h (99%) rename Modules/{Core/Common => Compatibility/Deprecated}/include/itkImageTransformer.hxx (100%) rename Modules/{Core/Common => Compatibility/Deprecated}/wrapping/itkImageTransformer.wrap (100%) diff --git a/Modules/Core/Common/include/itkImageTransformer.h b/Modules/Compatibility/Deprecated/include/itkImageTransformer.h similarity index 99% rename from Modules/Core/Common/include/itkImageTransformer.h rename to Modules/Compatibility/Deprecated/include/itkImageTransformer.h index fd13abc436a..6642f9552ec 100644 --- a/Modules/Core/Common/include/itkImageTransformer.h +++ b/Modules/Compatibility/Deprecated/include/itkImageTransformer.h @@ -53,7 +53,7 @@ namespace itk * ProcessObject::ReleaseDataBeforeUpdateFlagOn(). A user may want to * set this flag to limit peak memory usage during a pipeline update. * - * \ingroup ITKCommon + * \ingroup ITKDeprecated */ template< typename TInputImage > class ITK_TEMPLATE_EXPORT ImageTransformer:public ProcessObject diff --git a/Modules/Core/Common/include/itkImageTransformer.hxx b/Modules/Compatibility/Deprecated/include/itkImageTransformer.hxx similarity index 100% rename from Modules/Core/Common/include/itkImageTransformer.hxx rename to Modules/Compatibility/Deprecated/include/itkImageTransformer.hxx diff --git a/Modules/Core/Common/wrapping/itkImageTransformer.wrap b/Modules/Compatibility/Deprecated/wrapping/itkImageTransformer.wrap similarity index 100% rename from Modules/Core/Common/wrapping/itkImageTransformer.wrap rename to Modules/Compatibility/Deprecated/wrapping/itkImageTransformer.wrap From 6c575b3ec5170e3a44b6eecc3950941bdd4ed86b Mon Sep 17 00:00:00 2001 From: Bradley Lowekamp Date: Tue, 14 May 2019 15:09:49 -0400 Subject: [PATCH 13/16] BUG: Update NormalizeImageFilter with double graft mini-pipeline Use the double graft best practice for mini-pipelines in the NormalizeImageFilter. --- .../ImageIntensity/include/itkNormalizeImageFilter.hxx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Modules/Filtering/ImageIntensity/include/itkNormalizeImageFilter.hxx b/Modules/Filtering/ImageIntensity/include/itkNormalizeImageFilter.hxx index dbcac84aa6f..30d709a86a4 100644 --- a/Modules/Filtering/ImageIntensity/include/itkNormalizeImageFilter.hxx +++ b/Modules/Filtering/ImageIntensity/include/itkNormalizeImageFilter.hxx @@ -71,17 +71,19 @@ NormalizeImageFilter< TInputImage, TOutputImage > // Gather statistics - m_StatisticsFilter->SetInput( this->GetInput() ); - //m_StatisticsFilter->GetOutput()->SetRequestedRegion( this->GetOutput()->GetRequestedRegion() ); + auto localInput = TInputImage::New(); + localInput->Graft( this->GetInput() ); + + m_StatisticsFilter->SetInput( localInput ); m_StatisticsFilter->Update(); // Set the parameters for Shift m_ShiftScaleFilter->SetShift( -m_StatisticsFilter->GetMean() ); m_ShiftScaleFilter->SetScale( NumericTraits< typename StatisticsImageFilter< TInputImage >::RealType >::OneValue() / m_StatisticsFilter->GetSigma() ); - m_ShiftScaleFilter->SetInput( this->GetInput() ); + m_ShiftScaleFilter->SetInput( localInput ); - m_ShiftScaleFilter->GetOutput()->SetRequestedRegion( this->GetOutput()->GetRequestedRegion() ); + m_ShiftScaleFilter->GraftOutput( this->GetOutput() ); m_ShiftScaleFilter->Update(); // Graft the mini pipeline output to this filters output From efdca3a9aa529dbf64c75c6d844b6b6fa8ec2e1a Mon Sep 17 00:00:00 2001 From: Bradley Lowekamp Date: Tue, 14 May 2019 15:23:53 -0400 Subject: [PATCH 14/16] DOC: statistics filter changes for ITKv5 --- Documentation/ITK5MigrationGuide.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Documentation/ITK5MigrationGuide.md b/Documentation/ITK5MigrationGuide.md index 0b824726ebb..3c1b0078957 100644 --- a/Documentation/ITK5MigrationGuide.md +++ b/Documentation/ITK5MigrationGuide.md @@ -402,9 +402,19 @@ b40f74e07d74614c75be4aceac63b87e80e589d1 on 2018-11-14. `itk::Barrier` has been moved to `ITKDeprecated` module. + `FixedArray` member functions `rBegin()` and `rEnd()` are replaced by `rbegin()` and `rend()`, which return a `reverse_iterator`, compatible with the Standard C++ Library. +`itk::ImageTransformer` has been moved to `ITKDeprecated` module. The new `itk::ImageSink` filter can be used in its place. + +`itk::StatisticsImageFilter`, `itk::LabelStatisticsImageFilter` and +`itk::MinimumMaximumImageFilter` no longer produce an image as their +primary output, as it was a shallow copy of the primary +input. Additionally, minor API changes have occoured related to the +decorated output methods to conform to ITK conventions. + + Python changes -------------- From a931acf0f929ee6bbbabb3758a9871e3c1d66b9a Mon Sep 17 00:00:00 2001 From: Bradley Lowekamp Date: Wed, 15 May 2019 11:50:59 -0400 Subject: [PATCH 15/16] ENH: Add Python wrapping for streaming base classes --- Modules/Core/Common/wrapping/ITKCommonBase.wrap | 1 + Modules/Core/Common/wrapping/itkImageSink.wrap | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 Modules/Core/Common/wrapping/itkImageSink.wrap diff --git a/Modules/Core/Common/wrapping/ITKCommonBase.wrap b/Modules/Core/Common/wrapping/ITKCommonBase.wrap index 246f65baf37..410801ba95f 100644 --- a/Modules/Core/Common/wrapping/ITKCommonBase.wrap +++ b/Modules/Core/Common/wrapping/ITKCommonBase.wrap @@ -30,6 +30,7 @@ itk_wrap_simple_class("itk::LightObject" POINTER) itk_wrap_simple_class("itk::Object" POINTER) itk_wrap_simple_class("itk::DataObject" POINTER) itk_wrap_simple_class("itk::LightProcessObject" POINTER) +itk_wrap_simple_class("itk::StreamingProcessObject" POINTER) itk_wrap_simple_class("itk::ProcessObject" POINTER) itk_wrap_simple_class("itk::Command" POINTER) itk_wrap_simple_class("itk::Directory" POINTER) diff --git a/Modules/Core/Common/wrapping/itkImageSink.wrap b/Modules/Core/Common/wrapping/itkImageSink.wrap new file mode 100644 index 00000000000..1dbec2eb64d --- /dev/null +++ b/Modules/Core/Common/wrapping/itkImageSink.wrap @@ -0,0 +1,9 @@ +itk_wrap_class("itk::ImageSink" POINTER) + itk_wrap_image_filter("${WRAP_ITK_ALL_TYPES}" 1) + + foreach(d ${ITK_WRAP_IMAGE_DIMS}) + foreach(type ${WRAP_ITK_SCALAR}) + itk_wrap_template("${ITKM_VI${type}${d}}" "${ITKT_VI${type}${d}}") + endforeach() + endforeach() +itk_end_wrap_class() From 23da91e2e1c8dcc772ad08095e988e4415b18983 Mon Sep 17 00:00:00 2001 From: Bradley Lowekamp Date: Wed, 15 May 2019 12:42:23 -0400 Subject: [PATCH 16/16] ENH: Add streaming testing of label statistics filter --- .../Filtering/ImageStatistics/test/CMakeLists.txt | 15 +++++++++++++-- .../test/itkLabelStatisticsImageFilterTest.cxx | 13 +++++++++++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/Modules/Filtering/ImageStatistics/test/CMakeLists.txt b/Modules/Filtering/ImageStatistics/test/CMakeLists.txt index aec53140846..a115fb96627 100644 --- a/Modules/Filtering/ImageStatistics/test/CMakeLists.txt +++ b/Modules/Filtering/ImageStatistics/test/CMakeLists.txt @@ -42,9 +42,20 @@ itk_add_test(NAME itkStatisticsImageFilterTest_2 COMMAND ITKImageStatisticsTestDriver itkStatisticsImageFilterTest 1) itk_add_test(NAME itkStatisticsImageFilterTest_3 COMMAND ITKImageStatisticsTestDriver itkStatisticsImageFilterTest 63) -itk_add_test(NAME itkLabelStatisticsImageFilterTest +itk_add_test(NAME itkLabelStatisticsImageFilterTest_1 COMMAND ITKImageStatisticsTestDriver itkLabelStatisticsImageFilterTest - DATA{${ITK_DATA_ROOT}/Input/peppers.png} DATA{${ITK_DATA_ROOT}/Baseline/Algorithms/OtsuMultipleThresholdsImageFilterTest.png}) + DATA{${ITK_DATA_ROOT}/Input/peppers.png} + DATA{${ITK_DATA_ROOT}/Baseline/Algorithms/OtsuMultipleThresholdsImageFilterTest.png}) +itk_add_test(NAME itkLabelStatisticsImageFilterTest_2 + COMMAND ITKImageStatisticsTestDriver itkLabelStatisticsImageFilterTest + DATA{${ITK_DATA_ROOT}/Input/peppers.png} + DATA{${ITK_DATA_ROOT}/Baseline/Algorithms/OtsuMultipleThresholdsImageFilterTest.png} + 10 ) +itk_add_test(NAME itkLabelStatisticsImageFilterTest_3 + COMMAND ITKImageStatisticsTestDriver itkLabelStatisticsImageFilterTest + DATA{${ITK_DATA_ROOT}/Input/peppers.png} + DATA{${ITK_DATA_ROOT}/Baseline/Algorithms/OtsuMultipleThresholdsImageFilterTest.png} + 20 ) itk_add_test(NAME itkSumProjectionImageFilterTest COMMAND ITKImageStatisticsTestDriver --compare DATA{${ITK_DATA_ROOT}/Baseline/BasicFilters/HeadMRVolumeSumProjection.tif} diff --git a/Modules/Filtering/ImageStatistics/test/itkLabelStatisticsImageFilterTest.cxx b/Modules/Filtering/ImageStatistics/test/itkLabelStatisticsImageFilterTest.cxx index daab6b2e88e..b5a7f5329d7 100644 --- a/Modules/Filtering/ImageStatistics/test/itkLabelStatisticsImageFilterTest.cxx +++ b/Modules/Filtering/ImageStatistics/test/itkLabelStatisticsImageFilterTest.cxx @@ -29,11 +29,11 @@ int itkLabelStatisticsImageFilterTest(int argc, char* argv [] ) { std::cout << "itkLabelStatisticsImageFilterTest Start" << std::endl; - if( argc < 2 ) + if( argc < 3 ) { std::cerr << "Missing Arguments" << std::endl; std::cerr << "Usage: " << std::endl; - std::cerr << itkNameOfTestExecutableMacro(argv) << " inputImage labeledImage " << std::endl; + std::cerr << itkNameOfTestExecutableMacro(argv) << " inputImage labeledImage [numberOfStreamDivision]" << std::endl; return EXIT_FAILURE; } using ImageType = itk::Image; @@ -46,6 +46,14 @@ int itkLabelStatisticsImageFilterTest(int argc, char* argv [] ) reader1->SetFileName( argv[1] ); reader2->SetFileName( argv[2] ); + + unsigned int numberOfStreamDivisions = 1; + + if (argc > 3) + { + numberOfStreamDivisions = std::max( std::stoi( argv[3] ), 1 ); + } + using FilterType = itk::LabelStatisticsImageFilter< ImageType, ImageType >; FilterType::Pointer filter = FilterType::New(); @@ -55,6 +63,7 @@ int itkLabelStatisticsImageFilterTest(int argc, char* argv [] ) filter->SetInput ( reader1->GetOutput() ); filter->SetLabelInput ( reader2->GetOutput() ); filter->UseHistogramsOn(); + filter->SetNumberOfStreamDivisions( numberOfStreamDivisions ); try { filter->Update();