/*========================================================================= * * Copyright NumFOCUS * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *=========================================================================*/ #ifndef itkSpatialObject_h #define itkSpatialObject_h // Disable warning for lengthy symbol names in this file only #include "itkCovariantVector.h" #include "itkMacro.h" #include #include "itkSpatialObjectProperty.h" #include "itkProcessObject.h" #include "itkIndex.h" #include "itkImageRegion.h" #include "itkAffineTransform.h" #include "itkVectorContainer.h" #include "itkBoundingBox.h" #include namespace itk { /** * \class SpatialObject * \brief Implementation of the composite pattern * * The purpose of this class is to implement the composite pattern [Design * Patterns, Gamma, 1995] within itk, so that it becomes easy to create an * environment containing objects within a scene, and to manipulate the * environment as a whole or any of its component objects. An * object has a list of transformations to transform object coordinates * to the corresponding coordinates in the real world coordinate * system, and a list of inverse transformation to go backward. Any * spatial objects can be plugged to a spatial object as children. To * implement your own spatial object, you need to derive from the * following class, which requires overriding just a few virtual functions. * Examples of such functions are ValueAtInObjectSpace(), * IsInsideInObjectSpace(), and ComputeMyBoundingBox(), each of which has a * meaning specific to each particular object type. * \ingroup ITKSpatialObjects */ template class ITK_TEMPLATE_EXPORT SpatialObject : public DataObject { public: ITK_DISALLOW_COPY_AND_MOVE(SpatialObject); using ScalarType = double; using ObjectDimensionType = unsigned int; static constexpr ObjectDimensionType ObjectDimension = VDimension; static constexpr unsigned int MaximumDepth = std::numeric_limits::max(); /** Return the maximum depth that a tree of spatial objects can * have. This provides convenient access to a static constant. */ unsigned int GetMaximumDepth() const { return MaximumDepth; } using Self = SpatialObject; using Superclass = DataObject; using Pointer = SmartPointer; using ConstPointer = SmartPointer; // Spatial Function Iterator needs the following type alias using InputType = Point; using PointType = Point; using PointPointer = PointType *; using VectorType = Vector; using CovariantVectorType = CovariantVector; using VectorPointer = VectorType *; using DerivativeVectorType = CovariantVector; using DerivativeVectorPointer = DerivativeVectorType *; using DerivativeOffsetType = Vector; using TransformType = AffineTransform; using TransformPointer = typename TransformType::Pointer; using TransformConstPointer = const TransformType *; using VectorContainerType = VectorContainer; using BoundingBoxType = BoundingBox; using BoundingBoxPointer = typename BoundingBoxType::Pointer; /** Return type for the list of children */ using ChildrenListType = std::list; using ChildrenConstListType = std::list; using ChildrenListPointer = ChildrenListType *; using ChildrenConstListPointer = ChildrenConstListType *; using ObjectListType = std::list; using ObjectConstListType = std::list; using RegionType = ImageRegion; using PropertyType = SpatialObjectProperty; /* These are needed to participate in a Pipeline */ using IndexType = Index; using SizeType = Size; /** Get the dimensionality of the object */ unsigned int GetObjectDimension() const { return VDimension; } /** Method for creation through the object factory. */ itkNewMacro(Self); /** \see LightObject::GetNameOfClass() */ itkOverrideGetNameOfClassMacro(SpatialObject); /** Get/Set the ID */ void SetId(int id); itkGetConstReferenceMacro(Id, int); /** Set the typename of the SpatialObject. Use cautiously - Conversion, * Factory, and IO methods depend on standard naming. Can be used * to prepend a subtype to a typename. */ itkSetMacro(TypeName, std::string); /** Get the typename of the SpatialObject */ virtual const std::string GetTypeName() const { return m_TypeName; } /** Get the class name with the dimension of the spatial object appended. * * Returns the type of the spatial object as a string. * * Used by the SpatialObjectFactory. */ virtual std::string GetClassNameAndDimension() const; /** Restore a spatial object to its initial state, yet preserves Id as well as * parent and children relationships */ virtual void Clear(); /** Set the property applied to the object. */ void SetProperty(const PropertyType & property) { this->m_Property = property; this->Modified(); } /** Returns a pointer to the property object applied to this class. */ const PropertyType & GetProperty() const { return this->m_Property; } PropertyType & GetProperty() { return this->m_Property; } /** Returns the latest modified time of the spatial object, and * any of its components. */ ModifiedTimeType GetMTime() const override; /** Returns the latest modified time of the spatial object, but not * the modification time of the children */ ModifiedTimeType GetMyMTime() const { return Superclass::GetMTime(); } /**************/ /* Transforms */ /**************/ /** Set the global to local transformation. * * Defines the transformation from the global coordinate frame. * * By setting this transform, the object transform is updated. */ void SetObjectToWorldTransform(const TransformType * transform); itkGetModifiableObjectMacro(ObjectToWorldTransform, TransformType); const TransformType * GetObjectToWorldTransformInverse() const; /** Set the local to global transformation. * * Transforms points from the object-specific "physical" space to the "physical" space of its parent object. */ void SetObjectToParentTransform(const TransformType * transform); itkGetModifiableObjectMacro(ObjectToParentTransform, TransformType); const TransformType * GetObjectToParentTransformInverse() const; /** Set the local to global transformation. * * Compute the local transform when the global transform is set. * * This does not change the IndexToObjectMatrix. */ void ComputeObjectToParentTransform(); /**********************************************************************/ /* These are the three member functions that a subclass will typically * override. * * ComputeMyBoundingBox (protected:) * * IsInsideInObjectSpace * * Update * Optionally, a subclass may also wish to override * * ValueAtInObjectSpace * * IsEvaluableAtInObjectSpace - if the extent is beyond IsInside. */ /**********************************************************************/ /** Returns true if a point is inside the object or its children in object space. */ bool IsInsideInObjectSpace(const PointType & point, unsigned int depth, const std::string & name = "") const; /** Returns false by default, but is overridden in order to return true * if a point is inside the object. */ virtual bool IsInsideInObjectSpace(const PointType & point) const; /** Update - Optionally used to compute a world-coordinate representation of * the object. Object-dependent implementation. */ void Update() override; /** Returns the value at a point. Returns true if that value is valid. */ virtual bool ValueAtInObjectSpace(const PointType & point, double & value, unsigned int depth = 0, const std::string & name = "") const; /** Returns true if the object can provide a "meaningful" value at * a point. Often defaults to returning same answer as * IsInsideInWorldSpace, but certain objects influence space beyond their * spatial extent, e.g., an RFA Needle Spatial Object can cause a burn * that extends beyond the tip of the needle. */ virtual bool IsEvaluableAtInObjectSpace(const PointType & point, unsigned int depth = 0, const std::string & name = "") const; /********************************************************/ /* Helper functions to recurse queries through children */ /********************************************************/ /** Return if a point is inside the object or its children. */ virtual bool IsInsideChildrenInObjectSpace(const PointType & point, unsigned int depth = 0, const std::string & name = "") const; /** Return the value of the object at a point. */ virtual bool ValueAtChildrenInObjectSpace(const PointType & point, double & value, unsigned int depth = 0, const std::string & name = "") const; /** Return if the object is evaluable at a point. */ virtual bool IsEvaluableAtChildrenInObjectSpace(const PointType & point, unsigned int depth = 0, const std::string & name = "") const; /**************************/ /* Values and derivatives */ /**************************/ /** Set/Get the default inside value (ValueAtInWorldSpace()) of the object. * Default is 1.0 */ itkSetMacro(DefaultInsideValue, double); itkGetConstMacro(DefaultInsideValue, double); /** Set/Get the default outside value (ValueAtInWorldSpace()) of the object. * Default is 0.0 */ itkSetMacro(DefaultOutsideValue, double); itkGetConstMacro(DefaultOutsideValue, double); /** World space equivalent to ValueAtInObjectSpace * \note This member function assumes that the internal `ObjectToWorldTransformInverse` transform is up-to-date. This * transform may be updated explicitly by calling `GetObjectToWorldTransformInverse()`, `Update()`, or * `SetObjectToWorldTransform(transform)` * \note This member function is not meant to be overridden. In the future, it may not be declared `virtual` anymore. */ #ifndef ITK_FUTURE_LEGACY_REMOVE virtual #endif bool ValueAtInWorldSpace(const PointType & point, double & value, unsigned int depth = 0, const std::string & name = "") const; /** World space equivalent to IsInsideInObjectSpace * \note This member function assumes that the internal `ObjectToWorldTransformInverse` transform is up-to-date. This * transform may be updated explicitly by calling `GetObjectToWorldTransformInverse()`, `Update()`, or * `SetObjectToWorldTransform(transform)` * \note This member function is not meant to be overridden. In the future, it may not be declared `virtual` anymore. */ #ifndef ITK_FUTURE_LEGACY_REMOVE virtual #endif bool IsInsideInWorldSpace(const PointType & point, unsigned int depth, const std::string & name = "") const; /** Overload, optimized for depth = 0 and name = "": `spatialObject.IsInsideInWorldSpace(point)` is equivalent to * `spatialObject.IsInsideInWorldSpace(point, 0, "")`, but much faster. */ bool IsInsideInWorldSpace(const PointType & point) const; /** World space equivalent to IsEvaluableAtInObjectSpace * \note This member function assumes that the internal `ObjectToWorldTransformInverse` transform is up-to-date. This * transform may be updated explicitly by calling `GetObjectToWorldTransformInverse()`, `Update()`, or * `SetObjectToWorldTransform(transform)` * \note This member function is not meant to be overridden. In the future, it may not be declared `virtual` anymore. */ #ifndef ITK_FUTURE_LEGACY_REMOVE virtual #endif bool IsEvaluableAtInWorldSpace(const PointType & point, unsigned int depth = 0, const std::string & name = "") const; /** Return the n-th order derivative value at the specified point. */ virtual void DerivativeAtInObjectSpace(const PointType & point, short unsigned int order, CovariantVectorType & value, unsigned int depth = 0, const std::string & name = "", const DerivativeOffsetType & offset = MakeFilled(1)); /** Return the n-th order derivative value at the specified point. * \note This member function assumes that the internal `ObjectToWorldTransformInverse` transform is up-to-date. This * transform may be updated explicitly by calling `GetObjectToWorldTransformInverse()`, `Update()`, or * `SetObjectToWorldTransform(transform)` * \note This member function is not meant to be overridden. In the future, it may not be declared `virtual` anymore. */ #ifndef ITK_FUTURE_LEGACY_REMOVE virtual #endif void DerivativeAtInWorldSpace(const PointType & point, short unsigned int order, CovariantVectorType & value, unsigned int depth = 0, const std::string & name = "", const DerivativeOffsetType & offset = MakeFilled(1)); /*********************/ /* Deal with Parents */ /*********************/ /** Set the pointer to the parent object in the tree hierarchy. * Updates the ObjectToParentTransform to keep the object from moving * in space. */ void SetParent(Self * parent); /** Return true if the object has a parent object. Basically, only * the root object , or some isolated objects should return false. */ virtual bool HasParent() const; /** Get the parent of the spatial object. * * Returns a pointer to the parent object in the hierarchy tree. */ virtual const Self * GetParent() const; /** Get the parent of the spatial object. * * Returns a pointer to the parent object in the hierarchy tree. */ virtual Self * GetParent(); /** Set/Get the parent Identification number */ itkSetMacro(ParentId, int); itkGetConstReferenceMacro(ParentId, int); /**********************/ /* Deal with Children */ /**********************/ /** Set the list of pointers to children to the list passed as argument. */ void SetChildren(ChildrenListType & children); /** Add an object to the list of children. */ void AddChild(Self * pointer); /** Remove the object passed as arguments from the list of * children. */ bool RemoveChild(Self * pointer); /** Remove all children to a given depth */ void RemoveAllChildren(unsigned int depth = MaximumDepth); /** Get the children affiliated to this object. * A depth of 0 returns the immediate children. A depth of 1 returns the * children and those children's children. * \warning The user is responsible for freeing the list, but not the elements of * the list. */ virtual ChildrenListType * GetChildren(unsigned int depth = 0, const std::string & name = "") const; /** Get the children affiliated to this object. * A depth of 0 returns the immediate children. A depth of 1 returns the * children and those children's children. * \warning The user is responsible for freeing the list, but not the elements of * the list. */ virtual ChildrenConstListType * GetConstChildren(unsigned int depth = 0, const std::string & name = "") const; virtual void AddChildrenToList(ChildrenListType * childrenList, unsigned int depth = 0, const std::string & name = "") const; virtual void AddChildrenToConstList(ChildrenConstListType * childrenList, unsigned int depth = 0, const std::string & name = "") const; /** Get the number of children currently assigned to the object. */ unsigned int GetNumberOfChildren(unsigned int depth = 0, const std::string & name = "") const; /** Return a SpatialObject given its ID, if it is a child. */ SpatialObject * GetObjectById(int id); /** In practice, this is used to transform an imported MetaIO scene hierarchy * specified only by Ids into the SpatialObject hierarchy specified by * Ids and Child/Parent lists. */ bool FixParentChildHierarchyUsingParentIds(); /** Check if the parent objects have a defined ID. * * Confirms that every object inherited from this has a unique Id. */ bool CheckIdValidity() const; /** Give every object inherited from this a unique Id */ void FixIdValidity(); /** Generate a unique Id. * * Returns the next available Id. For speed reason the MaxID+1 is returned. */ int GetNextAvailableId() const; /**********************/ /* Bounding Box */ /**********************/ /** Get a pointer to the axis-aligned bounding box of the object in world * space. This box is computed by ComputeMyBoundingBox which * is called by Update(). */ itkGetConstObjectMacro(MyBoundingBoxInObjectSpace, BoundingBoxType); /** Get a pointer to the axis-aligned bounding box of the object in world * space. This box is computed by ComputeMyBoundingBox which * is called by Update(). */ virtual const BoundingBoxType * GetMyBoundingBoxInWorldSpace() const; /** Compute an axis-aligned bounding box for an object and its selected * children, down to a specified depth, in object space. * * After computation, the resulting bounding box is stored in m_FamilyBoundingBoxInWorldSpace. */ virtual bool ComputeFamilyBoundingBox(unsigned int depth = 0, const std::string & name = "") const; /** Get a pointer to the bounding box of the object. * The extents and the position of the box are not computed. */ itkGetConstObjectMacro(FamilyBoundingBoxInObjectSpace, BoundingBoxType); /** Get a pointer to the bounding box of the object. * The extents and the position of the box are not computed. */ virtual const BoundingBoxType * GetFamilyBoundingBoxInWorldSpace() const; /******************************/ /* Regions used by DataObject */ /******************************/ /** Set the largest possible region. * * Sets the region object that defines the size and starting index * for the largest possible region this image could represent. This * is used in determining how much memory would be needed to load an * entire dataset. It is also used to determine boundary * conditions. * \sa ImageRegion, SetBufferedRegion(), SetRequestedRegion() */ virtual void SetLargestPossibleRegion(const RegionType & region); /** Get the region object that defines the size and starting index * for the largest possible region this image could represent. This * is used in determining how much memory would be needed to load an * entire dataset. It is also used to determine boundary * conditions. * \sa ImageRegion, GetBufferedRegion(), GetRequestedRegion() */ virtual const RegionType & GetLargestPossibleRegion() const { return m_LargestPossibleRegion; } /** Set the region object that defines the size and starting index * of the region of the image currently loaded in memory. * \sa ImageRegion, SetLargestPossibleRegion(), SetRequestedRegion() */ virtual void SetBufferedRegion(const RegionType & region); /** Get the region object that defines the size and starting index * of the region of the image currently loaded in memory. * \sa ImageRegion, SetLargestPossibleRegion(), SetRequestedRegion() */ virtual const RegionType & GetBufferedRegion() const { return m_BufferedRegion; } /** Set the region object that defines the size and starting index * for the region of the image requested (i.e., the region of the * image to be operated on by a filter). * \sa ImageRegion, SetLargestPossibleRegion(), SetBufferedRegion() */ virtual void SetRequestedRegion(const RegionType & region); /** Set the requested region from this data object to match the requested * region of the data object passed in as a parameter. This method * implements the API from DataObject. The data object parameter must be * castable to an ImageBase. */ void SetRequestedRegion(const DataObject * data) override; /** Get the region object that defines the size and starting index * for the region of the image requested (i.e., the region of the * image to be operated on by a filter). * \sa ImageRegion, SetLargestPossibleRegion(), SetBufferedRegion() */ virtual const RegionType & GetRequestedRegion() const { return m_RequestedRegion; } /** Set the RequestedRegion to the LargestPossibleRegion. This * forces a filter to produce all of the output in one execution * (i.e. not streaming) on the next call to Update(). */ void SetRequestedRegionToLargestPossibleRegion() override; /** Determine whether the RequestedRegion is outside of the * BufferedRegion. This method returns true if the RequestedRegion * is outside the BufferedRegion (true if at least one pixel is * outside). This is used by the pipeline mechanism to determine * whether a filter needs to re-execute in order to satisfy the * current request. If the current RequestedRegion is already * inside the BufferedRegion from the previous execution (and the * current filter is up to date), then a given filter does not need * to re-execute */ bool RequestedRegionIsOutsideOfTheBufferedRegion() override; /** Verify that the RequestedRegion is within the * LargestPossibleRegion. If the RequestedRegion is not within the * LargestPossibleRegion, then the filter cannot possible satisfy * the request. This method returns true if the request can be * satisfied and returns fails if the request cannot. This method is * used by PropagateRequestedRegion(). PropagateRequestedRegion() * throws a InvalidRequestedRegionError exception is the requested * region is not within the LargestPossibleRegion. */ bool VerifyRequestedRegion() override; /** Update the information for this DataObject so that it can be used * as an output of a ProcessObject. This method is used the pipeline * mechanism to propagate information and initialize the meta data * associated with a DataObject. This method calls its source's * ProcessObject::UpdateOutputInformation() which determines modified * times, LargestPossibleRegions, and any extra meta data like spacing, * origin, etc. */ void UpdateOutputInformation() override; /** Copy the information from the specified data object. This method is * part of the pipeline execution model. By default, a ProcessObject * will copy meta-data from the first input to all of its * outputs. See ProcessObject::GenerateOutputInformation(). Each * subclass of DataObject is responsible for being able to copy * whatever meta-data it needs from from another DataObject. * ImageBase has more meta-data than its DataObject. Thus, it must * provide its own version of CopyInformation() in order to copy the * LargestPossibleRegion from the input parameter. */ void CopyInformation(const DataObject * data) override; /*************************************/ /* Evaluate used by SpatialFunctions */ /*************************************/ /** Returns true if a point is inside the object - provided * to make spatial objects compatible with spatial functions * and conditional iterators for defining regions of interest. */ bool Evaluate(const PointType & point) const { return this->IsInsideInWorldSpace(point); } #if !defined(ITK_LEGACY_REMOVE) itkLegacyMacro(void ComputeObjectToWorldTransform()) { this->Update(); /* Update() should be used instead of ProtectedComputeObjectToWorldTransform() */ } itkLegacyMacro(void ComputeBoundingBox()) { this->Update(); /* Update() should be used instead of outdated ComputeBoundingBox() */ } /** Returns true if a point is inside the object in object space. */ itkLegacyMacro(virtual bool IsInside(const PointType & point, unsigned int depth = 0, const std::string & name = "") const) { return IsInsideInObjectSpace(point, depth, name); }; #endif protected: /** Compute the World transform when the local transform is set * This function should be called each time the local transform * has been modified */ void ProtectedComputeObjectToWorldTransform(); /** Compute bounding box for the object in object space */ virtual void ComputeMyBoundingBox(); /** Default constructor. Ensures that its bounding boxes are empty (all * bounds zero-valued), its list of children is empty, and its transform * objects identical to the identity transform, initially. */ SpatialObject() = default; /** Destructor. */ ~SpatialObject() override; void PrintSelf(std::ostream & os, Indent indent) const override; BoundingBoxType * GetModifiableMyBoundingBoxInObjectSpace() { return m_MyBoundingBoxInObjectSpace.GetPointer(); } typename LightObject::Pointer InternalClone() const override; private: /** Object Identification Number */ int m_Id{ -1 }; /** Type of spatial object */ std::string m_TypeName{ "SpatialObject" }; PropertyType m_Property{}; int m_ParentId{ -1 }; Self * m_Parent{ nullptr }; RegionType m_LargestPossibleRegion{}; RegionType m_RequestedRegion{}; RegionType m_BufferedRegion{}; const BoundingBoxPointer m_MyBoundingBoxInObjectSpace{ BoundingBoxType::New() }; const BoundingBoxPointer m_MyBoundingBoxInWorldSpace{ BoundingBoxType::New() }; const BoundingBoxPointer m_FamilyBoundingBoxInObjectSpace{ BoundingBoxType::New() }; const BoundingBoxPointer m_FamilyBoundingBoxInWorldSpace{ BoundingBoxType::New() }; const TransformPointer m_ObjectToParentTransform{ TransformType::New() }; const TransformPointer m_ObjectToParentTransformInverse{ TransformType::New() }; const TransformPointer m_ObjectToWorldTransform{ TransformType::New() }; const TransformPointer m_ObjectToWorldTransformInverse{ TransformType::New() }; ChildrenListType m_ChildrenList{}; /** Default inside value for the ValueAtInWorldSpace() */ double m_DefaultInsideValue{ 1.0 }; /** Default outside value for the ValueAtInWorldSpace() */ double m_DefaultOutsideValue{ 0.0 }; }; } // end of namespace itk #if !defined(ITK_WRAPPING_PARSER) # ifndef ITK_MANUAL_INSTANTIATION # include "itkSpatialObject.hxx" # endif #endif #endif // itkSpatialObject_h