From 03b5cd610d6dd191b8c43c31300a9fa25d8384f5 Mon Sep 17 00:00:00 2001
From: Matt McCormick <matt.mccormick@kitware.com>
Date: Tue, 2 Jul 2024 13:29:58 -0400
Subject: [PATCH] feat: wasm transform interface classes

---
 include/itkTransformJSON.h                    | 225 +++++++++++-
 include/itkTransformToWasmTransformFilter.h   |  92 +++++
 include/itkTransformToWasmTransformFilter.hxx | 128 +++++++
 include/itkWasmTransform.h                    |  84 +++++
 include/itkWasmTransform.hxx                  |  40 ++
 include/itkWasmTransformIO.h                  |   3 -
 include/itkWasmTransformToTransformFilter.h   | 108 ++++++
 include/itkWasmTransformToTransformFilter.hxx | 267 ++++++++++++++
 include/itktransformParameterizationString.h  |  34 ++
 src/CMakeLists.txt                            |   1 +
 src/itkWasmTransformIO.cxx                    | 345 +-----------------
 src/itktransformParameterizationString.cxx    | 173 +++++++++
 test/CMakeLists.txt                           |  16 +
 ...itkWasmTransformInterfaceCompositeTest.cxx |  89 +++++
 test/itkWasmTransformInterfaceTest.cxx        |  89 +++++
 15 files changed, 1354 insertions(+), 340 deletions(-)
 create mode 100644 include/itkTransformToWasmTransformFilter.h
 create mode 100644 include/itkTransformToWasmTransformFilter.hxx
 create mode 100644 include/itkWasmTransform.h
 create mode 100644 include/itkWasmTransform.hxx
 create mode 100644 include/itkWasmTransformToTransformFilter.h
 create mode 100644 include/itkWasmTransformToTransformFilter.hxx
 create mode 100644 include/itktransformParameterizationString.h
 create mode 100644 src/itktransformParameterizationString.cxx
 create mode 100644 test/itkWasmTransformInterfaceCompositeTest.cxx
 create mode 100644 test/itkWasmTransformInterfaceTest.cxx

diff --git a/include/itkTransformJSON.h b/include/itkTransformJSON.h
index e2a72dcb5..f75b94c03 100644
--- a/include/itkTransformJSON.h
+++ b/include/itkTransformJSON.h
@@ -22,6 +22,8 @@
 
 #include <vector>
 
+#include "itkCompositeTransformIOHelper.h"
+
 #include "glaze/glaze.hpp"
 
 namespace itk
@@ -96,6 +98,227 @@ namespace itk
    * \ingroup WebAssemblyInterface
    */
   using TransformListJSON = std::list<TransformJSON>;
+
+template<typename TTransformBase>
+auto transformListToTransformListJSON(std::list<typename TTransformBase::ConstPointer> & transformList, bool inMemory) -> TransformListJSON
+{
+  TransformListJSON transformListJSON;
+
+  std::string              compositeTransformType = transformList.front()->GetTransformTypeAsString();
+  using TransformBaseType = TTransformBase;
+  using ParametersValueType = typename TransformBaseType::ParametersValueType;
+  CompositeTransformIOHelperTemplate<ParametersValueType> helper;
+
+  using ConstTransformListType = std::list<typename TransformBaseType::ConstPointer>;
+  ConstTransformListType usedTransformList = transformList;
+
+  //
+  // if the first transform in the list is a
+  // composite transform, use its internal list
+  // instead of the IO
+  if (compositeTransformType.find("CompositeTransform") != std::string::npos)
+  {
+    usedTransformList = helper.GetTransformList(transformList.front().GetPointer());
+  }
+
+  unsigned int count = 0;
+  typename ConstTransformListType::const_iterator end = usedTransformList.end();
+  for (typename ConstTransformListType::const_iterator it = usedTransformList.begin(); it != end; ++it)
+  {
+    TransformJSON            transformJSON;
+    const TransformBaseType *    currentTransform = it->GetPointer();
+    const std::string        transformType = currentTransform->GetTransformTypeAsString();
+    const std::string        delim = "_";
+    std::vector<std::string> tokens;
+    size_t                   start = 0;
+    size_t                   end = 0;
+    while ((end = transformType.find(delim, start)) != std::string::npos)
+    {
+      tokens.push_back(transformType.substr(start, end - start));
+      start = end + delim.length();
+    }
+    tokens.push_back(transformType.substr(start));
+    const std::string pString = tokens[0];
+    if (pString == "IdentityTransform")
+    {
+      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Identity;
+    }
+    else if (pString == "CompositeTransform")
+    {
+      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Composite;
+    }
+    else if (pString == "TranslationTransform")
+    {
+      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Translation;
+    }
+    else if (pString == "Euler2DTransform")
+    {
+      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Euler2D;
+    }
+    else if (pString == "Euler3DTransform")
+    {
+      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Euler3D;
+    }
+    else if (pString == "Rigid2DTransform")
+    {
+      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Rigid2D;
+    }
+    else if (pString == "Rigid3DPerspectiveTransform")
+    {
+      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Rigid3DPerspective;
+    }
+    else if (pString == "VersorRigid3DTransform")
+    {
+      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::VersorRigid3D;
+    }
+    else if (pString == "Versor")
+    {
+      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Versor;
+    }
+    else if (pString == "ScaleLogarithmicTransform")
+    {
+      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::ScaleLogarithmic;
+    }
+    else if (pString == "ScaleSkewVersor3DTransform")
+    {
+      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::ScaleSkewVersor3D;
+    }
+    else if (pString == "ScaleTransform")
+    {
+      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Scale;
+    }
+    else if (pString == "Similarity2DTransform")
+    {
+      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Similarity2D;
+    }
+    else if (pString == "Similarity3DTransform")
+    {
+      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Similarity3D;
+    }
+    else if (pString == "QuaternionRigidTransform")
+    {
+      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::QuaternionRigid;
+    }
+    else if (pString == "AffineTransform")
+    {
+      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Affine;
+    }
+    else if (pString == "ScalableAffineTransform")
+    {
+      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::ScalableAffine;
+    }
+    else if (pString == "AzimuthElevationToCartesianTransform")
+    {
+      transformJSON.transformType.transformParameterization =
+        JSONTransformParameterizationEnum::AzimuthElevationToCartesian;
+    }
+    else if (pString == "BSplineTransform")
+    {
+      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::BSpline;
+    }
+    else if (pString == "BSplineSmoothingOnUpdateDisplacementFieldTransform")
+    {
+      transformJSON.transformType.transformParameterization =
+        JSONTransformParameterizationEnum::BSplineSmoothingOnUpdateDisplacementField;
+    }
+    else if (pString == "ConstantVelocityFieldTransform")
+    {
+      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::ConstantVelocityField;
+    }
+    else if (pString == "DisplacementFieldTransform")
+    {
+      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::DisplacementField;
+    }
+    else if (pString == "GaussianExponentialDiffeomorphicTransform")
+    {
+      transformJSON.transformType.transformParameterization =
+        JSONTransformParameterizationEnum::GaussianExponentialDiffeomorphic;
+    }
+    else if (pString == "GaussianSmoothingOnUpdateDisplacementFieldTransform")
+    {
+      transformJSON.transformType.transformParameterization =
+        JSONTransformParameterizationEnum::GaussianSmoothingOnUpdateDisplacementField;
+    }
+    else if (pString == "GaussianSmoothingOnUpdateTimeVaryingVelocityFieldTransform")
+    {
+      transformJSON.transformType.transformParameterization =
+        JSONTransformParameterizationEnum::GaussianSmoothingOnUpdateTimeVaryingVelocityField;
+    }
+    else if (pString == "TimeVaryingVelocityFieldTransform")
+    {
+      transformJSON.transformType.transformParameterization =
+        JSONTransformParameterizationEnum::TimeVaryingVelocityField;
+    }
+    else if (pString == "VelocityFieldTransform")
+    {
+      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::VelocityField;
+    }
+    else
+    {
+      throw std::logic_error("Unknown transform type: " + pString);
+    }
+
+    constexpr size_t parametersSize = sizeof(ParametersValueType);
+    if (parametersSize == 4)
+    {
+      transformJSON.transformType.parametersValueType = JSONFloatTypesEnum::float32;
+    }
+    else if (parametersSize == 8)
+    {
+      transformJSON.transformType.parametersValueType = JSONFloatTypesEnum::float64;
+    }
+    else
+    {
+      throw std::logic_error("Unknown parameters value type");
+    }
+
+    transformJSON.transformType.inputDimension = currentTransform->GetInputSpaceDimension();
+    transformJSON.transformType.outputDimension = currentTransform->GetOutputSpaceDimension();
+
+    transformJSON.numberOfFixedParameters = currentTransform->GetFixedParameters().Size();
+    transformJSON.numberOfParameters = currentTransform->GetParameters().Size();
+    transformJSON.name = currentTransform->GetObjectName();
+    // Todo: needs to be pushed from itk::Transform to itk::TransformBase
+    // Available in ITK 5.4.1 and later
+    // https://github.com/InsightSoftwareConsortium/ITK/pull/4734
+    // transformJSON.inputSpaceName = currentTransform->GetInputSpaceName();
+    // transformJSON.inputSpaceName = currentTransform->GetOutputSpaceName();
+    if (inMemory)
+    {
+      if (pString == "CompositeTransform")
+      {
+        // For composite transforms, we don't store the parameters in memory directly
+        transformJSON.fixedParameters = "data:application/vnd.itk.address,0:0";
+        transformJSON.parameters = "data:application/vnd.itk.address,0:0";
+      }
+      else
+      {
+        std::ostringstream fixedParametersStream;
+        fixedParametersStream << "data:application/vnd.itk.address,0:";
+        const auto fixedParametersAddr = reinterpret_cast< size_t >( currentTransform->GetFixedParameters().data_block() );
+        fixedParametersStream << fixedParametersAddr;
+        transformJSON.fixedParameters = fixedParametersStream.str();
+
+        std::ostringstream parametersStream;
+        parametersStream << "data:application/vnd.itk.address,0:";
+        const auto parametersAddr = reinterpret_cast< size_t >( currentTransform->GetParameters().data_block() );
+        parametersStream << parametersAddr;
+        transformJSON.parameters = parametersStream.str();
+      }
+    }
+    else
+    {
+      transformJSON.fixedParameters = "data:application/vnd.itk.path,data/" + std::to_string(count) + "/fixed-parameters.raw";
+      transformJSON.parameters = "data:application/vnd.itk.path,data/" + std::to_string(count) + "/parameters.raw";
+    }
+
+    transformListJSON.push_back(transformJSON);
+    ++count;
+  }
+
+  return transformListJSON;
+}
+
 } // end namespace itk
 
 template <>
