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 -------------- 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 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 new file mode 100644 index 00000000000..98ec3426845 --- /dev/null +++ b/Modules/Core/Common/include/itkImageSink.h @@ -0,0 +1,190 @@ +/*========================================================================= + * + * 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" +#include "itkImageToImageFilterCommon.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, + private ImageToImageFilterCommon +{ +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; + + + /** 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; + + 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 VerifyInputInformation() ITKv5_CONST override; + + 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; + + /** + * Tolerances for checking whether input images are defined to + * occupy the same physical space. + */ + double m_CoordinateTolerance; + double m_DirectionTolerance; +}; + +} + +#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..1e11220a363 --- /dev/null +++ b/Modules/Core/Common/include/itkImageSink.hxx @@ -0,0 +1,287 @@ +/*========================================================================= + * + * 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" +#include "itkInputDataObjectConstIterator.h" + +namespace itk +{ + +template +ImageSink +::ImageSink() + : m_NumberOfStreamDivisions{1}, + m_CoordinateTolerance{Self::GetGlobalDefaultCoordinateTolerance()}, + m_DirectionTolerance{Self::GetGlobalDefaultDirectionTolerance()} +{ + // 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; + os << indent << "CoordinateTolerance: " << this->m_CoordinateTolerance << std::endl; + os << indent << "DirectionTolerance: " << this->m_DirectionTolerance << 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 +::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 +::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 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); 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 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() 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..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 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 diff --git a/Modules/Filtering/ImageStatistics/include/itkLabelStatisticsImageFilter.h b/Modules/Filtering/ImageStatistics/include/itkLabelStatisticsImageFilter.h index 89ff9dedd4a..63efd600310 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 >; @@ -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(). */ @@ -322,6 +313,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 +330,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..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; @@ -38,29 +39,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 +118,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 +166,7 @@ LabelStatisticsImageFilter< TInputImage, TLabelImage > template< typename TInputImage, typename TLabelImage > void LabelStatisticsImageFilter< TInputImage, TLabelImage > -::DynamicThreadedGenerateData(const RegionType & outputRegionForThread) +::ThreadedStreamedGenerateData(const RegionType & outputRegionForThread) { MapType localStatistics; 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 ) { diff --git a/Modules/Filtering/ImageStatistics/include/itkStatisticsImageFilter.h b/Modules/Filtering/ImageStatistics/include/itkStatisticsImageFilter.h index 6f1684ca1ee..7f4fdfd5684 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 >; @@ -91,59 +91,35 @@ 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(); + itkGetDecoratedOutputMacro(SumOfSquares, RealType); - 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 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 @@ -157,33 +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 BeforeThreadedGenerateData() override; + void BeforeStreamedGenerateData() override; - /** Do final mean and variance computation from data accumulated in threads. + /** Set outputs to computed values from all regions */ - void AfterThreadedGenerateData() override; - - void DynamicThreadedGenerateData( const RegionType &) override; + void AfterStreamedGenerateData() override; - // Override since the filter needs all the data for the algorithm - void GenerateInputRequestedRegion() override; + void ThreadedStreamedGenerateData( const RegionType &) override; - // Override since the filter produces all of its output - void EnlargeOutputRequestedRegion(DataObject *data) 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 3b10072a349..1c8bc9f6dd6 100644 --- a/Modules/Filtering/ImageStatistics/include/itkStatisticsImageFilter.hxx +++ b/Modules/Filtering/ImageStatistics/include/itkStatisticsImageFilter.hxx @@ -28,224 +28,47 @@ 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 - // - // 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() ); - 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() ); - this->ProcessObject::SetNthOutput( i, output.GetPointer() ); - } + this->SetNumberOfRequiredInputs(1); - 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 > -::GenerateInputRequestedRegion() -{ - Superclass::GenerateInputRequestedRegion(); - if ( this->GetInput() ) + if ( name == "Mean" || + name == "Sigma" || + name == "Variance" || + name == "Sum" || + name == "SumOfSquares" ) { - InputImagePointer image = - const_cast< typename Superclass::InputImageType * >( this->GetInput() ); - image->SetRequestedRegionToLargestPossibleRegion(); + return RealObjectType::New(); } + return Superclass::MakeOutput(name); } template< typename TInputImage > void StatisticsImageFilter< TInputImage > -::EnlargeOutputRequestedRegion(DataObject *data) +::BeforeStreamedGenerateData() { - Superclass::EnlargeOutputRequestedRegion(data); - data->SetRequestedRegionToLargestPossibleRegion(); -} + Superclass::BeforeStreamedGenerateData(); -template< typename TInputImage > -void -StatisticsImageFilter< 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 -StatisticsImageFilter< TInputImage > -::BeforeThreadedGenerateData() -{ // Resize the thread temporaries m_Count = NumericTraits< SizeValueType >::ZeroValue(); m_SumOfSquares = NumericTraits< RealType >::ZeroValue(); @@ -258,8 +81,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; @@ -272,19 +97,19 @@ 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 > void StatisticsImageFilter< TInputImage > -::DynamicThreadedGenerateData(const RegionType & regionForThread) +::ThreadedStreamedGenerateData(const RegionType & regionForThread) { CompensatedSummation sum = NumericTraits< RealType >::ZeroValue(); diff --git a/Modules/Filtering/ImageStatistics/test/CMakeLists.txt b/Modules/Filtering/ImageStatistics/test/CMakeLists.txt index cda8f2bf8a8..a115fb96627 100644 --- a/Modules/Filtering/ImageStatistics/test/CMakeLists.txt +++ b/Modules/Filtering/ImageStatistics/test/CMakeLists.txt @@ -36,11 +36,26 @@ 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 itkLabelStatisticsImageFilterTest +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_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(); 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); 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();