Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.Sign up
Refactor SpatialObjects for clarity #465
The SpatialObject classes have an unnecessarily complex hierarchy of transforms, contain the same (redundant) information (e.g., parent-child relationships and transforms) in multiple member variables, and impose the use of index space for object definitions when conceptually objects should simply exist in physical space. Additionally, there are many logic bugs and inconsistent concepts in the SpatialObject implementations.
We are seeking to increase the use and utility of spatial objects by simplifying and fixing their implementations.
This will break backward compatibility, but the hope is that the few who will be inconvenienced by this change will ultimately benefit from the bug fixes, speed improvements, and broad user base of spatialObjects that should result. Additionally, our hope is that a highly functional set of SpatialObjects can provide another data format for sharing analyses with other toolkits, such as VTK.
Challenges Being Addressed by Refactoring
Challenge 1) The SpatialObject classes have problematic APIs related to redundant constructs in SpatialObject variables and member functions and VNL TreeNode variables and member functions. For example, SpatialObjects have treenode member variables, treenodes have a set of transforms associated with them, and SpatialObjects provide a member functions for getting/setting treenode transforms, e.g., GetObjectToNodeTransform, GetNodeToParentNodeTransform. However, SpatialObjects also have their own transforms and associated member functions (e.g., GetIndexToObjectTransform, GetObjectToParentObjectTransform). These sets of transforms are partially redundant, are not conceptually consistent, and add unnecessary complexity to the classes.
Proposed Solution to Challenge 1) We are removing treenodes from SpatialObjects. There was no reason for the added complexity of treenodes. It is possible to represent objects and their relationships using only one ObjectToParent transform per object. Any additional transforms are gratuitous. Eliminating TreeNodes also reduce ITK's dependencies on VNL.
Challenge 2) TreeNodes also maintained the parent-child relationships between SpatialObjects, but SpatialObjects also had "InternalChildrenList" member variable and a "Parent" member variable for maintaining parent-child relationships. Maintaining these redundant representations was prone to errors.
Proposed Solution to Challenge 2) Eliminate the VNL treenodes from the SpatialObjects so that there is only one record of parent-child relationships. This also reduces ITK's dependency on VNL.
Challenge 3) The handling of children in various computations of IsInside, ValueAt, and Bounds is inconsistent and/or buggy for many SpatialObjects. Some implementations make these computations O(n^2) instead of O(n).
Proposed Solution to Challenge 3) Adding IsInsideChildren, ValueAtChildren and other helper classes to the top-level SpatialObject class simplifies the specification of the IsInside, ValueAt and other such object-specific classes in the derived classes. Also, now most derived classes need only implement IsInside and ComputeObjectBoundingBox member functions.
Challenge 4) All SpatialObjects must be specified in index space. This creates an unnecessary transform (and mental effort) for every object.
Proposed Solution to Challenge 4) After refactoring, if you want a Box or other object, you create it in physical space and manipulate it in physical space. You don't have to create an index space for the box before placing it in space using an indexToObject transform.
Challenge 5) While SpatialObjects have the concept of Index space, they do not use cosine directions with their index space. The segmentation of spatialObjects from images requires careful attention to handling multiple transforms that are inconsistent between images and the SpatialObjects.
Proposed Solution to Challenge 5) By having all objects exist in physicalSpace, then simply calling an image's TransformIndexToPhysicalPoint function is all that is needed to define the representation of a SpatialObject that is segmented from an image. This also simplifies viewing and combining SpatialObjects across and/or without images.
Challenge 6) Adding or removing an object to/from a parent could cause the object to move in space because their parent's parent's transforms were no longer being applied.
Proposed Solution to Challenge 6) With the refactoring, updates to ObjectToParentTransform are much easier to maintain as parent/child relationships change and as parameters change - rather than trying to maintain the many other transforms that use to come into play.
WIP Pull Request
See WIP :SpatialObjects V5 pull request #464
In preparation for ITKv5 release
API Changes: Philosophy
The philosophy that is driving the changes to the API is that we seek to clarify what coordinate space is being referenced when a function is called. We also want to make the interface more consistent with the rest of ITK, relying more heavily on ITK's Get/Set macros and the use of Update to harden parameter changes.
SpatialObjects have one primary coordinate space that is readily available to them, their ObjectSpace. This is the space in which the object was inherently defined. No transforms are applied to the points/values that get/set into this space. All children of an object are added into this space.
SpatialObjects have only one transform that they directly control, their ObjectToParentTransform.
WorldSpace is not directly controlled by any SpatialObject except the SpatialObject at the top level of the parent-child tree hierarchy of Spatial Objects. That is, any SpatialObject that does not have a parent exists in a WorldSpace that is defined by applying its ObjectToParentTransform to its ObjectSpace.
Several member functions and variables are available to every SpatialObject so that they can readily access the WorldSpace in which they exist:
Queries into ObjectSpace and WorldSpace
The member functions of SpatialObjects now specify if they reference ObjectSpace or WorldSpace
IsInside( point ) is now either IsInsideInObjectSpace( point ) or IsInsideInWorldSpace( point )
Other changes in the SpatialObjects base class
Other changes in top-level SpatialObject classes
Changes in SpatialObjects
The changes in specific SpatialObjects (e.g., ArrowSpatialObjects) have been extensive. Their APIs were changed to explicitly mention which space is being referenced. For example, the ArrowSpatialObject now has the member function SetDirectionInObjectSpace (rather than SetDirection). There were significant inconsistencies in such functions across SpatialObjects.
@aylward Thanks for the explanation. I made an issue to track discussions about this publically. Would you mind fixing the message of the issues/motivations for PR #464 so that we have a transparent history of this change?
This is awesome you are getting these fixes done!