@@ -131,4 +354,4 @@ struct glz::meta<itk::JSONTransformParameterizationEnum> {
   );
 };
 
-#endif // itkWasmTransformIO_h
+#endif // itkTransformJSON_h
diff --git a/include/itkTransformToWasmTransformFilter.h b/include/itkTransformToWasmTransformFilter.h
new file mode 100644
index 000000000..0eb539c5d
--- /dev/null
+++ b/include/itkTransformToWasmTransformFilter.h
@@ -0,0 +1,92 @@
+/*=========================================================================
+ *
+ *  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 itkTransformToWasmTransformFilter_h
+#define itkTransformToWasmTransformFilter_h
+
+#include "itkProcessObject.h"
+#include "itkWasmTransform.h"
+
+#include "itkDataObjectDecorator.h"
+
+namespace itk
+{
+/**
+ *\class TransformToWasmTransformFilter
+ * \brief Convert an Transform to an WasmTransform object.
+ * 
+ * \ingroup WebAssemblyInterface
+ */
+template <typename TTransform>
+class ITK_TEMPLATE_EXPORT TransformToWasmTransformFilter : public ProcessObject
+{
+public:
+  ITK_DISALLOW_COPY_AND_MOVE(TransformToWasmTransformFilter);
+
+  /** Standard class type aliases. */
+  using Self = TransformToWasmTransformFilter;
+  using Superclass = ProcessObject;
+  using Pointer = SmartPointer<Self>;
+  using ConstPointer = SmartPointer<const Self>;
+
+  /** Method for creation through the object factory. */
+  itkNewMacro(Self);
+
+  /** Run-time type information (and related methods). */
+  itkTypeMacro(TransformToWasmTransformFilter, ProcessObject);
+
+  using DataObjectIdentifierType = Superclass::DataObjectIdentifierType;
+  using DataObjectPointerArraySizeType = Superclass::DataObjectPointerArraySizeType;
+
+  using TransformType = TTransform;
+  using WasmTransformType = WasmTransform<TransformType>;
+
+  itkSetGetDecoratedObjectInputMacro(Transform, TransformType);
+
+  WasmTransformType *
+  GetOutput();
+  const WasmTransformType *
+  GetOutput() const;
+
+  WasmTransformType *
+  GetOutput(unsigned int idx);
+
+protected:
+  TransformToWasmTransformFilter();
+  ~TransformToWasmTransformFilter() override = default;
+
+  ProcessObject::DataObjectPointer
+  MakeOutput(ProcessObject::DataObjectPointerArraySizeType idx) override;
+  ProcessObject::DataObjectPointer
+  MakeOutput(const ProcessObject::DataObjectIdentifierType &) override;
+
+  void
+  GenerateOutputInformation() override
+  {} // do nothing
+  void
+  GenerateData() override;
+
+  void
+  PrintSelf(std::ostream & os, Indent indent) const override;
+};
+} // end namespace itk
+
+#ifndef ITK_MANUAL_INSTANTIATION
+#  include "itkTransformToWasmTransformFilter.hxx"
+#endif
+
+#endif
diff --git a/include/itkTransformToWasmTransformFilter.hxx b/include/itkTransformToWasmTransformFilter.hxx
new file mode 100644
index 000000000..74ef42bca
--- /dev/null
+++ b/include/itkTransformToWasmTransformFilter.hxx
@@ -0,0 +1,128 @@
+/*=========================================================================
+ *
+ *  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 itkTransformToWasmTransformFilter_hxx
+#define itkTransformToWasmTransformFilter_hxx
+
+#include "itkCompositeTransform.h"
+#include "itkCompositeTransformIOHelper.h"
+
+#include "itkWasmMapComponentType.h"
+#include "itkWasmMapPixelType.h"
+#include "itkTransformJSON.h"
+
+namespace itk
+{
+
+template <typename TTransform>
+TransformToWasmTransformFilter<TTransform>
+::TransformToWasmTransformFilter()
+{
+  this->SetNumberOfRequiredInputs(1);
+  this->SetPrimaryInputName("Transform");
+
+  typename WasmTransformType::Pointer output = static_cast<WasmTransformType *>(this->MakeOutput(0).GetPointer());
+  this->ProcessObject::SetNumberOfRequiredOutputs(1);
+  this->ProcessObject::SetNthOutput(0, output.GetPointer());
+}
+
+template <typename TTransform>
+ProcessObject::DataObjectPointer
+TransformToWasmTransformFilter<TTransform>
+::MakeOutput(ProcessObject::DataObjectPointerArraySizeType)
+{
+  return WasmTransformType::New().GetPointer();
+}
+
+template <typename TTransform>
+ProcessObject::DataObjectPointer
+TransformToWasmTransformFilter<TTransform>
+::MakeOutput(const ProcessObject::DataObjectIdentifierType &)
+{
+  return WasmTransformType::New().GetPointer();
+}
+
+template <typename TTransform>
+auto
+TransformToWasmTransformFilter<TTransform>
+::GetOutput() -> WasmTransformType *
+{
+  // we assume that the first output is of the templated type
+  return itkDynamicCastInDebugMode<WasmTransformType *>(this->GetPrimaryOutput());
+}
+
+template <typename TTransform>
+auto
+TransformToWasmTransformFilter<TTransform>
+::GetOutput() const -> const WasmTransformType *
+{
+  // we assume that the first output is of the templated type
+  return itkDynamicCastInDebugMode<const WasmTransformType *>(this->GetPrimaryOutput());
+}
+
+template <typename TTransform>
+auto
+TransformToWasmTransformFilter<TTransform>
+::GetOutput(unsigned int idx) -> WasmTransformType *
+{
+  auto * out = dynamic_cast<WasmTransformType *>(this->ProcessObject::GetOutput(idx));
+
+  if (out == nullptr && this->ProcessObject::GetOutput(idx) != nullptr)
+  {
+    itkWarningMacro(<< "Unable to convert output number " << idx << " to type " << typeid(WasmTransformType).name());
+  }
+  return out;
+}
+
+template <typename TTransform>
+void
+TransformToWasmTransformFilter<TTransform>
+::GenerateData()
+{
+  // Get the input and output pointers
+  const TransformType * transform = this->GetTransform();
+  WasmTransformType * wasmTransform = this->GetOutput();
+
+  wasmTransform->SetTransform(transform);
+
+  using TransformType = TTransform;
+  using ParametersValueType = typename TransformType::ParametersValueType;
+  using TransformBaseType = TransformBaseTemplate<ParametersValueType>;
+  std::list<typename TransformBaseType::ConstPointer> transformList { transform };
+  constexpr bool inMemory = true;
+  const TransformListJSON transformListJSON = transformListToTransformListJSON<TransformBaseType>(transformList, inMemory);
+
+  std::string serialized{};
+  auto ec = glz::write<glz::opts{ .prettify = true }>(transformListJSON, serialized);
+  if (ec)
+  {
+    itkExceptionMacro("Failed to serialize TransformListJSON");
+  }
+
+  wasmTransform->SetJSON(serialized);
+}
+
+template <typename TTransform>
+void
+TransformToWasmTransformFilter<TTransform>
+::PrintSelf(std::ostream & os, Indent indent) const
+{
+  Superclass::PrintSelf(os, indent);
+}
+} // end namespace itk
+
+#endif
diff --git a/include/itkWasmTransform.h b/include/itkWasmTransform.h
new file mode 100644
index 000000000..ce0f11c69
--- /dev/null
+++ b/include/itkWasmTransform.h
@@ -0,0 +1,84 @@
+/*=========================================================================
+ *
+ *  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 itkWasmTransform_h
+#define itkWasmTransform_h
+
+#include "itkWasmDataObject.h"
+#include "itkDataObjectDecorator.h"
+
+namespace itk
+{
+/**
+ *\class WasmTransform
+ * \brief JSON representation for an itk::Transform
+ *
+ * JSON representation for an itk::Transform for interfacing across programming languages and runtimes.
+ *
+ * FixedParameters and Parameters binary array buffer's are stored as strings with memory addresses or paths on disks or a virtual filesystem.
+ *
+ * - 0: FixedParameters for transform 0
+ * - 1: Parameters for transform 0
+ * - 2: FixedParameters for transform 1
+ * - 3: Parameters for transform 1
+ * - 4: FixedParameters for transform 2
+ * - 5: Parameters for transform 2
+ * [...]
+ * 
+ * where multiple FixedParameters/Parameters pairs are present for CompositeTransform's.
+ *
+ * \ingroup WebAssemblyInterface
+ */
+template <typename TTransform>
+class ITK_TEMPLATE_EXPORT WasmTransform : public WasmDataObject
+{
+public:
+  ITK_DISALLOW_COPY_AND_MOVE(WasmTransform);
+
+  /** Standard class type aliases. */
+  using Self = WasmTransform;
+  using Superclass = WasmDataObject;
+  using Pointer = SmartPointer<Self>;
+  using ConstPointer = SmartPointer<const Self>;
+
+  itkNewMacro(Self);
+  /** Run-time type information (and related methods). */
+  itkTypeMacro(WasmTransform, WasmDataObject);
+
+  using TransformType = TTransform;
+  using DecoratorType = DataObjectDecorator<TransformType>;
+
+  void SetTransform(const TransformType * transform);
+
+  const TransformType * GetTransform() const {
+    return static_cast< const DecoratorType * >(this->GetDataObject())->Get();
+  }
+
+protected:
+  WasmTransform()
+  {}
+  ~WasmTransform() override = default;
+
+};
+
+} // namespace itk
+
+#ifndef ITK_MANUAL_INSTANTIATION
+#  include "itkWasmTransform.hxx"
+#endif
+
+#endif
diff --git a/include/itkWasmTransform.hxx b/include/itkWasmTransform.hxx
new file mode 100644
index 000000000..00fd8322b
--- /dev/null
+++ b/include/itkWasmTransform.hxx
@@ -0,0 +1,40 @@
+/*=========================================================================
+ *
+ *  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 itkWasmTransform_hxx
+#define itkWasmTransform_hxx
+
+#include "itkWasmTransform.h"
+#include "itkDataObjectDecorator.h"
+
+namespace itk
+{
+
+template <typename TTransform>
+void
+WasmTransform<TTransform>
+::SetTransform(const TransformType * transform)
+{
+  using DecoratorType = DataObjectDecorator<TransformType>;
+  typename DecoratorType::Pointer decorator = DecoratorType::New();
+  decorator->Set(transform);
+  this->SetDataObject(decorator);
+}
+
+} // end namespace itk
+
+#endif
diff --git a/include/itkWasmTransformIO.h b/include/itkWasmTransformIO.h
index eb5b15c1e..7a5a5c846 100644
--- a/include/itkWasmTransformIO.h
+++ b/include/itkWasmTransformIO.h
@@ -116,9 +116,6 @@ class ITK_TEMPLATE_EXPORT WasmTransformIOTemplate : public TransformIOBaseTempla
   void
   WriteCBOR();
 
-  static std::string
-  TransformParameterizationString(const TransformTypeJSON & transform);
-
   cbor_item_t * m_CBORRoot{ nullptr };
 
 private:
diff --git a/include/itkWasmTransformToTransformFilter.h b/include/itkWasmTransformToTransformFilter.h
new file mode 100644
index 000000000..f1a8ea640
--- /dev/null
+++ b/include/itkWasmTransformToTransformFilter.h
@@ -0,0 +1,108 @@
+/*=========================================================================
+ *
+ *  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 itkWasmTransformToTransformFilter_h
+#define itkWasmTransformToTransformFilter_h
+
+#include "itkProcessObject.h"
+#include "itkWasmTransform.h"
+#include "itkDataObjectDecorator.h"
+
+namespace itk
+{
+/**
+ *\class WasmTransformToTransformFilter
+ * \brief Convert an WasmTransform to an Transform object.
+ *
+ * TTransform must match the type stored in the JSON representation or an exception will be shown.
+ *
+ * \ingroup WebAssemblyInterface
+ */
+template <typename TTransform>
+class ITK_TEMPLATE_EXPORT WasmTransformToTransformFilter : public ProcessObject
+{
+public:
+  ITK_DISALLOW_COPY_AND_MOVE(WasmTransformToTransformFilter);
+
+  /** Standard class type aliases. */
+  using Self = WasmTransformToTransformFilter;
+  using Superclass = ProcessObject;
+  using Pointer = SmartPointer<Self>;
+  using ConstPointer = SmartPointer<const Self>;
+
+  /** Method for creation through the object factory. */
+  itkNewMacro(Self);
+
+  /** Run-time type information (and related methods). */
+  itkTypeMacro(WasmTransformToTransformFilter, ProcessObject);
+
+  using DataObjectIdentifierType = Superclass::DataObjectIdentifierType;
+  using DataObjectPointerArraySizeType = Superclass::DataObjectPointerArraySizeType;
+
+  using TransformType = TTransform;
+  using DecoratorType = DataObjectDecorator<TransformType>;
+  using WasmTransformType = WasmTransform<TransformType>;
+
+  /** Set/Get the path input of this process object.  */
+  using Superclass::SetInput;
+  virtual void
+  SetInput(const WasmTransformType * transform);
+
+  virtual void
+  SetInput(unsigned int, const WasmTransformType * transform);
+
+  const WasmTransformType *
+  GetInput();
+
+  const WasmTransformType *
+  GetInput(unsigned int idx);
+
+  TransformType *
+  GetOutput();
+  const TransformType *
+  GetOutput() const;
+
+  TransformType *
+  GetOutput(unsigned int idx);
+
+protected:
+  WasmTransformToTransformFilter();
+  ~WasmTransformToTransformFilter() override = default;
+
+  ProcessObject::DataObjectPointer
+  MakeOutput(ProcessObject::DataObjectPointerArraySizeType idx) override;
+  ProcessObject::DataObjectPointer
+  MakeOutput(const ProcessObject::DataObjectIdentifierType &) override;
+
+  void
+  GenerateOutputInformation() override
+  {} // do nothing
+  void
+  GenerateData() override;
+
+  void
+  PrintSelf(std::ostream & os, Indent indent) const override;
+
+  typename TransformType::Pointer m_OutputTransform;
+};
+} // end namespace itk
+
+#ifndef ITK_MANUAL_INSTANTIATION
+#  include "itkWasmTransformToTransformFilter.hxx"
+#endif
+
+#endif
diff --git a/include/itkWasmTransformToTransformFilter.hxx b/include/itkWasmTransformToTransformFilter.hxx
new file mode 100644
index 000000000..4890d6dfa
--- /dev/null
+++ b/include/itkWasmTransformToTransformFilter.hxx
@@ -0,0 +1,267 @@
+/*=========================================================================
+ *
+ *  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 itkWasmTransformToTransformFilter_hxx
+#define itkWasmTransformToTransformFilter_hxx
+
+#include "itkTransformFactoryBase.h"
+
+#include "itktransformParameterizationString.h"
+#include <exception>
+#include "itkWasmMapComponentType.h"
+#include "itkWasmMapPixelType.h"
+
+namespace itk
+{
+
+template <typename TTransform>
+WasmTransformToTransformFilter<TTransform>
+::WasmTransformToTransformFilter()
+{
+  this->SetNumberOfRequiredInputs(1);
+
+  typename DecoratorType::Pointer output = static_cast<DecoratorType *>(this->MakeOutput(0).GetPointer());
+  this->ProcessObject::SetNumberOfRequiredOutputs(1);
+  this->ProcessObject::SetNthOutput(0, output.GetPointer());
+}
+
+template <typename TTransform>
+ProcessObject::DataObjectPointer
+WasmTransformToTransformFilter<TTransform>
+::MakeOutput(ProcessObject::DataObjectPointerArraySizeType)
+{
+  typename DecoratorType::Pointer decorator = DecoratorType::New();
+  this->m_OutputTransform = TransformType::New();
+  decorator->Set(m_OutputTransform);
+  return decorator.GetPointer();
+}
+
+template <typename TTransform>
+ProcessObject::DataObjectPointer
+WasmTransformToTransformFilter<TTransform>
+::MakeOutput(const ProcessObject::DataObjectIdentifierType &)
+{
+  typename DecoratorType::Pointer decorator = DecoratorType::New();
+  this->m_OutputTransform = TransformType::New();
+  decorator->Set(m_OutputTransform);
+  return decorator.GetPointer();
+}
+
+template <typename TTransform>
+auto
+WasmTransformToTransformFilter<TTransform>
+::GetOutput() -> TransformType *
+{
+  return this->m_OutputTransform;
+}
+
+template <typename TTransform>
+auto
+WasmTransformToTransformFilter<TTransform>
+::GetOutput() const -> const TransformType *
+{
+  return const_cast<const TransformType *>(this->m_OutputTransform);
+}
+
+template <typename TTransform>
+auto
+WasmTransformToTransformFilter<TTransform>
+::GetOutput(unsigned int idx) -> TransformType *
+{
+  auto * out = dynamic_cast<DecoratorType *>(this->ProcessObject::GetOutput(idx));
+
+  if (out == nullptr && this->ProcessObject::GetOutput(idx) != nullptr)
+  {
+    itkWarningMacro(<< "Unable to convert output number " << idx << " to type " << typeid(DecoratorType).name());
+  }
+  return out->Get();
+}
+
+template <typename TTransform>
+void
+WasmTransformToTransformFilter<TTransform>
+::SetInput(const WasmTransformType * input)
+{
+  // Process object is not const-correct so the const_cast is required here
+  this->ProcessObject::SetNthInput(0, const_cast<WasmTransformType *>(input));
+}
+
+template <typename TTransform>
+void
+WasmTransformToTransformFilter<TTransform>
+::SetInput(unsigned int index, const WasmTransformType * transform)
+{
+  // Process object is not const-correct so the const_cast is required here
+  this->ProcessObject::SetNthInput(index, const_cast<WasmTransformType *>(transform));
+}
+
+template <typename TTransform>
+const typename WasmTransformToTransformFilter<TTransform>::WasmTransformType *
+WasmTransformToTransformFilter<TTransform>
+::GetInput()
+{
+  return itkDynamicCastInDebugMode<const WasmTransformType *>(this->GetPrimaryInput());
+}
+
+template <typename TTransform>
+const typename WasmTransformToTransformFilter<TTransform>::WasmTransformType *
+WasmTransformToTransformFilter<TTransform>
+::GetInput(unsigned int idx)
+{
+  return itkDynamicCastInDebugMode<const TTransform *>(this->ProcessObject::GetInput(idx));
+}
+
+template <typename TTransform>
+void
+WasmTransformToTransformFilter<TTransform>
+::GenerateData()
+{
+  using ParametersValueType = typename TTransform::ParametersValueType;
+  using FixedParametersValueType = typename TTransform::FixedParametersValueType;
+
+  // Get the input and output pointers
+  const WasmTransformType * wasmTransform = this->GetInput();
+  auto deserializedAttempt = glz::read_json<TransformListJSON>(wasmTransform->GetJSON());
+  if (!deserializedAttempt)
+  {
+    itkExceptionMacro("Failed to deserialize TransformListJSON");
+  }
+  auto transformListJSON = deserializedAttempt.value();
+  if (transformListJSON.size() < 1)
+  {
+    itkExceptionMacro("Expected at least one transform in the list");
+  }
+  TransformType * transform = this->GetOutput();
+  unsigned int count = 0;
+  bool isComposite = false;
+  for (const auto & transformJSON : transformListJSON)
+  {
+    transform->SetObjectName(transformJSON.name);
+    transform->SetInputSpaceName(transformJSON.inputSpaceName);
+    transform->SetOutputSpaceName(transformJSON.outputSpaceName);
+
+    if (transformJSON.transformType.transformParameterization == JSONTransformParameterizationEnum::Composite)
+    {
+      isComposite = true;
+      ++count;
+      continue;
+    }
+
+    using ParametersValueType = typename TransformType::ParametersValueType;
+    std::string transformPrecision;
+    switch (transformJSON.transformType.parametersValueType)
+    {
+      case JSONFloatTypesEnum::float32:
+      {
+        transformPrecision = "float";
+        if (sizeof(ParametersValueType) != 4)
+        {
+          itkExceptionMacro("ParametersValueType does not match JSON transformType");
+        }
+        break;
+      }
+      case JSONFloatTypesEnum::float64:
+      {
+        transformPrecision = "double";
+        if (sizeof(ParametersValueType) != 8)
+        {
+          itkExceptionMacro("ParametersValueType does not match JSON transformType");
+        }
+        break;
+      }
+      default:
+      {
+        itkExceptionMacro("Unknown parameters value type");
+      }
+    }
+    const std::string transformParameterization = transformParameterizationString(transformJSON.transformType);
+    if (transform->GetInputSpaceDimension() != transformJSON.transformType.inputDimension)
+    {
+      itkExceptionMacro("InputSpaceDimension does not match JSON transformType");
+    }
+    if (transform->GetOutputSpaceDimension() != transformJSON.transformType.outputDimension)
+    {
+      itkExceptionMacro("OutputSpaceDimension does not match JSON transformType");
+    }
+    // itk::Transform<TParametersValueType, VInputDimension, VOutputDimension>::GetTransformTypeAsString() returns the
+    // transform type string Note: non-cubic B-Splines not supported
+    std::string transformType = transformParameterization + "Transform_" + transformPrecision + "_" +
+                                std::to_string(transformJSON.transformType.inputDimension) + "_" +
+                                std::to_string(transformJSON.transformType.outputDimension);
+
+    if (isComposite)
+    {
+      // call to GetFactory has side effect of initializing the
+      // TransformFactory overrides
+      using ComponentTransformType = Transform<ParametersValueType, TransformType::InputSpaceDimension, TransformType::OutputSpaceDimension>;
+      typename ComponentTransformType::Pointer ptr;
+      // TransformIOBaseTemplate::CreateTransform(ptr, transformType);
+      TransformFactoryBase * theFactory = TransformFactoryBase::GetFactory();
+
+      // Instantiate the transform
+      itkDebugMacro("About to call ObjectFactory");
+      LightObject::Pointer i = ObjectFactoryBase::CreateInstance(transformType.c_str());
+      itkDebugMacro("After call ObjectFactory");
+      ptr = dynamic_cast<ComponentTransformType *>(i.GetPointer());
+      if (ptr.IsNull())
+      {
+        std::ostringstream msg;
+        msg << "Could not create an instance of \"" << transformType << '"' << std::endl
+            << "The usual cause of this error is not registering the "
+            << "transform with TransformFactory" << std::endl;
+        msg << "Currently registered Transforms: " << std::endl;
+        std::list<std::string> names = theFactory->GetClassOverrideWithNames();
+        for (auto & name : names)
+        {
+          msg << "\t\"" << name << '"' << std::endl;
+        }
+        itkExceptionMacro(<< msg.str());
+      }
+      // Correct extra reference count from CreateInstance()
+      ptr->UnRegister();
+
+      FixedParametersValueType * fixedPtr = reinterpret_cast< FixedParametersValueType * >( std::strtoull(transformJSON.fixedParameters.substr(35).c_str(), nullptr, 10) );
+      ptr->CopyInFixedParameters(fixedPtr, fixedPtr + transformJSON.numberOfFixedParameters);
+      ParametersValueType * paramsPtr = reinterpret_cast< ParametersValueType * >( std::strtoull(transformJSON.parameters.substr(35).c_str(), nullptr, 10) );
+      ptr->CopyInParameters(paramsPtr, paramsPtr + transformJSON.numberOfParameters);
+
+      using CompositeTransformType = CompositeTransform<ParametersValueType, TransformType::InputSpaceDimension>;
+      CompositeTransformType * compositeTransform = dynamic_cast<CompositeTransformType *>(transform);
+      compositeTransform->AddTransform(ptr);
+    }
+    else
+    {
+      FixedParametersValueType * fixedPtr = reinterpret_cast< FixedParametersValueType * >( std::strtoull(transformJSON.fixedParameters.substr(35).c_str(), nullptr, 10) );
+      transform->CopyInFixedParameters(fixedPtr, fixedPtr + transformJSON.numberOfFixedParameters);
+      ParametersValueType * paramsPtr = reinterpret_cast< ParametersValueType * >( std::strtoull(transformJSON.parameters.substr(35).c_str(), nullptr, 10) );
+      transform->CopyInParameters(paramsPtr, paramsPtr + transformJSON.numberOfParameters);
+    }
+
+    ++count;
+  }
+}
+
+template <typename TTransform>
+void
+WasmTransformToTransformFilter<TTransform>
+::PrintSelf(std::ostream & os, Indent indent) const
+{
+  Superclass::PrintSelf(os, indent);
+}
+} // end namespace itk
+
+#endif
diff --git a/include/itktransformParameterizationString.h b/include/itktransformParameterizationString.h
new file mode 100644
index 000000000..086aa26bd
--- /dev/null
+++ b/include/itktransformParameterizationString.h
@@ -0,0 +1,34 @@
+/*=========================================================================
+ *
+ *  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 itktransformParameterizationString_h
+#define itktransformParameterizationString_h
+
+#include "WebAssemblyInterfaceExport.h"
+
+#include <string>
+
+#include "itkTransformJSON.h"
+
+namespace itk
+{
+
+const std::string
+WebAssemblyInterface_EXPORT transformParameterizationString(const TransformTypeJSON & json);
+
+} // namespace itk
+#endif // itktransformParameterizationString_h
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index f474dd77c..31d662c05 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -26,6 +26,7 @@ set(WebAssemblyInterface_SRCS
   itkSupportInputImageTypes.cxx
   itkSupportInputMeshTypes.cxx
   itkSupportInputPolyDataTypes.cxx
+  itktransformParameterizationString.cxx
   )
 itk_module_add_library(WebAssemblyInterface ${WebAssemblyInterface_SRCS})
 target_link_libraries(WebAssemblyInterface LINK_PUBLIC cbor cpp-base64)
diff --git a/src/itkWasmTransformIO.cxx b/src/itkWasmTransformIO.cxx
index a45a19a61..299ebdf1b 100644
--- a/src/itkWasmTransformIO.cxx
+++ b/src/itkWasmTransformIO.cxx
@@ -25,11 +25,8 @@
 #include "itkMakeUniqueForOverwrite.h"
 #include <sstream>
 
-#include "itkWasmComponentTypeFromIOComponentEnum.h"
-#include "itkIOComponentEnumFromWasmComponentType.h"
-#include "itkWasmPixelTypeFromIOPixelEnum.h"
-#include "itkIOPixelEnumFromWasmPixelType.h"
 #include "itkWasmIOCommon.h"
+#include "itktransformParameterizationString.h"
 
 #include "itkMetaDataObject.h"
 #include "itkIOCommon.h"
@@ -441,340 +438,13 @@ template <typename TParametersValueType>
 auto
 WasmTransformIOTemplate<TParametersValueType>::GetJSON() -> TransformListJSON
 {
-  TransformListJSON transformListJSON;
-
   ConstTransformListType & transformList = this->GetWriteTransformList();
-  std::string              compositeTransformType = transformList.front()->GetTransformTypeAsString();
-  CompositeTransformIOHelperTemplate<TParametersValueType> helper;
-
-  //
-  // if the first transform in the list is a
-  // composite transform, use its internal list
-  // instead of the IO
-  if (compositeTransformType.find("CompositeTransform") != std::string::npos)
-  {
-    transformList = helper.GetTransformList(transformList.front().GetPointer());
-  }
-
-  typename ConstTransformListType::const_iterator end = transformList.end();
-  for (typename ConstTransformListType::const_iterator it = transformList.begin(); it != end; ++it)
-  {
-    TransformJSON            transformJSON;
-    const TransformType *    currentTransform = it->GetPointer();
-    const std::string        transformType = currentTransform->GetTransformTypeAsString();
-    const std::string        delim = "_";
-    std::vector<std::string> tokens;
-    size_t                   start = 0;
-    size_t                   end = 0;
-    while ((end = transformType.find(delim, start)) != std::string::npos)
-    {
-      tokens.push_back(transformType.substr(start, end - start));
-      start = end + delim.length();
-    }
-    tokens.push_back(transformType.substr(start));
-    const std::string pString = tokens[0];
-    if (pString == "IdentityTransform")
-    {
-      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Identity;
-    }
-    else if (pString == "CompositeTransform")
-    {
-      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Composite;
-    }
-    else if (pString == "TranslationTransform")
-    {
-      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Translation;
-    }
-    else if (pString == "Euler2DTransform")
-    {
-      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Euler2D;
-    }
-    else if (pString == "Euler3DTransform")
-    {
-      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Euler3D;
-    }
-    else if (pString == "Rigid2DTransform")
-    {
-      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Rigid2D;
-    }
-    else if (pString == "Rigid3DPerspectiveTransform")
-    {
-      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Rigid3DPerspective;
-    }
-    else if (pString == "VersorRigid3DTransform")
-    {
-      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::VersorRigid3D;
-    }
-    else if (pString == "Versor")
-    {
-      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Versor;
-    }
-    else if (pString == "ScaleLogarithmicTransform")
-    {
-      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::ScaleLogarithmic;
-    }
-    else if (pString == "ScaleSkewVersor3DTransform")
-    {
-      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::ScaleSkewVersor3D;
-    }
-    else if (pString == "ScaleTransform")
-    {
-      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Scale;
-    }
-    else if (pString == "Similarity2DTransform")
-    {
-      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Similarity2D;
-    }
-    else if (pString == "Similarity3DTransform")
-    {
-      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Similarity3D;
-    }
-    else if (pString == "QuaternionRigidTransform")
-    {
-      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::QuaternionRigid;
-    }
-    else if (pString == "AffineTransform")
-    {
-      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Affine;
-    }
-    else if (pString == "ScalableAffineTransform")
-    {
-      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::ScalableAffine;
-    }
-    else if (pString == "AzimuthElevationToCartesianTransform")
-    {
-      transformJSON.transformType.transformParameterization =
-        JSONTransformParameterizationEnum::AzimuthElevationToCartesian;
-    }
-    else if (pString == "BSplineTransform")
-    {
-      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::BSpline;
-    }
-    else if (pString == "BSplineSmoothingOnUpdateDisplacementFieldTransform")
-    {
-      transformJSON.transformType.transformParameterization =
-        JSONTransformParameterizationEnum::BSplineSmoothingOnUpdateDisplacementField;
-    }
-    else if (pString == "ConstantVelocityFieldTransform")
-    {
-      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::ConstantVelocityField;
-    }
-    else if (pString == "DisplacementFieldTransform")
-    {
-      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::DisplacementField;
-    }
-    else if (pString == "GaussianExponentialDiffeomorphicTransform")
-    {
-      transformJSON.transformType.transformParameterization =
-        JSONTransformParameterizationEnum::GaussianExponentialDiffeomorphic;
-    }
-    else if (pString == "GaussianSmoothingOnUpdateDisplacementFieldTransform")
-    {
-      transformJSON.transformType.transformParameterization =
-        JSONTransformParameterizationEnum::GaussianSmoothingOnUpdateDisplacementField;
-    }
-    else if (pString == "GaussianSmoothingOnUpdateTimeVaryingVelocityFieldTransform")
-    {
-      transformJSON.transformType.transformParameterization =
-        JSONTransformParameterizationEnum::GaussianSmoothingOnUpdateTimeVaryingVelocityField;
-    }
-    else if (pString == "TimeVaryingVelocityFieldTransform")
-    {
-      transformJSON.transformType.transformParameterization =
-        JSONTransformParameterizationEnum::TimeVaryingVelocityField;
-    }
-    else if (pString == "VelocityFieldTransform")
-    {
-      transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::VelocityField;
-    }
-    else
-    {
-      itkExceptionMacro("Unknown transform type: " << pString);
-    }
-
-    const std::string typeName = this->GetTypeNameString();
-    if (typeName == "float")
-    {
-      transformJSON.transformType.parametersValueType = JSONFloatTypesEnum::float32;
-    }
-    else if (typeName == "double")
-    {
-      transformJSON.transformType.parametersValueType = JSONFloatTypesEnum::float64;
-    }
-    else
-    {
-      itkExceptionMacro("Unknown parameters value type: " << typeName);
-    }
-
-    transformJSON.transformType.inputDimension = currentTransform->GetInputSpaceDimension();
-    transformJSON.transformType.outputDimension = currentTransform->GetOutputSpaceDimension();
-
-    transformJSON.numberOfFixedParameters = currentTransform->GetFixedParameters().Size();
-    transformJSON.numberOfParameters = currentTransform->GetParameters().Size();
-    transformJSON.name = currentTransform->GetObjectName();
-    // Todo: needs to be pushed from itk::Transform to itk::TransformBase
-    // Available in ITK 5.4.1 and later
-    // https://github.com/InsightSoftwareConsortium/ITK/pull/4734
-    // transformJSON.inputSpaceName = currentTransform->GetInputSpaceName();
-    // transformJSON.inputSpaceName = currentTransform->GetOutputSpaceName();
-
-    transformListJSON.push_back(transformJSON);
-  }
 
+  constexpr bool inMemory = false;
+  TransformListJSON transformListJSON = transformListToTransformListJSON<TransformType>(transformList, inMemory);
   return transformListJSON;
 }
 
-template <typename TParametersValueType>
-std::string
-WasmTransformIOTemplate<TParametersValueType>::TransformParameterizationString(const TransformTypeJSON & json)
-{
-  std::string transformParameterization;
-  switch (json.transformParameterization)
-  {
-    case JSONTransformParameterizationEnum::Identity:
-    {
-      transformParameterization = "Identity";
-      break;
-    }
-    case JSONTransformParameterizationEnum::Composite:
-    {
-      transformParameterization = "Composite";
-      break;
-    }
-    case JSONTransformParameterizationEnum::Translation:
-    {
-      transformParameterization = "Translation";
-      break;
-    }
-    case JSONTransformParameterizationEnum::Euler2D:
-    {
-      transformParameterization = "Euler2D";
-      break;
-    }
-    case JSONTransformParameterizationEnum::Euler3D:
-    {
-      transformParameterization = "Euler3D";
-      break;
-    }
-    case JSONTransformParameterizationEnum::Rigid2D:
-    {
-      transformParameterization = "Rigid2D";
-      break;
-    }
-    case JSONTransformParameterizationEnum::Rigid3DPerspective:
-    {
-      transformParameterization = "Rigid3DPerspective";
-      break;
-    }
-    case JSONTransformParameterizationEnum::VersorRigid3D:
-    {
-      transformParameterization = "VersorRigid3D";
-      break;
-    }
-    case JSONTransformParameterizationEnum::Versor:
-    {
-      transformParameterization = "Versor";
-      break;
-    }
-    case JSONTransformParameterizationEnum::ScaleLogarithmic:
-    {
-      transformParameterization = "ScaleLogarithmic";
-      break;
-    }
-    case JSONTransformParameterizationEnum::ScaleSkewVersor3D:
-    {
-      transformParameterization = "ScaleSkewVersor3D";
-      break;
-    }
-    case JSONTransformParameterizationEnum::Scale:
-    {
-      transformParameterization = "Scale";
-      break;
-    }
-    case JSONTransformParameterizationEnum::Similarity2D:
-    {
-      transformParameterization = "Similarity2D";
-      break;
-    }
-    case JSONTransformParameterizationEnum::Similarity3D:
-    {
-      transformParameterization = "Similarity3D";
-      break;
-    }
-    case JSONTransformParameterizationEnum::QuaternionRigid:
-    {
-      transformParameterization = "QuaternionRigid";
-      break;
-    }
-    case JSONTransformParameterizationEnum::Affine:
-    {
-      transformParameterization = "Affine";
-      break;
-    }
-    case JSONTransformParameterizationEnum::ScalableAffine:
-    {
-      transformParameterization = "ScalableAffine";
-      break;
-    }
-    case JSONTransformParameterizationEnum::AzimuthElevationToCartesian:
-    {
-      transformParameterization = "AzimuthElevationToCartesian";
-      break;
-    }
-    case JSONTransformParameterizationEnum::BSpline:
-    {
-      transformParameterization = "BSpline";
-      break;
-    }
-    case JSONTransformParameterizationEnum::BSplineSmoothingOnUpdateDisplacementField:
-    {
-      transformParameterization = "BSplineSmoothingOnUpdateDisplacementField";
-      break;
-    }
-    case JSONTransformParameterizationEnum::ConstantVelocityField:
-    {
-      transformParameterization = "ConstantVelocityField";
-      break;
-    }
-    case JSONTransformParameterizationEnum::DisplacementField:
-    {
-      transformParameterization = "DisplacementField";
-      break;
-    }
-    case JSONTransformParameterizationEnum::GaussianExponentialDiffeomorphic:
-    {
-      transformParameterization = "GaussianExponentialDiffeomorphic";
-      break;
-    }
-    case JSONTransformParameterizationEnum::GaussianSmoothingOnUpdateDisplacementField:
-    {
-      transformParameterization = "GaussianSmoothingOnUpdateDisplacementField";
-      break;
-    }
-    case JSONTransformParameterizationEnum::GaussianSmoothingOnUpdateTimeVaryingVelocityField:
-    {
-      transformParameterization = "GaussianSmoothingOnUpdateTimeVaryingVelocityField";
-      break;
-    }
-    case JSONTransformParameterizationEnum::TimeVaryingVelocityField:
-    {
-      transformParameterization = "TimeVaryingVelocityField";
-      break;
-    }
-    case JSONTransformParameterizationEnum::VelocityField:
-    {
-      transformParameterization = "VelocityField";
-      break;
-    }
-    default:
-    {
-      throw std::invalid_argument("Unknown transform parameterization");
-    }
-  }
-
-  return transformParameterization;
-}
-
 template <typename TParametersValueType>
 void
 WasmTransformIOTemplate<TParametersValueType>::SetJSON(const TransformListJSON & json)
@@ -801,7 +471,7 @@ WasmTransformIOTemplate<TParametersValueType>::SetJSON(const TransformListJSON &
         itkExceptionMacro("Unknown parameters value type");
       }
     }
-    const std::string transformParameterization = TransformParameterizationString(transformJSON.transformType);
+    const std::string transformParameterization = transformParameterizationString(transformJSON.transformType);
     // itk::Transform<TParametersValueType, VInputDimension, VOutputDimension>::GetTransformTypeAsString() returns the
     // transform type string Note: non-cubic B-Splines not supported
     std::string transformType = transformParameterization + "Transform_" + transformPrecision + "_" +
@@ -813,6 +483,9 @@ WasmTransformIOTemplate<TParametersValueType>::SetJSON(const TransformListJSON &
     TransformPointer transform;
     this->CreateTransform(transform, transformType);
     transform->SetObjectName(transformJSON.name);
+    // todo: ITK 5.4.1
+    // transform->SetInputSpaceName(transformJSON.inputSpaceName);
+    // transform->SetOutputSpaceName(transformJSON.outputSpaceName);
     this->GetReadTransformList().push_back(transform);
   }
 }
@@ -834,7 +507,7 @@ WasmTransformIOTemplate<TParametersValueType>::WriteCBOR()
   cbor_item_t * index = this->m_CBORRoot;
   // write the transformListJSON into the cbor array
   ConstTransformListType & writeTransformList = this->GetWriteTransformList();
-  std::string              compositeTransformType = writeTransformList.front()->GetTransformTypeAsString();
+  const std::string    compositeTransformType = writeTransformList.front()->GetTransformTypeAsString();
   CompositeTransformIOHelperTemplate<TParametersValueType> helper;
 
   //
@@ -854,7 +527,7 @@ WasmTransformIOTemplate<TParametersValueType>::WriteCBOR()
     cbor_item_t * transformItem = cbor_new_definite_map(8);
     cbor_item_t * transformTypeItem = cbor_new_definite_map(4);
 
-    const std::string transformParameterization = TransformParameterizationString(transformJSON.transformType);
+    const std::string transformParameterization = transformParameterizationString(transformJSON.transformType);
     cbor_map_add(transformTypeItem,
                  cbor_pair{ cbor_move(cbor_build_string("transformParameterization")),
                             cbor_move(cbor_build_string(transformParameterization.c_str())) });
diff --git a/src/itktransformParameterizationString.cxx b/src/itktransformParameterizationString.cxx
new file mode 100644
index 000000000..25a66913e
--- /dev/null
+++ b/src/itktransformParameterizationString.cxx
@@ -0,0 +1,173 @@
+/*=========================================================================
+ *
+ *  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.
+ *
+ *=========================================================================*/
+#include "itktransformParameterizationString.h"
+
+namespace itk
+{
+
+const std::string
+transformParameterizationString(const TransformTypeJSON & json)
+{
+  std::string transformParameterization;
+  switch (json.transformParameterization)
+  {
+    case JSONTransformParameterizationEnum::Identity:
+    {
+      transformParameterization = "Identity";
+      break;
+    }
+    case JSONTransformParameterizationEnum::Composite:
+    {
+      transformParameterization = "Composite";
+      break;
+    }
+    case JSONTransformParameterizationEnum::Translation:
+    {
+      transformParameterization = "Translation";
+      break;
+    }
+    case JSONTransformParameterizationEnum::Euler2D:
+    {
+      transformParameterization = "Euler2D";
+      break;
+    }
+    case JSONTransformParameterizationEnum::Euler3D:
+    {
+      transformParameterization = "Euler3D";
+      break;
+    }
+    case JSONTransformParameterizationEnum::Rigid2D:
+    {
+      transformParameterization = "Rigid2D";
+      break;
+    }
+    case JSONTransformParameterizationEnum::Rigid3DPerspective:
+    {
+      transformParameterization = "Rigid3DPerspective";
+      break;
+    }
+    case JSONTransformParameterizationEnum::VersorRigid3D:
+    {
+      transformParameterization = "VersorRigid3D";
+      break;
+    }
+    case JSONTransformParameterizationEnum::Versor:
+    {
+      transformParameterization = "Versor";
+      break;
+    }
+    case JSONTransformParameterizationEnum::ScaleLogarithmic:
+    {
+      transformParameterization = "ScaleLogarithmic";
+      break;
+    }
+    case JSONTransformParameterizationEnum::ScaleSkewVersor3D:
+    {
+      transformParameterization = "ScaleSkewVersor3D";
+      break;
+    }
+    case JSONTransformParameterizationEnum::Scale:
+    {
+      transformParameterization = "Scale";
+      break;
+    }
+    case JSONTransformParameterizationEnum::Similarity2D:
+    {
+      transformParameterization = "Similarity2D";
+      break;
+    }
+    case JSONTransformParameterizationEnum::Similarity3D:
+    {
+      transformParameterization = "Similarity3D";
+      break;
+    }
+    case JSONTransformParameterizationEnum::QuaternionRigid:
+    {
+      transformParameterization = "QuaternionRigid";
+      break;
+    }
+    case JSONTransformParameterizationEnum::Affine:
+    {
+      transformParameterization = "Affine";
+      break;
+    }
+    case JSONTransformParameterizationEnum::ScalableAffine:
+    {
+      transformParameterization = "ScalableAffine";
+      break;
+    }
+    case JSONTransformParameterizationEnum::AzimuthElevationToCartesian:
+    {
+      transformParameterization = "AzimuthElevationToCartesian";
+      break;
+    }
+    case JSONTransformParameterizationEnum::BSpline:
+    {
+      transformParameterization = "BSpline";
+      break;
+    }
+    case JSONTransformParameterizationEnum::BSplineSmoothingOnUpdateDisplacementField:
+    {
+      transformParameterization = "BSplineSmoothingOnUpdateDisplacementField";
+      break;
+    }
+    case JSONTransformParameterizationEnum::ConstantVelocityField:
+    {
+      transformParameterization = "ConstantVelocityField";
+      break;
+    }
+    case JSONTransformParameterizationEnum::DisplacementField:
+    {
+      transformParameterization = "DisplacementField";
+      break;
+    }
+    case JSONTransformParameterizationEnum::GaussianExponentialDiffeomorphic:
+    {
+      transformParameterization = "GaussianExponentialDiffeomorphic";
+      break;
+    }
+    case JSONTransformParameterizationEnum::GaussianSmoothingOnUpdateDisplacementField:
+    {
+      transformParameterization = "GaussianSmoothingOnUpdateDisplacementField";
+      break;
+    }
+    case JSONTransformParameterizationEnum::GaussianSmoothingOnUpdateTimeVaryingVelocityField:
+    {
+      transformParameterization = "GaussianSmoothingOnUpdateTimeVaryingVelocityField";
+      break;
+    }
+    case JSONTransformParameterizationEnum::TimeVaryingVelocityField:
+    {
+      transformParameterization = "TimeVaryingVelocityField";
+      break;
+    }
+    case JSONTransformParameterizationEnum::VelocityField:
+    {
+      transformParameterization = "VelocityField";
+      break;
+    }
+    default:
+    {
+      throw std::invalid_argument("Unknown transform parameterization");
+    }
+  }
+
+  return transformParameterization;
+}
+
+} // namespace itk
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 609622974..5e092eb5f 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -71,6 +71,8 @@ set(WebAssemblyInterfaceTests
   itkSupportInputMeshTypesMemoryIOTest.cxx
   itkSupportInputPolyDataTypesTest.cxx
   itkTransformJSONTest.cxx
+  itkWasmTransformInterfaceTest.cxx
+  itkWasmTransformInterfaceCompositeTest.cxx
 )
 
 if (EMSCRIPTEN)
