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; +}