Proposals:TransformIOFactory
Proposal
itk::TransformFileReader should use a factory mechanism for creating transformations.
Current capabilities
itk::TransformFileReader and itk::TransformFileWriter allow an application so save/load transformations from disk. The IO process converts the transformations to a MetaTransfrom which allows the transform parameters to be serialized in a simple manner. The file format can contain multiple transformations. For each transform in the file, the type of transformation is stored along with its parameters.
Proposed extension to ITK
We propose to extend this design to make it more flexible. Currently, the reader and writer are hardcoded to be able to serialize a known set of transformations. As such, ITK can only read/write the types of transformations were coded in the reader and writer. We propose using a TransformFactory to allow any transformation to be read/written in a generic manner. This will allow a user to add a transformation type to the factory within their application code without having to modify the TransformFileReader and TransformFileWriter. We can have a set of standard transformation registered with the factory similar to how we have a set of standard ImageIO factories registered.
This design leverages the current ITK factory mechanism. In ITK, there is a master list of registered factories. Each factory can override the creation of multiple object types. In the ImageIOFactory mechanism, there is a single factory for each file format. This allows the ImageFileReader to ask for all factories that can replace an ImageIO object. The ImageFileReader and ImageFileWriter then runs through each factory that can replace an ImageIO and queries whether that particular ImageIO can read/write the specified format.
For transformations, the process can be much simpler. A single factory can be used. This one factory will have multiple overrides, one override for each type of transformation that can be read. When the TransformFileReader reads a transform from the file, it will first read the transformation type (plain text string). The TransformFileReader will then ask the TransformFactory for an instance of a transform that matches the transformation type (plain text string). The TransformFileReader can then set the parameters on this transform using SetParameters().
The transformation type will be an encoding of the class name and the template parameters, i.e. AffineTransform_float_3_3.
This is simpler from the ImageIO mechanism because here we are asking the factory for an object to create a particular object type, i.e. AffineTransform_float_3_3 instead of asking for all objects that can create ImageIO objects.
Pseudocode
TransformBase can serve as the access point for adding new transformation types to the TransformFactory
class TransformBase { template <class T> static void RegisterTransform () { // Search for singleton, create if not existing ObjectFactorBase* f = ObjectFactoryBase::find( "TransformFactory" ); f->RegisterOverride ( T::GenerateName(), // gives AffineTransform_float_3_3 T::GenerateName(), "Create a " + T::GetName(), 1, CreateObjectFunction<T>::New()); } }
This allows a user to add a transformation type to the TransformFactory using a single line of code
TransformBase::RegisterTransform<MyNewTransformType>();
The TransformFileWriter could register a set of default transformation types.
class TransformWriter { TransformWriter () { if ( !initialized ) { TransformBase::TransformFactory->RegisterOverride (); RegisterTransform <Affine<double,3,3> >(); TransformFactory::RegisterTransform <Affine<float,3,3> >(); TransformFactory::RegisterTransform ( Affine<float,3,3> >::GenerateName(), CreateObjectFunction < Affine < float, 3,3> >::New() ); TransformFactory::RegisterTransform <Affine<double,2,2> >(); TransformFactory::RegisterTransform <Affine<double,3,3> >(); } } }
The Write() method become very simple. It just runs through each trasformation in the list to be written and writes out the tranformation type and its parameters.
void Write () { foreach t in m_TransformList { out << t::GenerateName() << " " << t->GetParameters() << " " << t->GetFixedParamaters(); } }
The Read() method is a little more complicated. It has to read the transformation type then ask the factory to create an object of that type (passing in the transformation type as a string "AffineTransform_float_3_3").
void Read () { foreach line in File { Name << line; Parameters << line; FixedParameters << line; TransformBase* xform = ObjectFactoryBase::CreateInstance ( Name ); xform->SetParameters ( Parameters ); // What to do w/BSpline xform->SetFixedParameters ( FixedParameters ); m_TransformList.push_back ( xform ); } }