@@ -276,6 +278,20 @@ itk_add_test(NAME itkTransformJSONTest
     itkTransformJSONTest
 )
 
+itk_add_test(NAME itkWasmTransformInterfaceTest
+    COMMAND WebAssemblyInterfaceTestDriver
+    itkWasmTransformInterfaceTest
+      DATA{Input/LinearTransform.h5}
+      ${ITK_TEST_OUTPUT_DIR}/itkWasmTransformInterfaceTest.h5
+)
+
+itk_add_test(NAME itkWasmTransformInterfaceCompositeTest
+    COMMAND WebAssemblyInterfaceTestDriver
+    itkWasmTransformInterfaceCompositeTest
+      DATA{Input/CompositeTransform.h5}
+      ${ITK_TEST_OUTPUT_DIR}/itkWasmTransformInterfaceCompositeTest.h5
+)
+
 if(EMSCRIPTEN)
   # setjmp workaround
   set_property(TARGET WebAssemblyInterfaceTestDriver APPEND_STRING
diff --git a/test/itkWasmTransformInterfaceCompositeTest.cxx b/test/itkWasmTransformInterfaceCompositeTest.cxx
new file mode 100644
index 000000000..3c85f8291
--- /dev/null
+++ b/test/itkWasmTransformInterfaceCompositeTest.cxx
@@ -0,0 +1,89 @@
+/*=========================================================================
+ *
+ *  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.
+ *
+ *=========================================================================*/
+#include "itkTransformToWasmTransformFilter.h"
+#include "itkWasmTransformToTransformFilter.h"
+
+#include "itkCompositeTransform.h"
+#include "itkTransformFileReader.h"
+#include "itkTransformFileWriter.h"
+#include "itkTestingMacros.h"
+#include "itkHDF5TransformIOFactory.h"
+
+int
+itkWasmTransformInterfaceCompositeTest(int argc, char * argv[])
+{
+  if (argc < 3)
+  {
+    std::cerr << "Missing parameters" << std::endl;
+    std::cerr << "Usage: " << itkNameOfTestExecutableMacro(argv) << " InputTransform OutputTransform" << std::endl;
+    return EXIT_FAILURE;
+  }
+  const char * inputTransformFile = argv[1];
+  const char * outputTransformFile = argv[2];
+
+  itk::HDF5TransformIOFactory::RegisterOneFactory();
+
+  constexpr unsigned int Dimension = 2;
+  using ParametersValueType = double;
+  using TransformType = itk::CompositeTransform<ParametersValueType, Dimension>;
+  using TransformPointer = TransformType::Pointer;
+
+  using ReaderType = itk::TransformFileReaderTemplate<ParametersValueType>;
+  auto reader = ReaderType::New();
+  reader->SetFileName(inputTransformFile);
+  ITK_TRY_EXPECT_NO_EXCEPTION(reader->Update());
+  auto inputTransforms = reader->GetTransformList();
+  std::cout << "inputTransform: " << inputTransforms->front() << std::endl;
+
+  using TransformToWasmTransformFilterType = itk::TransformToWasmTransformFilter<TransformType>;
+  auto transformToJSONFilter = TransformToWasmTransformFilterType::New();
+  transformToJSONFilter->SetTransform(static_cast<const TransformType *>(inputTransforms->front().GetPointer()));
+  ITK_TRY_EXPECT_NO_EXCEPTION(transformToJSONFilter->Update());
+  auto transformJSON = transformToJSONFilter->GetOutput();
+  std::cout << "Transform JSON: " << transformJSON->GetJSON() << std::endl;
+
+  using WasmTransformToTransformFilterType = itk::WasmTransformToTransformFilter<TransformType>;
+  auto jsonToTransformFilter = WasmTransformToTransformFilterType::New();
+  jsonToTransformFilter->SetInput(transformJSON);
+  ITK_TRY_EXPECT_NO_EXCEPTION(jsonToTransformFilter->Update());
+  TransformType::Pointer convertedTransform = jsonToTransformFilter->GetOutput();
+  std::cout << "convertedTransform: " << convertedTransform << std::endl;
+
+  using WriterType = itk::TransformFileWriterTemplate<ParametersValueType>;
+  auto writer = WriterType::New();
+  writer->SetFileName(outputTransformFile);
+  writer->SetInput(convertedTransform);
+  ITK_TRY_EXPECT_NO_EXCEPTION(writer->Update());
+
+  const auto numFixedParams = convertedTransform->GetFixedParameters().Size();
+  const auto numParams = convertedTransform->GetParameters().Size();
+  const auto numFixedParamsInput = inputTransforms->front()->GetFixedParameters().Size();
+  const auto numParamsInput = inputTransforms->front()->GetParameters().Size();
+  ITK_TEST_EXPECT_EQUAL(numFixedParams, numFixedParamsInput);
+  ITK_TEST_EXPECT_EQUAL(numParams, numParamsInput);
+  for (unsigned int i = 0; i < numFixedParams; ++i)
+  {
+    ITK_TEST_EXPECT_EQUAL(convertedTransform->GetFixedParameters()[i], inputTransforms->front()->GetFixedParameters()[i]);
+  }
+  for (unsigned int i = 0; i < numParams; ++i)
+  {
+    ITK_TEST_EXPECT_EQUAL(convertedTransform->GetParameters()[i], inputTransforms->front()->GetParameters()[i]);
+  }
+
+  return EXIT_SUCCESS;
+}
diff --git a/test/itkWasmTransformInterfaceTest.cxx b/test/itkWasmTransformInterfaceTest.cxx
new file mode 100644
index 000000000..26816ae0f
--- /dev/null
+++ b/test/itkWasmTransformInterfaceTest.cxx
@@ -0,0 +1,89 @@
+/*=========================================================================
+ *
+ *  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.
+ *
+ *=========================================================================*/
+#include "itkAffineTransform.h"
+#include "itkTransformFileReader.h"
+#include "itkTransformFileWriter.h"
+#include "itkTestingMacros.h"
+#include "itkHDF5TransformIOFactory.h"
+
+#include "itkTransformToWasmTransformFilter.h"
+#include "itkWasmTransformToTransformFilter.h"
+
+int
+itkWasmTransformInterfaceTest(int argc, char * argv[])
+{
+  if (argc < 3)
+  {
+    std::cerr << "Missing parameters" << std::endl;
+    std::cerr << "Usage: " << itkNameOfTestExecutableMacro(argv) << " InputTransform OutputTransform" << std::endl;
+    return EXIT_FAILURE;
+  }
+  const char * inputTransformFile = argv[1];
+  const char * outputTransformFile = argv[2];
+
+  itk::HDF5TransformIOFactory::RegisterOneFactory();
+
+  constexpr unsigned int Dimension = 3;
+  using ParametersValueType = double;
+  using TransformType = itk::AffineTransform<ParametersValueType, Dimension>;
+  using TransformPointer = TransformType::Pointer;
+
+  using ReaderType = itk::TransformFileReaderTemplate<ParametersValueType>;
+  auto reader = ReaderType::New();
+  reader->SetFileName(inputTransformFile);
+  ITK_TRY_EXPECT_NO_EXCEPTION(reader->Update());
+  auto inputTransforms = reader->GetTransformList();
+  std::cout << "inputTransform: " << inputTransforms->front() << std::endl;
+
+  using TransformToWasmTransformFilterType = itk::TransformToWasmTransformFilter<TransformType>;
+  auto transformToJSONFilter = TransformToWasmTransformFilterType::New();
+  transformToJSONFilter->SetTransform(static_cast<const TransformType *>(inputTransforms->front().GetPointer()));
+  ITK_TRY_EXPECT_NO_EXCEPTION(transformToJSONFilter->Update());
+  auto transformJSON = transformToJSONFilter->GetOutput();
+  std::cout << "Transform JSON: " << transformJSON->GetJSON() << std::endl;
+
+  using WasmTransformToTransformFilterType = itk::WasmTransformToTransformFilter<TransformType>;
+  auto jsonToTransformFilter = WasmTransformToTransformFilterType::New();
+  jsonToTransformFilter->SetInput(transformJSON);
+  ITK_TRY_EXPECT_NO_EXCEPTION(jsonToTransformFilter->Update());
+  TransformType::Pointer convertedTransform = jsonToTransformFilter->GetOutput();
+  std::cout << "convertedTransform: " << convertedTransform << std::endl;
+
+  using WriterType = itk::TransformFileWriterTemplate<ParametersValueType>;
+  auto writer = WriterType::New();
+  writer->SetFileName(outputTransformFile);
+  writer->SetInput(convertedTransform);
+  ITK_TRY_EXPECT_NO_EXCEPTION(writer->Update());
+
+  const auto numFixedParams = convertedTransform->GetFixedParameters().Size();
+  const auto numParams = convertedTransform->GetParameters().Size();
+  const auto numFixedParamsInput = inputTransforms->front()->GetFixedParameters().Size();
+  const auto numParamsInput = inputTransforms->front()->GetParameters().Size();
+  ITK_TEST_EXPECT_EQUAL(numFixedParams, numFixedParamsInput);
+  ITK_TEST_EXPECT_EQUAL(numParams, numParamsInput);
+  for (unsigned int i = 0; i < numFixedParams; ++i)
+  {
+    ITK_TEST_EXPECT_EQUAL(convertedTransform->GetFixedParameters()[i], inputTransforms->front()->GetFixedParameters()[i]);
+  }
+  for (unsigned int i = 0; i < numParams; ++i)
+  {
+    ITK_TEST_EXPECT_EQUAL(convertedTransform->GetParameters()[i], inputTransforms->front()->GetParameters()[i]);
+  }
+
+  return EXIT_SUCCESS;
+}