From 9c20e4914b3029b7d2dc2080e22f1764f0466318 Mon Sep 17 00:00:00 2001 From: Matt McCormick Date: Sat, 13 Apr 2024 22:45:09 -0400 Subject: [PATCH 01/15] docs: add TransformList, Transform LinkML model --- model/itk-wasm.yml | 110 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/model/itk-wasm.yml b/model/itk-wasm.yml index f3e48b046..a580e9c7e 100644 --- a/model/itk-wasm.yml +++ b/model/itk-wasm.yml @@ -411,6 +411,78 @@ classes: A type that can be represented in JSON. class_uri: wasm:JsonCompatible + Transform: + description: >- + Representation of a spatial transformation. + class_uri: wasm:Transform + slots: + - parametersValueType + attributes: + name: + description: >- + Name of the transform. + range: string + required: true + inputDimension: + description: >- + Dimension of the input space. + range: integer + required: true + outputDimension: + description: >- + Dimension of the output space. + range: integer + required: true + transformType: + description: >- + Type of the transform. + range: TransformTypes + required: true + inputSpaceName: + description: >- + Name of the input space. + range: string + required: true + outputSpaceName: + description: >- + Name of the output space. + range: string + required: true + fixedParameters: + description: >- + Fixed parameters of the transform. + range: BinaryData + required: true + parameters: + description: >- + Parameters of the transform. + range: BinaryData + required: true + numberOfParameters: + description: >- + Number of parameters in the transform. + range: integer + required: true + numberOfFixedParameters: + description: >- + Number of fixed parameters in the transform. + range: integer + required: true + + TransformList: + is_a: InterfaceType + description: >- + Representation of a sequence of transforms. + class_uri: wasm:TransformList + attributes: + values: + description: >- + The content of the transform sequence. + range: Transform + multivalued: true + required: true + inlined_as_list: true + slots: path: description: >- @@ -433,6 +505,13 @@ slots: - range: FloatTypes required: true + parametersValueType: + description: >- + Type of binary data components in a transform. + any_of: + - range: FloatTypes + required: true + pixelType: description: >- Type of the pixel or attribute. @@ -541,3 +620,34 @@ enums: Matrix: VariableLengthVector: VariableSizeMatrix: + + TransformTypes: + description: >- + Transform types. + permissible_values: + IdentityTransform: + AffineTransform: + TranslationTransform: + AzimuthElevationToCartesianTransform: + BSplineTransform: + Euler2DTransform: + Euler3DTransform: + QuaternionRigidTransform: + Rigid2DTransform: + Rigid3DPerspectiveTransform: + ScalableAffineTransform: + ScaleLogarithmicTransform: + ScaleSkewVersor3DTransform: + ScaleTransform: + Similarity2DTransform: + Similarity3DTransform: + VersorRigid3DTransform: + VersorTransform: + BSplineSmoothingOnUpdateDisplacementFieldTransform: + ConstantVelocityFieldTransform: + DisplacementFieldTransform: + GaussianExponentialDiffeomorphicTransform: + GaussianSmoothingOnUpdateDisplacementFieldTransform: + GaussianSmoothingOnUpdateTimeVaryingVelocityFieldTransform: + TimeVaryingVelocityFieldTransform: + VelocityFieldTransform: From 3469000fd10aa6e70b991ffd4271754d6f3a72d5 Mon Sep 17 00:00:00 2001 From: Matt McCormick Date: Mon, 15 Apr 2024 16:26:51 -0400 Subject: [PATCH 02/15] refactor: remove old module IO CMake code This functionality has been migrated to the IO packages. --- CMakeLists.txt | 21 --------------------- src/CMakeLists.txt | 16 ---------------- 2 files changed, 37 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index febbbf156..ddf44aec4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,27 +9,6 @@ endif() set(WebAssemblyInterface_LIBRARIES WebAssemblyInterface) -option(BUILD_ITK_WASM_IO_MODULES "Build the itk-wasm ImageIO's and MeshIO's" OFF) -if(BUILD_ITK_WASM_IO_MODULES) - set(WebAssemblyInterface_MeshIOModules - "ITKIOMeshBYU" - "ITKIOMeshFreeSurfer" - "ITKIOMeshOBJ" - "ITKIOMeshOFF" - "IOMeshSTL" - "IOMeshSWC" - "ITKIOMeshVTK" - CACHE STRING - "String delimited list of ITK mesh IO modules to support.") - set(meshios_ITKIOMeshBYU itkBYUMeshIO) - set(meshios_ITKIOMeshFreeSurfer itkFreeSurferAsciiMeshIO itkFreeSurferBinaryMeshIO) - set(meshios_ITKIOMeshVTK itkVTKPolyDataMeshIO) - set(meshios_ITKIOMeshOBJ itkOBJMeshIO) - set(meshios_ITKIOMeshOFF itkOFFMeshIO) - set(meshios_IOMeshSTL itkSTLMeshIO) - set(meshios_IOMeshSWC itkSWCMeshIO) -endif() - include(FetchContent) set(_itk_build_testing ${BUILD_TESTING}) set(BUILD_TESTING OFF) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 283bf0895..3f478b163 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -29,19 +29,3 @@ target_link_libraries(WebAssemblyInterface LINK_PUBLIC cbor cpp-base64) if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") target_compile_options(WebAssemblyInterface PRIVATE "-Wno-unused-result") endif() - -if(BUILD_ITK_WASM_IO_MODULES) - -if(EMSCRIPTEN) -# -s WASM_ASYNC_COMPILATION=0 -# -flto - set(common_link_flags " -s ALLOW_MEMORY_GROWTH=1 -s MAXIMUM_MEMORY=4GB -s FORCE_FILESYSTEM=1 -s MODULARIZE=1 -s WASM=1 -lnodefs.js -s EXIT_RUNTIME=0 -s INVOKE_RUN=1 --post-js ${CMAKE_CURRENT_SOURCE_DIR}/emscripten-module/itkJSPost.js") - set(esm_link_flags " -s EXPORT_ES6=1 -s USE_ES6_IMPORT_META=1") -endif() - -include(${CMAKE_CURRENT_SOURCE_DIR}/../CMake/BuildZstd.cmake) -add_subdirectory(io/internal/pipelines/image/convert-image) -add_subdirectory(io/internal/pipelines/mesh/convert-mesh) -add_subdirectory(io/internal/pipelines/mesh/mesh-to-polydata) - -endif() # BUILD_ITK_WASM_IO_MODULES From fdcef10048bec59895a87b05f3f5ec5086dd9a75 Mon Sep 17 00:00:00 2001 From: Matt McCormick Date: Mon, 15 Apr 2024 16:35:58 -0400 Subject: [PATCH 03/15] feat: add itk::WasmTransformIOFactory --- include/itkWasmTransformIOFactory.h | 70 +++++++++++++++++++++++++ itk-module.cmake | 2 + src/CMakeLists.txt | 1 + src/itkWasmTransformIOFactory.cxx | 71 ++++++++++++++++++++++++++ test/Input/LinearTransform.h5.cid | 1 + test/itkWasmTransformIOTest.cxx | 79 +++++++++++++++++++++++++++++ wrapping/itkWasmTransformIO.wrap | 2 + 7 files changed, 226 insertions(+) create mode 100644 include/itkWasmTransformIOFactory.h create mode 100644 src/itkWasmTransformIOFactory.cxx create mode 100644 test/Input/LinearTransform.h5.cid create mode 100644 test/itkWasmTransformIOTest.cxx create mode 100644 wrapping/itkWasmTransformIO.wrap diff --git a/include/itkWasmTransformIOFactory.h b/include/itkWasmTransformIOFactory.h new file mode 100644 index 000000000..bef0ecf3e --- /dev/null +++ b/include/itkWasmTransformIOFactory.h @@ -0,0 +1,70 @@ +/*========================================================================= + * + * 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 itkWasmTransformIOFactory_h +#define itkWasmTransformIOFactory_h +#include "WebAssemblyInterfaceExport.h" + +#include "itkObjectFactoryBase.h" +#include "itkTransformIOBase.h" + +namespace itk +{ +/** \class WasmTransformIOFactory + * + * \brief Create instances of WasmTransformIO objects using an object factory. + * + * \ingroup WebAssemblyInterface + */ +class WebAssemblyInterface_EXPORT WasmTransformIOFactory: public ObjectFactoryBase +{ +public: + /** Standard class typedefs. */ + typedef WasmTransformIOFactory Self; + typedef ObjectFactoryBase Superclass; + typedef SmartPointer< Self > Pointer; + typedef SmartPointer< const Self > ConstPointer; + + /** Class methods used to interface with the registered factories. */ + const char * GetITKSourceVersion() const override; + + const char * GetDescription() const override; + + /** Method for class instantiation. */ + itkFactorylessNewMacro(Self); + + /** Run-time type information (and related methods). */ + itkTypeMacro(WasmTransformIOFactory, ObjectFactoryBase); + + /** Register one factory of this type */ + static void RegisterOneFactory() + { + WasmTransformIOFactory::Pointer wasmFactory = WasmTransformIOFactory::New(); + + ObjectFactoryBase::RegisterFactoryInternal(wasmFactory); + } + +protected: + WasmTransformIOFactory(); + ~WasmTransformIOFactory() override; + +private: + ITK_DISALLOW_COPY_AND_ASSIGN(WasmTransformIOFactory); +}; +} // end namespace itk + +#endif diff --git a/itk-module.cmake b/itk-module.cmake index 5d1597191..9d636d4c7 100644 --- a/itk-module.cmake +++ b/itk-module.cmake @@ -9,6 +9,7 @@ itk_module(WebAssemblyInterface ITKCommon ITKIOImageBase ITKIOMeshBase + ITKIOTransformBase COMPILE_DEPENDS MeshToPolyData ITKImageFunction @@ -19,6 +20,7 @@ itk_module(WebAssemblyInterface FACTORY_NAMES ImageIO::Wasm MeshIO::Wasm + TransformIO::Wasm EXCLUDE_FROM_DEFAULT DESCRIPTION "${DOCUMENTATION}" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3f478b163..44ee9bfc5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -11,6 +11,7 @@ set(WebAssemblyInterface_SRCS itkWasmMeshIOBase.cxx itkWasmMeshIOFactory.cxx itkWasmMeshIO.cxx + itkWasmTransformIOFactory.cxx itkWasmStringStream.cxx itkInputTextStream.cxx itkOutputTextStream.cxx diff --git a/src/itkWasmTransformIOFactory.cxx b/src/itkWasmTransformIOFactory.cxx new file mode 100644 index 000000000..ff95f8c40 --- /dev/null +++ b/src/itkWasmTransformIOFactory.cxx @@ -0,0 +1,71 @@ +/*========================================================================= + * + * 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 "itkWasmTransformIOFactory.h" +#include "itkWasmTransformIO.h" +#include "itkVersion.h" + +namespace itk +{ + +WasmTransformIOFactory +::WasmTransformIOFactory() +{ + this->RegisterOverride( "itkTransformIOBase", + "itkWasmTransformIO", + "Wasm Transform IO", + 1, + CreateObjectFunction< WasmTransformIO >::New() ); +} + + +WasmTransformIOFactory +::~WasmTransformIOFactory() +{} + + +const char * +WasmTransformIOFactory +::GetITKSourceVersion() const +{ + return ITK_SOURCE_VERSION; +} + + +const char * +WasmTransformIOFactory +::GetDescription() const +{ + return "Wasm TransformIO Factory, allows the loading of Wasm images into Insight"; +} + + +// Undocumented API used to register during static initialization. +// DO NOT CALL DIRECTLY. + +static bool WasmTransformIOFactoryHasBeenRegistered; + +void WebAssemblyInterface_EXPORT WasmTransformIOFactoryRegister__Private(void) +{ + if( ! WasmTransformIOFactoryHasBeenRegistered ) + { + WasmTransformIOFactoryHasBeenRegistered = true; + WasmTransformIOFactory::RegisterOneFactory(); + } +} + +} // end namespace itk diff --git a/test/Input/LinearTransform.h5.cid b/test/Input/LinearTransform.h5.cid new file mode 100644 index 000000000..ae2f334c5 --- /dev/null +++ b/test/Input/LinearTransform.h5.cid @@ -0,0 +1 @@ +bafkreicdjbd4wnnbfzh44hdt3gp46brt7v3mbyqm6kspxb4btldmkweojy diff --git a/test/itkWasmTransformIOTest.cxx b/test/itkWasmTransformIOTest.cxx new file mode 100644 index 000000000..cc2cb5c15 --- /dev/null +++ b/test/itkWasmTransformIOTest.cxx @@ -0,0 +1,79 @@ +/*========================================================================= + * + * 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 "itkWasmTransformIOFactory.h" +#include "itkWasmTransformIO.h" +#include "itkTransformFileReader.h" +#include "itkTransformFileWriter.h" +#include "itkTestingMacros.h" + +int +itkWasmTransformIOTest(int argc, char * argv[]) +{ + if (argc < 6) + { + std::cerr << "Missing parameters" << std::endl; + std::cerr << "Usage: " << itkNameOfTestExecutableMacro(argv) << " InputTransform TransformDirectory ConvertedDirectory TransformCBOR ConvertedCBOR" << std::endl; + return EXIT_FAILURE; + } + const char * inputTransformFile = argv[1]; + const char * imageDirectory = argv[2]; + const char * convertedDirectoryFile = argv[3]; + const char * imageCBOR = argv[4]; + const char * convertedCBORFile = argv[5]; + + itk::WasmTransformIOFactory::RegisterOneFactory(); + + constexpr unsigned int Dimension = 3; + using PixelType = unsigned char; + using TransformType = itk::Transform; + using TransformPointer = TransformType::Pointer; + + TransformPointer inputTransform = nullptr; + ITK_TRY_EXPECT_NO_EXCEPTION(inputTransform = itk::ReadTransform(inputTransformFile)); + + auto imageIO = itk::WasmTransformIO::New(); + + using WriterType = itk::TransformFileWriter; + auto wasmWriter = WriterType::New(); + //wasmWriter->SetTransformIO( imageIO ); + wasmWriter->SetFileName( imageDirectory ); + wasmWriter->SetInput( inputTransform ); + + ITK_TRY_EXPECT_NO_EXCEPTION(wasmWriter->Update()); + + using ReaderType = itk::TransformFileReader; + auto wasmReader = ReaderType::New(); + //wasmReader->SetTransformIO( imageIO ); + wasmReader->SetFileName( imageDirectory ); + + ITK_TRY_EXPECT_NO_EXCEPTION(wasmReader->Update()); + + TransformPointer writtenReadTransform = wasmReader->GetOutput(); + + ITK_TRY_EXPECT_NO_EXCEPTION(itk::WriteTransform(writtenReadTransform, convertedDirectoryFile)); + + wasmWriter->SetFileName( imageCBOR ); + ITK_TRY_EXPECT_NO_EXCEPTION(wasmWriter->Update()); + + wasmReader->SetFileName( imageCBOR ); + ITK_TRY_EXPECT_NO_EXCEPTION(wasmReader->Update()); + + ITK_TRY_EXPECT_NO_EXCEPTION(itk::WriteTransform(wasmReader->GetOutput(), convertedCBORFile)); + + return EXIT_SUCCESS; +} diff --git a/wrapping/itkWasmTransformIO.wrap b/wrapping/itkWasmTransformIO.wrap new file mode 100644 index 000000000..ff6516b15 --- /dev/null +++ b/wrapping/itkWasmTransformIO.wrap @@ -0,0 +1,2 @@ +itk_wrap_simple_class("itk::WasmTransformIO" POINTER) +itk_wrap_simple_class("itk::WasmTransformIOFactory" POINTER) From cdd70edc015e3c8c65d4b31b06a1aca7df1d4879 Mon Sep 17 00:00:00 2001 From: Matt McCormick Date: Mon, 15 Apr 2024 21:58:22 -0400 Subject: [PATCH 04/15] feat: itk::WasmTransformIO initial addition --- .gitignore | 2 + include/itkFloatTypesJSON.h | 42 + include/itkInputMeshIO.h | 9 +- include/itkOutputMeshIO.h | 325 ++-- include/itkTransformJSON.h | 132 ++ include/itkWasmIOCommon.h | 93 ++ include/itkWasmImageIO.h | 4 +- include/itkWasmMeshIO.h | 47 +- include/itkWasmTransformIO.h | 160 ++ include/itkWasmTransformIOFactory.h | 19 +- itk-module.cmake | 1 + model/itk-wasm.yml | 109 +- .../itk-wasm/test/data/cow.iwm/index.json | 4 +- .../cow.iwm/index.json | 4 +- src/CMakeLists.txt | 2 + src/itkWasmIOCommon.cxx | 274 ++++ src/itkWasmImageIO.cxx | 3 +- src/itkWasmMeshIO.cxx | 335 +--- src/itkWasmMeshIOBase.cxx | 9 +- src/itkWasmMeshIOFactory.cxx | 2 +- src/itkWasmTransformIO.cxx | 1355 +++++++++++++++++ src/itkWasmTransformIOFactory.cxx | 18 +- test/CMakeLists.txt | 17 + test/itkTransformJSONTest.cxx | 59 + test/itkWasmTransformIOTest.cxx | 118 +- 25 files changed, 2539 insertions(+), 604 deletions(-) create mode 100644 include/itkFloatTypesJSON.h create mode 100644 include/itkTransformJSON.h create mode 100644 include/itkWasmIOCommon.h create mode 100644 include/itkWasmTransformIO.h create mode 100644 src/itkWasmIOCommon.cxx create mode 100644 src/itkWasmTransformIO.cxx create mode 100644 test/itkTransformJSONTest.cxx diff --git a/.gitignore b/.gitignore index 8f04c494f..efa8454e5 100644 --- a/.gitignore +++ b/.gitignore @@ -92,3 +92,5 @@ cypress/screenshots/ cypress/videos/ packages/core/typescript/itk-wasm/dist + +CMakePresets.json diff --git a/include/itkFloatTypesJSON.h b/include/itkFloatTypesJSON.h new file mode 100644 index 000000000..e44a92089 --- /dev/null +++ b/include/itkFloatTypesJSON.h @@ -0,0 +1,42 @@ +/*========================================================================= + * + * 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 itkFloatTypesJSON_h +#define itkFloatTypesJSON_h + +#include + +#include "glaze/glaze.hpp" + +namespace itk +{ + enum class JSONFloatTypesEnum + { + float32, + float64, + }; +} // end namespace itk + +template <> +struct glz::meta { + using enum itk::JSONFloatTypesEnum; + static constexpr auto value = glz::enumerate(float32, + float64 + ); +}; + +#endif // itkFloatTypesJSON_h diff --git a/include/itkInputMeshIO.h b/include/itkInputMeshIO.h index f251ba316..5296f9849 100644 --- a/include/itkInputMeshIO.h +++ b/include/itkInputMeshIO.h @@ -21,6 +21,7 @@ #include "itkPipeline.h" #include "itkWasmMeshIOBase.h" #include "itkWasmMeshIO.h" +#include "itkWasmIOCommon.h" #ifndef ITK_WASM_NO_MEMORY_IO #include "itkWasmExports.h" @@ -87,7 +88,7 @@ bool lexical_cast(const std::string &input, InputMeshIO &inputMeshIO) const std::string pointsString( pointsJson.GetString() ); const char * pointsPtr = reinterpret_cast< char * >( std::strtoull(pointsString.substr(35).c_str(), nullptr, 10) ); WasmMeshIOBase::DataContainerType * pointsContainer = wasmMeshIOBase->GetPointsContainer(); - SizeValueType numberOfBytes = wasmMeshIO->GetNumberOfPoints() * wasmMeshIO->GetPointDimension() * WasmMeshIO::ITKComponentSize( wasmMeshIO->GetPointComponentType() ); + SizeValueType numberOfBytes = wasmMeshIO->GetNumberOfPoints() * wasmMeshIO->GetPointDimension() * ITKComponentSize( wasmMeshIO->GetPointComponentType() ); pointsContainer->resize(numberOfBytes); pointsContainer->assign(pointsPtr, pointsPtr + numberOfBytes); @@ -95,7 +96,7 @@ bool lexical_cast(const std::string &input, InputMeshIO &inputMeshIO) const std::string cellsString( cellsJson.GetString() ); const char * cellsPtr = reinterpret_cast< char * >( std::strtoull(cellsString.substr(35).c_str(), nullptr, 10) ); WasmMeshIOBase::DataContainerType * cellsContainer = wasmMeshIOBase->GetCellsContainer(); - numberOfBytes = static_cast< SizeValueType >( wasmMeshIO->GetCellBufferSize() * WasmMeshIO::ITKComponentSize( wasmMeshIO->GetCellComponentType() )); + numberOfBytes = static_cast< SizeValueType >( wasmMeshIO->GetCellBufferSize() * ITKComponentSize( wasmMeshIO->GetCellComponentType() )); cellsContainer->resize(numberOfBytes); cellsContainer->assign(cellsPtr, cellsPtr + numberOfBytes); @@ -105,7 +106,7 @@ bool lexical_cast(const std::string &input, InputMeshIO &inputMeshIO) WasmMeshIOBase::DataContainerType * pointDataContainer = wasmMeshIOBase->GetPointDataContainer(); numberOfBytes = static_cast< SizeValueType >( - wasmMeshIO->GetNumberOfPointPixels() * wasmMeshIO->GetNumberOfPointPixelComponents() * WasmMeshIO::ITKComponentSize( wasmMeshIO->GetPointPixelComponentType() ) + wasmMeshIO->GetNumberOfPointPixels() * wasmMeshIO->GetNumberOfPointPixelComponents() * ITKComponentSize( wasmMeshIO->GetPointPixelComponentType() ) ); pointDataContainer->resize(numberOfBytes); pointDataContainer->assign(pointDataPtr, pointDataPtr + numberOfBytes); @@ -116,7 +117,7 @@ bool lexical_cast(const std::string &input, InputMeshIO &inputMeshIO) WasmMeshIOBase::DataContainerType * cellDataContainer = wasmMeshIOBase->GetCellDataContainer(); numberOfBytes = static_cast< SizeValueType >( - wasmMeshIO->GetNumberOfPointPixels() * wasmMeshIO->GetNumberOfPointPixelComponents() * WasmMeshIO::ITKComponentSize( wasmMeshIO->GetPointPixelComponentType() ) + wasmMeshIO->GetNumberOfPointPixels() * wasmMeshIO->GetNumberOfPointPixelComponents() * ITKComponentSize( wasmMeshIO->GetPointPixelComponentType() ) ); cellDataContainer->resize(numberOfBytes); cellDataContainer->assign(cellDataPtr, cellDataPtr + numberOfBytes); diff --git a/include/itkOutputMeshIO.h b/include/itkOutputMeshIO.h index 1591cae35..ee7e2ab2a 100644 --- a/include/itkOutputMeshIO.h +++ b/include/itkOutputMeshIO.h @@ -23,6 +23,7 @@ #include "itkMeshIOBase.h" #include "itkWasmMeshIOBase.h" #include "itkWasmMeshIO.h" +#include "itkWasmIOCommon.h" #ifndef ITK_WASM_NO_MEMORY_IO #include "itkWasmExports.h" #endif @@ -31,189 +32,191 @@ namespace itk { -namespace wasm -{ -/** - *\class OutputMeshIO - * \brief Output image for an itk::wasm::Pipeline from an itk::MeshIOBase - * - * This image is written to the filesystem or memory when it goes out of scope. - * - * This class is for the ReadMesh itk-wasm pipeline. Most pipelines will use itk::wasm::OutputMesh. - * - * \ingroup WebAssemblyInterface - */ -class OutputMeshIO -{ -public: - /** Set whether to only read mesh metadata. Do not read the points, points data. */ - void SetInformationOnly(bool informationOnly) { - this->m_InformationOnly = informationOnly; - } - - void Set(MeshIOBase * imageIO) { - this->m_MeshIO = imageIO; - } - - MeshIOBase * Get() const { - return this->m_MeshIO.GetPointer(); - } - - /** FileName or output index. */ - void SetIdentifier(const std::string & identifier) + namespace wasm { - this->m_Identifier = identifier; - } - const std::string & GetIdentifier() const - { - return this->m_Identifier; - } - - OutputMeshIO() = default; - ~OutputMeshIO() { - if(wasm::Pipeline::get_use_memory_io()) - { -#ifndef ITK_WASM_NO_MEMORY_IO - if (!this->m_MeshIO.IsNull() && !this->m_Identifier.empty()) - { - const auto index = std::stoi(this->m_Identifier); - auto wasmMeshIOBase = itk::WasmMeshIOBase::New(); - wasmMeshIOBase->SetMeshIO(this->m_MeshIO); - setMemoryStoreOutputDataObject(0, index, wasmMeshIOBase); - - if (this->m_InformationOnly) - { - return; - } - - const auto pointsSize = wasmMeshIOBase->GetPointsContainer()->size(); - if (pointsSize) - { - const auto pointsAddress = reinterpret_cast< size_t >( &(wasmMeshIOBase->GetPointsContainer()->at(0)) ); - setMemoryStoreOutputArray(0, index, 0, pointsAddress, pointsSize); - } - - const auto cellsSize = wasmMeshIOBase->GetCellsContainer()->size(); - if (cellsSize) + /** + *\class OutputMeshIO + * \brief Output image for an itk::wasm::Pipeline from an itk::MeshIOBase + * + * This image is written to the filesystem or memory when it goes out of scope. + * + * This class is for the ReadMesh itk-wasm pipeline. Most pipelines will use itk::wasm::OutputMesh. + * + * \ingroup WebAssemblyInterface + */ + class OutputMeshIO { - const auto cellsAddress = reinterpret_cast< size_t >( &(wasmMeshIOBase->GetCellsContainer()->at(0)) ); - setMemoryStoreOutputArray(0, index, 1, cellsAddress, cellsSize); - } - - const auto pointDataSize = wasmMeshIOBase->GetPointDataContainer()->size(); - if (pointDataSize) - { - const auto pointDataAddress = reinterpret_cast< size_t >( &(wasmMeshIOBase->GetPointDataContainer()->at(0)) ); - setMemoryStoreOutputArray(0, index, 2, pointDataAddress, pointDataSize); - } - - const auto cellDataSize = wasmMeshIOBase->GetCellDataContainer()->size(); - if (cellDataSize) - { - const auto cellDataAddress = reinterpret_cast< size_t >( &(wasmMeshIOBase->GetCellDataContainer()->at(0)) ); - setMemoryStoreOutputArray(0, index, 3, cellDataAddress, cellDataSize); - } - - } -#else - std::cerr << "Memory IO not supported" << std::endl; - abort(); -#endif - } - else - { -#ifndef ITK_WASM_NO_FILESYSTEM_IO - if (!this->m_MeshIO.IsNull() && !this->m_Identifier.empty()) - { - this->m_MeshIO->ReadMeshInformation(); - - auto wasmMeshIO = itk::WasmMeshIO::New(); - wasmMeshIO->SetFileName(this->m_Identifier); - - const unsigned int dimension = this->m_MeshIO->GetPointDimension(); - wasmMeshIO->SetPointDimension(dimension); - wasmMeshIO->SetPointComponentType(this->m_MeshIO->GetPointComponentType()); - wasmMeshIO->SetPointPixelType(this->m_MeshIO->GetPointPixelType()); - wasmMeshIO->SetPointPixelComponentType(this->m_MeshIO->GetPointPixelComponentType()); - wasmMeshIO->SetNumberOfPointPixelComponents(this->m_MeshIO->GetNumberOfPointPixelComponents()); - wasmMeshIO->SetCellComponentType(this->m_MeshIO->GetCellComponentType()); - wasmMeshIO->SetCellPixelType(this->m_MeshIO->GetCellPixelType()); - wasmMeshIO->SetCellPixelComponentType(this->m_MeshIO->GetCellPixelComponentType()); - wasmMeshIO->SetNumberOfCellPixelComponents(this->m_MeshIO->GetNumberOfCellPixelComponents()); - wasmMeshIO->SetNumberOfPoints(this->m_MeshIO->GetNumberOfPoints()); - wasmMeshIO->SetNumberOfPointPixels(this->m_MeshIO->GetNumberOfPointPixels()); - wasmMeshIO->SetNumberOfCells(this->m_MeshIO->GetNumberOfCells()); - wasmMeshIO->SetNumberOfCellPixels(this->m_MeshIO->GetNumberOfCellPixels()); - wasmMeshIO->SetCellBufferSize(this->m_MeshIO->GetCellBufferSize()); - - wasmMeshIO->WriteMeshInformation(); - - if (this->m_InformationOnly) + public: + /** Set whether to only read mesh metadata. Do not read the points, points data. */ + void SetInformationOnly(bool informationOnly) { - return; + this->m_InformationOnly = informationOnly; } - SizeValueType numberOfBytes = this->m_MeshIO->GetNumberOfPoints() * this->m_MeshIO->GetPointDimension() * WasmMeshIO::ITKComponentSize( this->m_MeshIO->GetPointComponentType() ); - std::vector loadBuffer(numberOfBytes); - if (numberOfBytes) + void Set(MeshIOBase *imageIO) { - this->m_MeshIO->ReadPoints(reinterpret_cast< void * >( &(loadBuffer.at(0)) )); - wasmMeshIO->WritePoints(reinterpret_cast< void * >( &(loadBuffer.at(0)) )); + this->m_MeshIO = imageIO; } - numberOfBytes = static_cast< SizeValueType >( this->m_MeshIO->GetCellBufferSize() * WasmMeshIO::ITKComponentSize( this->m_MeshIO->GetCellComponentType() )); - if (numberOfBytes) + MeshIOBase *Get() const { - loadBuffer.resize(numberOfBytes); - this->m_MeshIO->ReadCells(reinterpret_cast< void * >( &(loadBuffer.at(0)) )); - wasmMeshIO->WriteCells(reinterpret_cast< void * >( &(loadBuffer.at(0)) )); + return this->m_MeshIO.GetPointer(); } - numberOfBytes = - static_cast< SizeValueType >( - this->m_MeshIO->GetNumberOfPointPixels() * this->m_MeshIO->GetNumberOfPointPixelComponents() * WasmMeshIO::ITKComponentSize( this->m_MeshIO->GetPointPixelComponentType() ) - ); - if (numberOfBytes) + /** FileName or output index. */ + void SetIdentifier(const std::string &identifier) { - loadBuffer.resize(numberOfBytes); - this->m_MeshIO->ReadPointData(reinterpret_cast< void * >( &(loadBuffer.at(0)) )); - wasmMeshIO->WritePointData(reinterpret_cast< void * >( &(loadBuffer.at(0)) )); + this->m_Identifier = identifier; } - - numberOfBytes = - static_cast< SizeValueType >( - this->m_MeshIO->GetNumberOfCellPixels() * this->m_MeshIO->GetNumberOfCellPixelComponents() * WasmMeshIO::ITKComponentSize( this->m_MeshIO->GetCellPixelComponentType() ) - ); - if (numberOfBytes) + const std::string &GetIdentifier() const { - loadBuffer.resize(numberOfBytes); - this->m_MeshIO->ReadCellData(reinterpret_cast< void * >( &(loadBuffer.at(0)) )); - wasmMeshIO->WriteCellData(reinterpret_cast< void * >( &(loadBuffer.at(0)) )); + return this->m_Identifier; } - wasmMeshIO->Write(); - } + OutputMeshIO() = default; + ~OutputMeshIO() + { + if (wasm::Pipeline::get_use_memory_io()) + { +#ifndef ITK_WASM_NO_MEMORY_IO + if (!this->m_MeshIO.IsNull() && !this->m_Identifier.empty()) + { + const auto index = std::stoi(this->m_Identifier); + auto wasmMeshIOBase = itk::WasmMeshIOBase::New(); + wasmMeshIOBase->SetMeshIO(this->m_MeshIO); + setMemoryStoreOutputDataObject(0, index, wasmMeshIOBase); + + if (this->m_InformationOnly) + { + return; + } + + const auto pointsSize = wasmMeshIOBase->GetPointsContainer()->size(); + if (pointsSize) + { + const auto pointsAddress = reinterpret_cast(&(wasmMeshIOBase->GetPointsContainer()->at(0))); + setMemoryStoreOutputArray(0, index, 0, pointsAddress, pointsSize); + } + + const auto cellsSize = wasmMeshIOBase->GetCellsContainer()->size(); + if (cellsSize) + { + const auto cellsAddress = reinterpret_cast(&(wasmMeshIOBase->GetCellsContainer()->at(0))); + setMemoryStoreOutputArray(0, index, 1, cellsAddress, cellsSize); + } + + const auto pointDataSize = wasmMeshIOBase->GetPointDataContainer()->size(); + if (pointDataSize) + { + const auto pointDataAddress = reinterpret_cast(&(wasmMeshIOBase->GetPointDataContainer()->at(0))); + setMemoryStoreOutputArray(0, index, 2, pointDataAddress, pointDataSize); + } + + const auto cellDataSize = wasmMeshIOBase->GetCellDataContainer()->size(); + if (cellDataSize) + { + const auto cellDataAddress = reinterpret_cast(&(wasmMeshIOBase->GetCellDataContainer()->at(0))); + setMemoryStoreOutputArray(0, index, 3, cellDataAddress, cellDataSize); + } + } #else - std::cerr << "Filesystem IO not supported" << std::endl; - abort(); + std::cerr << "Memory IO not supported" << std::endl; + abort(); #endif - } - } -protected: - typename MeshIOBase::Pointer m_MeshIO; + } + else + { +#ifndef ITK_WASM_NO_FILESYSTEM_IO + if (!this->m_MeshIO.IsNull() && !this->m_Identifier.empty()) + { + this->m_MeshIO->ReadMeshInformation(); + + auto wasmMeshIO = itk::WasmMeshIO::New(); + wasmMeshIO->SetFileName(this->m_Identifier); + + const unsigned int dimension = this->m_MeshIO->GetPointDimension(); + wasmMeshIO->SetPointDimension(dimension); + wasmMeshIO->SetPointComponentType(this->m_MeshIO->GetPointComponentType()); + wasmMeshIO->SetPointPixelType(this->m_MeshIO->GetPointPixelType()); + wasmMeshIO->SetPointPixelComponentType(this->m_MeshIO->GetPointPixelComponentType()); + wasmMeshIO->SetNumberOfPointPixelComponents(this->m_MeshIO->GetNumberOfPointPixelComponents()); + wasmMeshIO->SetCellComponentType(this->m_MeshIO->GetCellComponentType()); + wasmMeshIO->SetCellPixelType(this->m_MeshIO->GetCellPixelType()); + wasmMeshIO->SetCellPixelComponentType(this->m_MeshIO->GetCellPixelComponentType()); + wasmMeshIO->SetNumberOfCellPixelComponents(this->m_MeshIO->GetNumberOfCellPixelComponents()); + wasmMeshIO->SetNumberOfPoints(this->m_MeshIO->GetNumberOfPoints()); + wasmMeshIO->SetNumberOfPointPixels(this->m_MeshIO->GetNumberOfPointPixels()); + wasmMeshIO->SetNumberOfCells(this->m_MeshIO->GetNumberOfCells()); + wasmMeshIO->SetNumberOfCellPixels(this->m_MeshIO->GetNumberOfCellPixels()); + wasmMeshIO->SetCellBufferSize(this->m_MeshIO->GetCellBufferSize()); + + wasmMeshIO->WriteMeshInformation(); + + if (this->m_InformationOnly) + { + return; + } + + SizeValueType numberOfBytes = this->m_MeshIO->GetNumberOfPoints() * this->m_MeshIO->GetPointDimension() * ITKComponentSize(this->m_MeshIO->GetPointComponentType()); + std::vector loadBuffer(numberOfBytes); + if (numberOfBytes) + { + this->m_MeshIO->ReadPoints(reinterpret_cast(&(loadBuffer.at(0)))); + wasmMeshIO->WritePoints(reinterpret_cast(&(loadBuffer.at(0)))); + } + + numberOfBytes = static_cast(this->m_MeshIO->GetCellBufferSize() * ITKComponentSize(this->m_MeshIO->GetCellComponentType())); + if (numberOfBytes) + { + loadBuffer.resize(numberOfBytes); + this->m_MeshIO->ReadCells(reinterpret_cast(&(loadBuffer.at(0)))); + wasmMeshIO->WriteCells(reinterpret_cast(&(loadBuffer.at(0)))); + } + + numberOfBytes = + static_cast( + this->m_MeshIO->GetNumberOfPointPixels() * this->m_MeshIO->GetNumberOfPointPixelComponents() * ITKComponentSize(this->m_MeshIO->GetPointPixelComponentType())); + if (numberOfBytes) + { + loadBuffer.resize(numberOfBytes); + this->m_MeshIO->ReadPointData(reinterpret_cast(&(loadBuffer.at(0)))); + wasmMeshIO->WritePointData(reinterpret_cast(&(loadBuffer.at(0)))); + } + + numberOfBytes = + static_cast( + this->m_MeshIO->GetNumberOfCellPixels() * this->m_MeshIO->GetNumberOfCellPixelComponents() * ITKComponentSize(this->m_MeshIO->GetCellPixelComponentType())); + if (numberOfBytes) + { + loadBuffer.resize(numberOfBytes); + this->m_MeshIO->ReadCellData(reinterpret_cast(&(loadBuffer.at(0)))); + wasmMeshIO->WriteCellData(reinterpret_cast(&(loadBuffer.at(0)))); + } + + wasmMeshIO->Write(); + } +#else + std::cerr << "Filesystem IO not supported" << std::endl; + abort(); +#endif + } + } - std::string m_Identifier; + protected: + typename MeshIOBase::Pointer m_MeshIO; - bool m_InformationOnly{ false }; -}; + std::string m_Identifier; -bool lexical_cast(const std::string &input, OutputMeshIO &outputMeshIO) -{ - outputMeshIO.SetIdentifier(input); - return true; -} + bool m_InformationOnly{false}; + }; + + bool lexical_cast(const std::string &input, OutputMeshIO &outputMeshIO) + { + outputMeshIO.SetIdentifier(input); + return true; + } -} // namespace wasm + } // namespace wasm } // namespace itk #endif diff --git a/include/itkTransformJSON.h b/include/itkTransformJSON.h new file mode 100644 index 000000000..7f20a72e3 --- /dev/null +++ b/include/itkTransformJSON.h @@ -0,0 +1,132 @@ +/*========================================================================= + * + * 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 itkTransformJSON_h +#define itkTransformJSON_h + +#include "itkFloatTypesJSON.h" + +#include + +#include "glaze/glaze.hpp" + +namespace itk +{ + enum class JSONTransformParameterizationEnum + { + Identity, + Translation, + Euler2D, + Euler3D, + Rigid2D, + Rigid3DPerspective, + VersorRigid3D, + Versor, + ScaleLogarithmic, + ScaleSkewVersor3D, + Scale, + Similarity2D, + Similarity3D, + QuaternionRigid, + Affine, + ScalableAffine, + AzimuthElevationToCartesian, + BSpline, + BSplineSmoothingOnUpdateDisplacementField, + ConstantVelocityField, + DisplacementField, + GaussianExponentialDiffeomorphic, + GaussianSmoothingOnUpdateDisplacementField, + GaussianSmoothingOnUpdateTimeVaryingVelocityField, + TimeVaryingVelocityField, + VelocityField, + }; + + /** \class TransformTypeJSON + * + * \brief Transform type JSON representation data structure. + * + * \ingroup WebAssemblyInterface + */ + struct TransformTypeJSON + { + JSONTransformParameterizationEnum transformParameterization; + JSONFloatTypesEnum parametersValueType; + unsigned int inputDimension; + unsigned int outputDimension; + }; + + /** \class TransformJSON + * + * \brief Transform JSON representation data structure. + * + * \ingroup WebAssemblyInterface + */ + struct TransformJSON + { + TransformTypeJSON transformType; + uint64_t numberOfFixedParameters; + uint64_t numberOfParameters; + std::string name; + std::string inputSpaceName; + std::string outputSpaceName; + std::string fixedParameters{ "data:application/vnd.itk.path,data/fixed-parameters.raw" }; + std::string parameters{ "data:application/vnd.itk.path,data/parameters.raw" }; + }; + + /** \class TransformListJSON + * + * \brief Transform list JSON representation data structure. + * + * \ingroup WebAssemblyInterface + */ + using TransformListJSON = std::list; +} // end namespace itk + +template <> +struct glz::meta { + using enum itk::JSONTransformParameterizationEnum; + static constexpr auto value = glz::enumerate(Identity, + Translation, + Euler2D, + Euler3D, + Rigid2D, + Rigid3DPerspective, + VersorRigid3D, + Versor, + ScaleLogarithmic, + ScaleSkewVersor3D, + Scale, + Similarity2D, + Similarity3D, + QuaternionRigid, + Affine, + ScalableAffine, + AzimuthElevationToCartesian, + BSpline, + BSplineSmoothingOnUpdateDisplacementField, + ConstantVelocityField, + DisplacementField, + GaussianExponentialDiffeomorphic, + GaussianSmoothingOnUpdateDisplacementField, + GaussianSmoothingOnUpdateTimeVaryingVelocityField, + TimeVaryingVelocityField, + VelocityField + ); +}; + +#endif // itkWasmTransformIO_h diff --git a/include/itkWasmIOCommon.h b/include/itkWasmIOCommon.h new file mode 100644 index 000000000..25ce7b1b1 --- /dev/null +++ b/include/itkWasmIOCommon.h @@ -0,0 +1,93 @@ +/*========================================================================= + * + * 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 itkWasmIOCommon_h +#define itkWasmIOCommon_h +// Common functions used across the WebAssembly IO classes + +#include "WebAssemblyInterfaceExport.h" + +#include "itkIntTypes.h" +#include "itkMath.h" +#include "itkCommonEnums.h" + +#include + +#include "cbor.h" + +namespace itk +{ + +/** \brief Opens a file for reading and random access + * + * \param[out] inputStream is an istream presumed to be opened for reading + * \param[in] filename is the name of the file + * \param[in] ascii optional (default is false); + * if true than the file will be opened in ASCII mode, + * which generally only applies to Windows + * + * The stream is closed if it's already opened. If an error is + * encountered than an exception will be thrown. + */ +WebAssemblyInterface_EXPORT +void +openFileForReading(std::ifstream & inputStream, const std::string & filename, + bool ascii = false); + +/** \brief Opens a file for writing and random access + * + * \param[out] outputStream is an ostream presumed to be opened for writing + * \param[in] filename is the name of the file + * \param[in] truncate optional (default is true); + * if true than the file's existing content is truncated, + * if false than the file is opened for reading and + * writing with existing content intact + * \param[in] ascii optional (default is false); + * if true than the file will be opened in ASCII mode, + * which generally only applies to Windows + * + * The stream is closed if it's already opened. If an error is + * encountered than an exception will be thrown. + */ +WebAssemblyInterface_EXPORT +void +openFileForWriting(std::ofstream & outputStream, const std::string & filename, + bool truncate = true, bool ascii = false); + +/** Convenient method to read a buffer as binary. Return true on success. */ +WebAssemblyInterface_EXPORT +bool +readBufferAsBinary(std::istream & os, void *buffer, SizeValueType numberOfBytesToBeRead); + +WebAssemblyInterface_EXPORT +bool fileNameIsCBOR(const char * fileName); + +WebAssemblyInterface_EXPORT +void +readCBORBuffer(const cbor_item_t * index, const char * dataName, void * buffer, SizeValueType numberOfBytesToBeRead); + +WebAssemblyInterface_EXPORT +void +writeCBORBuffer(cbor_item_t * index, const char * dataName, const void * buffer, SizeValueType numberOfBytesToWrite, IOComponentEnum ioComponent); + +WebAssemblyInterface_EXPORT +size_t +ITKComponentSize( const CommonEnums::IOComponent ); + +} // end namespace itk + +#endif // itkWasmIOCommon_h diff --git a/include/itkWasmImageIO.h b/include/itkWasmImageIO.h index 428e9debd..e89139f8a 100644 --- a/include/itkWasmImageIO.h +++ b/include/itkWasmImageIO.h @@ -30,9 +30,9 @@ namespace itk * \brief Read and write an itk::Image in a web-friendly format. * * This format is intended to facilitate data exchange in itk-wasm. - * It reads and writes an itk-wasm Image object in a CbOR file on the + * It reads and writes an itk-wasm Image object in a CBOR file on the * filesystem with JSON files and binary files for TypedArrays. - * + * * The file extensions used are .iwi and .iwi.cbor. * * \ingroup IOFilters diff --git a/include/itkWasmMeshIO.h b/include/itkWasmMeshIO.h index 0e66141ed..1f7a55579 100644 --- a/include/itkWasmMeshIO.h +++ b/include/itkWasmMeshIO.h @@ -33,10 +33,10 @@ namespace itk * * This format is intended to facilitate data exchange in itk-wasm. * It reads and writes an itk-wasm Mesh object where TypedArrays are - * replaced by binary files on the filesystem or in a ZIP file. - * - * The format is experimental and subject to change. We mean it. + * replaced by binary files on the filesystem or in a CBOR file. * + * The file extensions used are .iwm and .iwm.cbor. + * * \ingroup IOFilters * \ingroup WebAssemblyInterface */ @@ -95,52 +95,11 @@ class WebAssemblyInterface_EXPORT WasmMeshIO: public MeshIOBase rapidjson::Document GetJSON(); #endif - static size_t ITKComponentSize( const CommonEnums::IOComponent ); - protected: WasmMeshIO(); ~WasmMeshIO() override; void PrintSelf(std::ostream & os, Indent indent) const override; - /** \brief Opens a file for reading and random access - * - * \param[out] inputStream is an istream presumed to be opened for reading - * \param[in] filename is the name of the file - * \param[in] ascii optional (default is false); - * if true than the file will be opened in ASCII mode, - * which generally only applies to Windows - * - * The stream is closed if it's already opened. If an error is - * encountered than an exception will be thrown. - */ - void OpenFileForReading(std::ifstream & inputStream, const std::string & filename, - bool ascii = false); - - /** \brief Opens a file for writing and random access - * - * \param[out] outputStream is an ostream presumed to be opened for writing - * \param[in] filename is the name of the file - * \param[in] truncate optional (default is true); - * if true than the file's existing content is truncated, - * if false than the file is opened for reading and - * writing with existing content intact - * \param[in] ascii optional (default is false); - * if true than the file will be opened in ASCII mode, - * which generally only applies to Windows - * - * The stream is closed if it's already opened. If an error is - * encountered than an exception will be thrown. - */ - void OpenFileForWriting(std::ofstream & outputStream, const std::string & filename, - bool truncate = true, bool ascii = false); - - /** Convenient method to read a buffer as binary. Return true on success. */ - bool ReadBufferAsBinary(std::istream & os, void *buffer, SizeValueType numberOfBytesToBeRead); - - bool FileNameIsCBOR(); - void ReadCBORBuffer(const char * dataName, void * buffer, SizeValueType numberOfBytesToBeRead); - void WriteCBORBuffer(const char * dataName, void * buffer, SizeValueType numberOfBytesToWrite, IOComponentEnum ioComponent); - /** Reads in the mesh information and populates the related buffers. */ void ReadCBOR(void * buffer = nullptr, unsigned char * cborBuffer = nullptr, size_t cborBufferLength = 0); /** Writes the buffers into the CBOR item and the buffer out to disk. */ diff --git a/include/itkWasmTransformIO.h b/include/itkWasmTransformIO.h new file mode 100644 index 000000000..eb5b15c1e --- /dev/null +++ b/include/itkWasmTransformIO.h @@ -0,0 +1,160 @@ +/*========================================================================= + * + * 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 itkWasmTransformIO_h +#define itkWasmTransformIO_h +#include "WebAssemblyInterfaceExport.h" + +#include "itkTransformIOBase.h" +#include "itkMacro.h" +#include + +#include "itkTransformJSON.h" +#include "cbor.h" + +namespace itk +{ +/** \class WasmTransformIOTemplate + * + * \brief Read and write the an itk::Transform in a format for interfacing in WebAssembly (Wasm). + * + * This format is intended to facilitate data exchange in itk-wasm. + * It reads and writes an itk-wasm Transform object where TypedArrays are + * replaced by binary files on the filesystem or in a CBOR file. + * + * The file extensions used are .iwt and .iwt.cbor. + * + * \ingroup IOFilters + * \ingroup WebAssemblyInterface + */ +template +class ITK_TEMPLATE_EXPORT WasmTransformIOTemplate : public TransformIOBaseTemplate +{ +public: + using Self = WasmTransformIOTemplate; + using Superclass = TransformIOBaseTemplate; + using Pointer = SmartPointer; + using typename Superclass::TransformListType; + using typename Superclass::TransformPointer; + using typename Superclass::TransformType; + using ParametersType = typename TransformType::ParametersType; + using ParametersValueType = typename TransformType::ParametersValueType; + using FixedParametersType = typename TransformType::FixedParametersType; + using FixedParametersValueType = typename TransformType::FixedParametersValueType; + + using ConstTransformListType = typename TransformIOBaseTemplate::ConstTransformListType; + + /** Method for creation through the object factory. */ + itkNewMacro(Self); + + /** Run-time type information (and related methods). */ + itkTypeMacro(WasmTransformIOTemplate, TransformIOBaseTemplate); + + /** Reads the data from disk into the memory buffer provided. */ + void + Read() override; + + bool + CanReadFile(const char *) override; + + /** Set the JSON representation of the image information. */ + void + SetJSON(const TransformListJSON & json); + + /*-------- This part of the interfaces deals with writing data ----- */ + + bool + CanWriteFile(const char *) override; + + void + Write() override; + +#if !defined(ITK_WRAPPING_PARSER) + /** Get the JSON representation of the mesh information. */ + auto + GetJSON() -> TransformListJSON; +#endif + +protected: + WasmTransformIOTemplate(); + ~WasmTransformIOTemplate() override; + void + PrintSelf(std::ostream & os, Indent indent) const override; + + auto + ReadTransformInformation() -> const TransformListJSON; + void + ReadFixedParameters(const TransformListJSON & json); + void + ReadParameters(const TransformListJSON & json); + + void + WriteTransformInformation(); + void + WriteFixedParameters(); + void + WriteParameters(); + + /** Reads in the transform information and populates the related buffers. */ + void + ReadCBOR(); + /** Writes the buffers into the CBOR item and the buffer out to disk. */ + void + WriteCBOR(); + + static std::string + TransformParameterizationString(const TransformTypeJSON & transform); + + cbor_item_t * m_CBORRoot{ nullptr }; + +private: + ITK_DISALLOW_COPY_AND_ASSIGN(WasmTransformIOTemplate); +}; +} // end namespace itk + +#endif // itkWasmTransformIO_h + +/** Explicit instantiations */ +#ifndef ITK_TEMPLATE_EXPLICIT_WasmTransformIO +// Explicit instantiation is required to ensure correct dynamic_cast +// behavior across shared libraries. +// +// IMPORTANT: Since within the same compilation unit, +// ITK_TEMPLATE_EXPLICIT_ defined and undefined states +// need to be considered. This code *MUST* be *OUTSIDE* the header +// guards. +// +#if defined(WebAssemblyInterface_EXPORTS) +// We are building this library +# define WebAssemblyInterface_EXPORT_EXPLICIT ITK_FORWARD_EXPORT +#else +// We are using this library +# define WebAssemblyInterface_EXPORT_EXPLICIT WebAssemblyInterface_EXPORT +#endif +namespace itk +{ +ITK_GCC_PRAGMA_DIAG_PUSH() +ITK_GCC_PRAGMA_DIAG(ignored "-Wattributes") + +extern template class WebAssemblyInterface_EXPORT_EXPLICIT WasmTransformIOTemplate; +extern template class WebAssemblyInterface_EXPORT_EXPLICIT WasmTransformIOTemplate; + +ITK_GCC_PRAGMA_DIAG_POP() + +} // end namespace itk +#undef WebAssemblyInterface_EXPORT_EXPLICIT +#endif diff --git a/include/itkWasmTransformIOFactory.h b/include/itkWasmTransformIOFactory.h index bef0ecf3e..87ff92503 100644 --- a/include/itkWasmTransformIOFactory.h +++ b/include/itkWasmTransformIOFactory.h @@ -30,19 +30,21 @@ namespace itk * * \ingroup WebAssemblyInterface */ -class WebAssemblyInterface_EXPORT WasmTransformIOFactory: public ObjectFactoryBase +class WebAssemblyInterface_EXPORT WasmTransformIOFactory : public ObjectFactoryBase { public: /** Standard class typedefs. */ - typedef WasmTransformIOFactory Self; - typedef ObjectFactoryBase Superclass; - typedef SmartPointer< Self > Pointer; - typedef SmartPointer< const Self > ConstPointer; + typedef WasmTransformIOFactory Self; + typedef ObjectFactoryBase Superclass; + typedef SmartPointer Pointer; + typedef SmartPointer ConstPointer; /** Class methods used to interface with the registered factories. */ - const char * GetITKSourceVersion() const override; + const char * + GetITKSourceVersion() const override; - const char * GetDescription() const override; + const char * + GetDescription() const override; /** Method for class instantiation. */ itkFactorylessNewMacro(Self); @@ -51,7 +53,8 @@ class WebAssemblyInterface_EXPORT WasmTransformIOFactory: public ObjectFactoryBa itkTypeMacro(WasmTransformIOFactory, ObjectFactoryBase); /** Register one factory of this type */ - static void RegisterOneFactory() + static void + RegisterOneFactory() { WasmTransformIOFactory::Pointer wasmFactory = WasmTransformIOFactory::New(); diff --git a/itk-module.cmake b/itk-module.cmake index 9d636d4c7..affae1d2a 100644 --- a/itk-module.cmake +++ b/itk-module.cmake @@ -17,6 +17,7 @@ itk_module(WebAssemblyInterface ITKTestKernel ITKMesh ITKImageGrid + ITKIOTransformHDF5 FACTORY_NAMES ImageIO::Wasm MeshIO::Wasm diff --git a/model/itk-wasm.yml b/model/itk-wasm.yml index a580e9c7e..cdeb0bb47 100644 --- a/model/itk-wasm.yml +++ b/model/itk-wasm.yml @@ -411,18 +411,13 @@ classes: A type that can be represented in JSON. class_uri: wasm:JsonCompatible - Transform: + TransformType: description: >- - Representation of a spatial transformation. - class_uri: wasm:Transform + Representation of an N-dimensional scientific spatial transformation. + class_uri: wasm:TransformType slots: - parametersValueType attributes: - name: - description: >- - Name of the transform. - range: string - required: true inputDimension: description: >- Dimension of the input space. @@ -433,10 +428,36 @@ classes: Dimension of the output space. range: integer required: true + transformParameterization: + description: >- + How the transform is parameterized. + range: TransformParameterizations + required: true + + Transform: + description: >- + Representation of a spatial transformation. + class_uri: wasm:Transform + attributes: transformType: description: >- Type of the transform. - range: TransformTypes + range: TransformType + required: true + numberOfFixedParameters: + description: >- + Number of fixed parameters in the transform. + range: integer + required: true + numberOfParameters: + description: >- + Number of parameters in the transform. + range: integer + required: true + name: + description: >- + Name of the transform. + range: string required: true inputSpaceName: description: >- @@ -450,7 +471,7 @@ classes: required: true fixedParameters: description: >- - Fixed parameters of the transform. + Fixed parameters of the transform. These are always double / float64. range: BinaryData required: true parameters: @@ -458,16 +479,6 @@ classes: Parameters of the transform. range: BinaryData required: true - numberOfParameters: - description: >- - Number of parameters in the transform. - range: integer - required: true - numberOfFixedParameters: - description: >- - Number of fixed parameters in the transform. - range: integer - required: true TransformList: is_a: InterfaceType @@ -621,33 +632,35 @@ enums: VariableLengthVector: VariableSizeMatrix: - TransformTypes: + TransformParameterizations: description: >- - Transform types. + How a transform is parameterized, defining how its parameters should be interpreted. + + A detailed description of each transform type can be found in the ITK Software Guide: https://itk.org/ITKSoftwareGuide/html/Book1/ITKSoftwareGuide-Book1ch4.html permissible_values: - IdentityTransform: - AffineTransform: - TranslationTransform: - AzimuthElevationToCartesianTransform: - BSplineTransform: - Euler2DTransform: - Euler3DTransform: - QuaternionRigidTransform: - Rigid2DTransform: - Rigid3DPerspectiveTransform: - ScalableAffineTransform: - ScaleLogarithmicTransform: - ScaleSkewVersor3DTransform: - ScaleTransform: - Similarity2DTransform: - Similarity3DTransform: - VersorRigid3DTransform: - VersorTransform: - BSplineSmoothingOnUpdateDisplacementFieldTransform: - ConstantVelocityFieldTransform: - DisplacementFieldTransform: - GaussianExponentialDiffeomorphicTransform: - GaussianSmoothingOnUpdateDisplacementFieldTransform: - GaussianSmoothingOnUpdateTimeVaryingVelocityFieldTransform: - TimeVaryingVelocityFieldTransform: - VelocityFieldTransform: + Identity: + Translation: + Euler2D: + Euler3D: + Rigid2D: + Rigid3DPerspective: + VersorRigid3D: + Versor: + ScaleLogarithmic: + ScaleSkewVersor3D: + Scale: + Similarity2D: + Similarity3D: + QuaternionRigid: + Affine: + ScalableAffine: + AzimuthElevationToCartesian: + BSpline: + BSplineSmoothingOnUpdateDisplacementField: + ConstantVelocityField: + DisplacementField: + GaussianExponentialDiffeomorphic: + GaussianSmoothingOnUpdateDisplacementField: + GaussianSmoothingOnUpdateTimeVaryingVelocityField: + TimeVaryingVelocityField: + VelocityField: diff --git a/packages/core/typescript/itk-wasm/test/data/cow.iwm/index.json b/packages/core/typescript/itk-wasm/test/data/cow.iwm/index.json index cbfa1ab62..87afefcac 100644 --- a/packages/core/typescript/itk-wasm/test/data/cow.iwm/index.json +++ b/packages/core/typescript/itk-wasm/test/data/cow.iwm/index.json @@ -17,6 +17,6 @@ "cellBufferSize": 18856, "points": "data:application/vnd.itk.path,data/points.raw", "cells": "data:application/vnd.itk.path,data/cells.raw", - "pointData": "data:application/vnd.itk.path,data/pointData.raw", - "cellData": "data:application/vnd.itk.path,data/cellData.raw" + "pointData": "data:application/vnd.itk.path,data/point-data.raw", + "cellData": "data:application/vnd.itk.path,data/cell-data.raw" } \ No newline at end of file diff --git a/packages/core/typescript/itk-wasm/test/pipelines/mesh-read-write-pipeline/cow.iwm/index.json b/packages/core/typescript/itk-wasm/test/pipelines/mesh-read-write-pipeline/cow.iwm/index.json index cbfa1ab62..87afefcac 100644 --- a/packages/core/typescript/itk-wasm/test/pipelines/mesh-read-write-pipeline/cow.iwm/index.json +++ b/packages/core/typescript/itk-wasm/test/pipelines/mesh-read-write-pipeline/cow.iwm/index.json @@ -17,6 +17,6 @@ "cellBufferSize": 18856, "points": "data:application/vnd.itk.path,data/points.raw", "cells": "data:application/vnd.itk.path,data/cells.raw", - "pointData": "data:application/vnd.itk.path,data/pointData.raw", - "cellData": "data:application/vnd.itk.path,data/cellData.raw" + "pointData": "data:application/vnd.itk.path,data/point-data.raw", + "cellData": "data:application/vnd.itk.path,data/cell-data.raw" } \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 44ee9bfc5..f474dd77c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,6 +4,7 @@ set(WebAssemblyInterface_SRCS itkPipeline.cxx itkMetaDataDictionaryJSON.cxx itkWasmExports.cxx + itkWasmIOCommon.cxx itkWasmDataObject.cxx itkWasmImageIOBase.cxx itkWasmImageIOFactory.cxx @@ -12,6 +13,7 @@ set(WebAssemblyInterface_SRCS itkWasmMeshIOFactory.cxx itkWasmMeshIO.cxx itkWasmTransformIOFactory.cxx + itkWasmTransformIO.cxx itkWasmStringStream.cxx itkInputTextStream.cxx itkOutputTextStream.cxx diff --git a/src/itkWasmIOCommon.cxx b/src/itkWasmIOCommon.cxx new file mode 100644 index 000000000..4d5dcef94 --- /dev/null +++ b/src/itkWasmIOCommon.cxx @@ -0,0 +1,274 @@ +/*========================================================================= + * + * 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 "itkWasmIOCommon.h" + +#include "itksys/SystemTools.hxx" + +#include +#include + +#include "cbor.h" + +namespace itk +{ + +void +openFileForReading(std::ifstream & inputStream, const std::string & filename, bool ascii) +{ + // Make sure that we have a file to + if ( filename.empty() ) + { + throw std::runtime_error( "A FileName must be specified." ); + } + + // Close file from any previous image + if ( inputStream.is_open() ) + { + inputStream.close(); + } + + std::ios::openmode mode = std::ios::in; + if ( !ascii ) + { + mode |= std::ios::binary; + } + + inputStream.open( filename.c_str(), mode ); + + if ( !inputStream.is_open() || inputStream.fail() ) + { + std::ostringstream ostrm; + ostrm << "Could not open file: " + << filename << " for reading." + << std::endl + << "Reason: " + << itksys::SystemTools::GetLastSystemError(); + throw std::runtime_error(ostrm.str()); + } +} + + +void +openFileForWriting(std::ofstream & outputStream, const std::string & filename, bool truncate, bool ascii) +{ + // Make sure that we have a file to + if ( filename.empty() ) + { + throw std::runtime_error("A FileName must be specified." ); + } + + // Close file from any previous image + if ( outputStream.is_open() ) + { + outputStream.close(); + } + + std::ios::openmode mode = std::ios::out; + if ( truncate ) + { + // typically, ios::out also implies ios::trunc, but being explicit is safer + mode |= std::ios::trunc; + } + else + { + mode |= std::ios::in; + // opening a nonexistent file for reading + writing is not allowed on some platforms + if ( !itksys::SystemTools::FileExists( filename.c_str() ) ) + { + itksys::SystemTools::Touch( filename.c_str(), true ); + // don't worry about failure here, errors should be detected later when the file + // is "actually" opened, unless there is a race condition + } + } + if ( !ascii ) + { + mode |= std::ios::binary; + } + + outputStream.open( filename.c_str(), mode ); + + if ( !outputStream.is_open() || outputStream.fail() ) + { + std::ostringstream ostrm; + ostrm << "Could not open file: " + << filename << " for writing." + << std::endl + << "Reason: " + << itksys::SystemTools::GetLastSystemError(); + throw std::runtime_error(ostrm.str()); + } +} + +/** Convenient method to read a buffer as binary. Return true on success. */ +bool +readBufferAsBinary(std::istream & is, void *buffer, SizeValueType num) +{ + const auto numberOfBytesToBeRead = Math::CastWithRangeCheck< std::streamsize >(num); + + is.read(static_cast< char * >( buffer ), numberOfBytesToBeRead); + + const std::streamsize numberOfBytesRead = is.gcount(); + + if ( ( numberOfBytesRead != numberOfBytesToBeRead ) || is.fail() ) + { + return false; // read failed + } + + return true; +} + + +bool +fileNameIsCBOR(const char * fileName) +{ + const std::string path(fileName); + std::string::size_type cborPos = path.rfind(".cbor"); + if ( cborPos != std::string::npos ) + { + return true; + } + return false; +} + + +void +readCBORBuffer(const cbor_item_t * index, const char * dataName, void * buffer, SizeValueType numberOfBytesToBeRead) +{ + if (index == nullptr) { + throw std::logic_error("Read information before reading the data buffer"); + } + const size_t indexCount = cbor_map_size(index); + const struct cbor_pair * indexHandle = cbor_map_handle(index); + for (size_t ii = 0; ii < indexCount; ++ii) + { + const std::string_view key(reinterpret_cast(cbor_string_handle(indexHandle[ii].key)), cbor_string_length(indexHandle[ii].key)); + if (key == dataName) + { + const cbor_item_t * dataItem = cbor_tag_item(indexHandle[ii].value); + const char * dataHandle = reinterpret_cast< char * >( cbor_bytestring_handle(dataItem) ); + std::memcpy(buffer, dataHandle, numberOfBytesToBeRead); + } + } +} + +void +writeCBORBuffer(cbor_item_t * index, const char * dataName, const void * buffer, SizeValueType numberOfBytesToWrite, IOComponentEnum ioComponent) +{ + if (index == nullptr) { + throw std::logic_error("Call write information before writing the data buffer"); + } + cbor_item_t * dataItem = cbor_build_bytestring(reinterpret_cast< const unsigned char *>(buffer), numberOfBytesToWrite); + uint64_t tag = 0; + // Todo: support endianness + // https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml + switch (ioComponent) { + case IOComponentEnum::CHAR: + tag = 64; + break; + case IOComponentEnum::UCHAR: + tag = 64; + break; + case IOComponentEnum::SHORT: + tag = 73; + break; + case IOComponentEnum::USHORT: + tag = 69; + break; + case IOComponentEnum::INT: + tag = 74; + break; + case IOComponentEnum::UINT: + tag = 70; + break; + case IOComponentEnum::LONG: + tag = 75; + break; + case IOComponentEnum::ULONG: + tag = 71; + break; + case IOComponentEnum::LONGLONG: + tag = 75; + break; + case IOComponentEnum::ULONGLONG: + tag = 71; + break; + case IOComponentEnum::FLOAT: + tag = 85; + break; + case IOComponentEnum::DOUBLE: + tag = 86; + break; + default: + throw std::logic_error("Unexpected component type"); + } + cbor_item_t * dataTag = cbor_new_tag(tag); + cbor_tag_set_item(dataTag, cbor_move(dataItem)); + cbor_map_add(index, + cbor_pair{ + cbor_move(cbor_build_string(dataName)), + cbor_move(dataTag)}); +} + +size_t +ITKComponentSize(const CommonEnums::IOComponent itkComponentType) +{ + switch ( itkComponentType ) + { + case CommonEnums::IOComponent::CHAR: + return sizeof( uint8_t ); + + case CommonEnums::IOComponent::UCHAR: + return sizeof( uint8_t ); + + case CommonEnums::IOComponent::SHORT: + return sizeof( int16_t ); + + case CommonEnums::IOComponent::USHORT: + return sizeof( uint16_t ); + + case CommonEnums::IOComponent::INT: + return sizeof( int32_t ); + + case CommonEnums::IOComponent::UINT: + return sizeof( uint32_t ); + + case CommonEnums::IOComponent::LONG: + return sizeof( int64_t ); + + case CommonEnums::IOComponent::ULONG: + return sizeof( uint64_t ); + + case CommonEnums::IOComponent::LONGLONG: + return sizeof( int64_t ); + + case CommonEnums::IOComponent::ULONGLONG: + return sizeof( uint64_t ); + + case CommonEnums::IOComponent::FLOAT: + return sizeof( float ); + + case CommonEnums::IOComponent::DOUBLE: + return sizeof( double ); + + default: + return sizeof( int8_t ); + } +} + +} // end namespace itk diff --git a/src/itkWasmImageIO.cxx b/src/itkWasmImageIO.cxx index 0598ddb61..9ce2dbef1 100644 --- a/src/itkWasmImageIO.cxx +++ b/src/itkWasmImageIO.cxx @@ -23,6 +23,7 @@ #include "itkWasmPixelTypeFromIOPixelEnum.h" #include "itkIOPixelEnumFromWasmPixelType.h" #include "itkMetaDataDictionaryJSON.h" +#include "itkWasmIOCommon.h" #include "itkMetaDataObject.h" #include "itkIOCommon.h" @@ -567,7 +568,7 @@ ::Read( void *buffer ) this->OpenFileForReading( dataStream, dataFile.c_str() ); const SizeValueType numberOfBytesToBeRead = static_cast< SizeValueType >( this->GetImageSizeInBytes() ); - if ( !this->ReadBufferAsBinary( dataStream, buffer, numberOfBytesToBeRead ) ) + if ( !readBufferAsBinary( dataStream, buffer, numberOfBytesToBeRead ) ) { itkExceptionMacro(<< "Read failed: Wanted " << numberOfBytesToBeRead diff --git a/src/itkWasmMeshIO.cxx b/src/itkWasmMeshIO.cxx index a752d7322..6bb5a5fad 100644 --- a/src/itkWasmMeshIO.cxx +++ b/src/itkWasmMeshIO.cxx @@ -22,6 +22,7 @@ #include "itkIOComponentEnumFromWasmComponentType.h" #include "itkWasmPixelTypeFromIOPixelEnum.h" #include "itkIOPixelEnumFromWasmPixelType.h" +#include "itkWasmIOCommon.h" #include "itkMetaDataObject.h" #include "itkIOCommon.h" @@ -62,262 +63,6 @@ ::PrintSelf(std::ostream & os, Indent indent) const } -void -WasmMeshIO -::OpenFileForReading(std::ifstream & inputStream, const std::string & filename, bool ascii) -{ - // Make sure that we have a file to - if ( filename.empty() ) - { - itkExceptionMacro( << "A FileName must be specified." ); - } - - // Close file from any previous image - if ( inputStream.is_open() ) - { - inputStream.close(); - } - - // Open the new file for reading - itkDebugMacro( << "Opening file for reading: " << filename ); - - std::ios::openmode mode = std::ios::in; - if ( !ascii ) - { - mode |= std::ios::binary; - } - - inputStream.open( filename.c_str(), mode ); - - if ( !inputStream.is_open() || inputStream.fail() ) - { - itkExceptionMacro( << "Could not open file: " - << filename << " for reading." - << std::endl - << "Reason: " - << itksys::SystemTools::GetLastSystemError() ); - } -} - - -void -WasmMeshIO -::OpenFileForWriting(std::ofstream & outputStream, const std::string & filename, bool truncate, bool ascii) -{ - // Make sure that we have a file to - if ( filename.empty() ) - { - itkExceptionMacro( << "A FileName must be specified." ); - } - - // Close file from any previous image - if ( outputStream.is_open() ) - { - outputStream.close(); - } - - // Open the new file for writing - itkDebugMacro( << "Opening file for writing: " << filename ); - - std::ios::openmode mode = std::ios::out; - if ( truncate ) - { - // typically, ios::out also implies ios::trunc, but being explicit is safer - mode |= std::ios::trunc; - } - else - { - mode |= std::ios::in; - // opening a nonexistent file for reading + writing is not allowed on some platforms - if ( !itksys::SystemTools::FileExists( filename.c_str() ) ) - { - itksys::SystemTools::Touch( filename.c_str(), true ); - // don't worry about failure here, errors should be detected later when the file - // is "actually" opened, unless there is a race condition - } - } - if ( !ascii ) - { - mode |= std::ios::binary; - } - - outputStream.open( filename.c_str(), mode ); - - if ( !outputStream.is_open() || outputStream.fail() ) - { - itkExceptionMacro( << "Could not open file: " - << filename << " for writing." - << std::endl - << "Reason: " - << itksys::SystemTools::GetLastSystemError() ); - } -} - - -bool -WasmMeshIO -::ReadBufferAsBinary(std::istream & is, void *buffer, SizeValueType num) -{ - const auto numberOfBytesToBeRead = Math::CastWithRangeCheck< std::streamsize >(num); - - is.read(static_cast< char * >( buffer ), numberOfBytesToBeRead); - - const std::streamsize numberOfBytesRead = is.gcount(); - - if ( ( numberOfBytesRead != numberOfBytesToBeRead ) || is.fail() ) - { - return false; // read failed - } - - return true; -} - - -size_t -WasmMeshIO -::ITKComponentSize(const CommonEnums::IOComponent itkComponentType) -{ - switch ( itkComponentType ) - { - case CommonEnums::IOComponent::CHAR: - return sizeof( uint8_t ); - - case CommonEnums::IOComponent::UCHAR: - return sizeof( uint8_t ); - - case CommonEnums::IOComponent::SHORT: - return sizeof( int16_t ); - - case CommonEnums::IOComponent::USHORT: - return sizeof( uint16_t ); - - case CommonEnums::IOComponent::INT: - return sizeof( int32_t ); - - case CommonEnums::IOComponent::UINT: - return sizeof( uint32_t ); - - case CommonEnums::IOComponent::LONG: - return sizeof( int64_t ); - - case CommonEnums::IOComponent::ULONG: - return sizeof( uint64_t ); - - case CommonEnums::IOComponent::LONGLONG: - return sizeof( int64_t ); - - case CommonEnums::IOComponent::ULONGLONG: - return sizeof( uint64_t ); - - case CommonEnums::IOComponent::FLOAT: - return sizeof( float ); - - case CommonEnums::IOComponent::DOUBLE: - return sizeof( double ); - - default: - return sizeof( int8_t ); - } -} - -bool -WasmMeshIO -::FileNameIsCBOR() -{ - const std::string path(this->GetFileName()); - std::string::size_type cborPos = path.rfind(".cbor"); - if ( cborPos != std::string::npos ) - { - return true; - } - return false; -} - - -void -WasmMeshIO -::ReadCBORBuffer(const char * dataName, void * buffer, SizeValueType numberOfBytesToBeRead) -{ - cbor_item_t * index = this->m_CBORRoot; - if (index == nullptr) { - itkExceptionMacro("Call ReadMeshInformation before reading the data buffer"); - } - const size_t indexCount = cbor_map_size(index); - const struct cbor_pair * indexHandle = cbor_map_handle(index); - for (size_t ii = 0; ii < indexCount; ++ii) - { - const std::string_view key(reinterpret_cast(cbor_string_handle(indexHandle[ii].key)), cbor_string_length(indexHandle[ii].key)); - if (key == dataName) - { - const cbor_item_t * dataItem = cbor_tag_item(indexHandle[ii].value); - const char * dataHandle = reinterpret_cast< char * >( cbor_bytestring_handle(dataItem) ); - std::memcpy(buffer, dataHandle, numberOfBytesToBeRead); - } - } -} - - -void -WasmMeshIO -::WriteCBORBuffer(const char * dataName, void * buffer, SizeValueType numberOfBytesToWrite, IOComponentEnum ioComponent) -{ - cbor_item_t * index = this->m_CBORRoot; - if (index == nullptr) { - itkExceptionMacro("Call WriteMeshInformation before writing the data buffer"); - } - cbor_item_t * dataItem = cbor_build_bytestring(reinterpret_cast< const unsigned char *>(buffer), numberOfBytesToWrite); - uint64_t tag = 0; - // Todo: support endianness - // https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml - switch (ioComponent) { - case IOComponentEnum::CHAR: - tag = 64; - break; - case IOComponentEnum::UCHAR: - tag = 64; - break; - case IOComponentEnum::SHORT: - tag = 73; - break; - case IOComponentEnum::USHORT: - tag = 69; - break; - case IOComponentEnum::INT: - tag = 74; - break; - case IOComponentEnum::UINT: - tag = 70; - break; - case IOComponentEnum::LONG: - tag = 75; - break; - case IOComponentEnum::ULONG: - tag = 71; - break; - case IOComponentEnum::LONGLONG: - tag = 75; - break; - case IOComponentEnum::ULONGLONG: - tag = 71; - break; - case IOComponentEnum::FLOAT: - tag = 85; - break; - case IOComponentEnum::DOUBLE: - tag = 86; - break; - default: - itkExceptionMacro("Unexpected component type"); - } - cbor_item_t * dataTag = cbor_new_tag(tag); - cbor_tag_set_item(dataTag, cbor_move(dataItem)); - cbor_map_add(index, - cbor_pair{ - cbor_move(cbor_build_string(dataName)), - cbor_move(dataTag)}); -} - - bool WasmMeshIO ::CanReadFile(const char *filename) @@ -602,12 +347,12 @@ ::GetJSON() cellsDataFile.SetString( cellsDataFileString.c_str(), allocator ); document.AddMember( "cells", cellsDataFile, allocator ); - std::string pointDataDataFileString( "data:application/vnd.itk.path,data/pointData.raw" ); + std::string pointDataDataFileString( "data:application/vnd.itk.path,data/point-data.raw" ); rapidjson::Value pointDataDataFile; pointDataDataFile.SetString( pointDataDataFileString.c_str(), allocator ); document.AddMember( "pointData", pointDataDataFile, allocator ); - std::string cellDataDataFileString( "data:application/vnd.itk.path,data/cellData.raw" ); + std::string cellDataDataFileString( "data:application/vnd.itk.path,data/cell-data.raw" ); rapidjson::Value cellDataDataFile; cellDataDataFile.SetString( cellDataDataFileString.c_str(), allocator ); document.AddMember( "cellData", cellDataDataFile, allocator ); @@ -775,7 +520,7 @@ ::ReadMeshInformation() { this->SetByteOrderToLittleEndian(); - if ( this->FileNameIsCBOR() ) + if ( fileNameIsCBOR(this->GetFileName()) ) { this->ReadCBOR(); return; @@ -787,7 +532,7 @@ ::ReadMeshInformation() const auto dataPath = path + "/data"; std::ifstream inputStream; - this->OpenFileForReading( inputStream, indexPath.c_str(), true ); + openFileForReading( inputStream, indexPath.c_str(), true ); std::string str((std::istreambuf_iterator(inputStream)), std::istreambuf_iterator()); if (document.Parse(str.c_str()).HasParseError()) @@ -827,18 +572,18 @@ ::ReadPoints( void *buffer ) const SizeValueType numberOfBytesToBeRead = static_cast< SizeValueType >( this->GetNumberOfPoints() * this->GetPointDimension() * ITKComponentSize( this->GetPointComponentType() ) ); - if ( this->FileNameIsCBOR() ) + if ( fileNameIsCBOR(this->GetFileName()) ) { - this->ReadCBORBuffer("points", buffer, numberOfBytesToBeRead); + readCBORBuffer(this->m_CBORRoot, "points", buffer, numberOfBytesToBeRead); return; } std::ifstream dataStream; const std::string path(this->GetFileName()); const std::string dataFile = path + "/data/points.raw"; - this->OpenFileForReading( dataStream, dataFile.c_str() ); + openFileForReading( dataStream, dataFile.c_str() ); - if ( !this->ReadBufferAsBinary( dataStream, buffer, numberOfBytesToBeRead ) ) + if ( !readBufferAsBinary( dataStream, buffer, numberOfBytesToBeRead ) ) { itkExceptionMacro(<< "Read failed: Wanted " << numberOfBytesToBeRead @@ -855,9 +600,9 @@ ::ReadCells( void *buffer ) const SizeValueType numberOfBytesToBeRead = static_cast< SizeValueType >( this->GetCellBufferSize() * ITKComponentSize( this->GetCellComponentType() )); - if ( this->FileNameIsCBOR() ) + if ( fileNameIsCBOR(this->GetFileName()) ) { - this->ReadCBORBuffer("cells", buffer, numberOfBytesToBeRead); + readCBORBuffer(this->m_CBORRoot, "cells", buffer, numberOfBytesToBeRead); return; } @@ -865,9 +610,9 @@ ::ReadCells( void *buffer ) const std::string dataPath = "data/cells.raw"; std::ifstream dataStream; const std::string dataFile = path + "/" + dataPath; - this->OpenFileForReading( dataStream, dataFile.c_str() ); + openFileForReading( dataStream, dataFile.c_str() ); - if ( !this->ReadBufferAsBinary( dataStream, buffer, numberOfBytesToBeRead ) ) + if ( !readBufferAsBinary( dataStream, buffer, numberOfBytesToBeRead ) ) { itkExceptionMacro(<< "Read failed: Wanted " << numberOfBytesToBeRead @@ -884,19 +629,19 @@ ::ReadPointData( void *buffer ) const SizeValueType numberOfBytesToBeRead = static_cast< SizeValueType >( this->GetNumberOfPointPixels() * this->GetNumberOfPointPixelComponents() * ITKComponentSize( this->GetPointPixelComponentType() )); - if ( this->FileNameIsCBOR() ) + if ( fileNameIsCBOR(this->GetFileName()) ) { - this->ReadCBORBuffer("pointData", buffer, numberOfBytesToBeRead); + readCBORBuffer(this->m_CBORRoot, "pointData", buffer, numberOfBytesToBeRead); return; } const std::string path(this->GetFileName()); - const std::string dataPath = "data/pointData.raw"; + const std::string dataPath = "data/point-data.raw"; std::ifstream dataStream; const std::string dataFile = path + "/" + dataPath; - this->OpenFileForReading( dataStream, dataFile.c_str() ); + openFileForReading( dataStream, dataFile.c_str() ); - if ( !this->ReadBufferAsBinary( dataStream, buffer, numberOfBytesToBeRead ) ) + if ( !readBufferAsBinary( dataStream, buffer, numberOfBytesToBeRead ) ) { itkExceptionMacro(<< "Read failed: Wanted " << numberOfBytesToBeRead @@ -913,19 +658,19 @@ ::ReadCellData( void *buffer ) const SizeValueType numberOfBytesToBeRead = static_cast< SizeValueType >( this->GetNumberOfCellPixels() * this->GetNumberOfCellPixelComponents() * ITKComponentSize( this->GetCellPixelComponentType() )); - if ( this->FileNameIsCBOR() ) + if ( fileNameIsCBOR(this->GetFileName()) ) { - this->ReadCBORBuffer("cellData", buffer, numberOfBytesToBeRead); + readCBORBuffer(this->m_CBORRoot, "cellData", buffer, numberOfBytesToBeRead); return; } const std::string path(this->GetFileName()); - const std::string dataPath = "data/cellData.raw"; + const std::string dataPath = "data/cell-data.raw"; std::ifstream dataStream; const std::string dataFile = path + "/" + dataPath; - this->OpenFileForReading( dataStream, dataFile.c_str() ); + openFileForReading( dataStream, dataFile.c_str() ); - if ( !this->ReadBufferAsBinary( dataStream, buffer, numberOfBytesToBeRead ) ) + if ( !readBufferAsBinary( dataStream, buffer, numberOfBytesToBeRead ) ) { itkExceptionMacro(<< "Read failed: Wanted " << numberOfBytesToBeRead @@ -967,7 +712,7 @@ void WasmMeshIO ::WriteMeshInformation() { - if ( this->FileNameIsCBOR() ) + if ( fileNameIsCBOR(this->GetFileName()) ) { this->WriteCBOR(); return; @@ -1008,7 +753,7 @@ ::WriteMeshInformation() } std::ofstream outputStream; - this->OpenFileForWriting( outputStream, indexPath.c_str(), true, true ); + openFileForWriting( outputStream, indexPath.c_str(), true, true ); rapidjson::OStreamWrapper ostreamWrapper( outputStream ); rapidjson::PrettyWriter< rapidjson::OStreamWrapper > writer( ostreamWrapper ); document.Accept( writer ); @@ -1022,9 +767,9 @@ ::WritePoints( void *buffer ) { const SizeValueType numberOfBytes = this->GetNumberOfPoints() * this->GetPointDimension() * ITKComponentSize( this->GetPointComponentType() ); - if (this->FileNameIsCBOR()) + if (fileNameIsCBOR(this->GetFileName())) { - this->WriteCBORBuffer( "points", buffer, numberOfBytes, this->GetPointComponentType() ); + writeCBORBuffer(this->m_CBORRoot, "points", buffer, numberOfBytes, this->GetPointComponentType() ); return; } @@ -1032,7 +777,7 @@ ::WritePoints( void *buffer ) const std::string filePath = "data/points.raw"; const std::string fileName = path + "/" + filePath; std::ofstream outputStream; - this->OpenFileForWriting( outputStream, fileName, true, false ); + openFileForWriting( outputStream, fileName, true, false ); outputStream.write(static_cast< const char * >( buffer ), numberOfBytes); if (outputStream.tellp() != numberOfBytes ) { @@ -1050,9 +795,9 @@ ::WriteCells( void *buffer ) { const SizeValueType numberOfBytes = this->GetCellBufferSize() * ITKComponentSize( this->GetCellComponentType() ); - if (this->FileNameIsCBOR()) + if (fileNameIsCBOR(this->GetFileName())) { - this->WriteCBORBuffer( "cells", buffer, numberOfBytes, this->GetCellComponentType() ); + writeCBORBuffer(this->m_CBORRoot, "cells", buffer, numberOfBytes, this->GetCellComponentType() ); return; } @@ -1060,7 +805,7 @@ ::WriteCells( void *buffer ) const std::string filePath = "data/cells.raw"; const std::string fileName = path + "/" + filePath; std::ofstream outputStream; - this->OpenFileForWriting( outputStream, fileName, true, false ); + openFileForWriting( outputStream, fileName, true, false ); outputStream.write(static_cast< const char * >( buffer ), numberOfBytes); \ if (outputStream.tellp() != numberOfBytes ) { @@ -1078,17 +823,17 @@ ::WritePointData( void *buffer ) { const SizeValueType numberOfBytes = this->GetNumberOfPointPixels() * this->GetNumberOfPointPixelComponents() * ITKComponentSize( this->GetPointPixelComponentType() ); - if (this->FileNameIsCBOR()) + if (fileNameIsCBOR(this->GetFileName())) { - this->WriteCBORBuffer( "pointData", buffer, numberOfBytes, this->GetPointPixelComponentType() ); + writeCBORBuffer(this->m_CBORRoot, "pointData", buffer, numberOfBytes, this->GetPointPixelComponentType() ); return; } const std::string path(this->GetFileName()); - const std::string filePath = "data/pointData.raw"; + const std::string filePath = "data/point-data.raw"; const std::string fileName = path + "/" + filePath; std::ofstream outputStream; - this->OpenFileForWriting( outputStream, fileName, true, false ); + openFileForWriting( outputStream, fileName, true, false ); outputStream.write(static_cast< const char * >( buffer ), numberOfBytes); \ if (outputStream.tellp() != numberOfBytes ) { @@ -1106,17 +851,17 @@ ::WriteCellData( void *buffer ) { const SizeValueType numberOfBytes = this->GetNumberOfPointPixels() * this->GetNumberOfPointPixelComponents() * ITKComponentSize( this->GetCellPixelComponentType() ); - if (this->FileNameIsCBOR()) + if (fileNameIsCBOR(this->GetFileName())) { - this->WriteCBORBuffer( "cellData", buffer, numberOfBytes, this->GetCellPixelComponentType() ); + writeCBORBuffer(this->m_CBORRoot, "cellData", buffer, numberOfBytes, this->GetCellPixelComponentType() ); return; } const std::string path(this->GetFileName()); - const std::string filePath = "data/cellData.raw"; + const std::string filePath = "data/cell-data.raw"; const std::string fileName = path + "/" + filePath; std::ofstream outputStream; - this->OpenFileForWriting( outputStream, fileName, true, false ); + openFileForWriting( outputStream, fileName, true, false ); outputStream.write(static_cast< const char * >( buffer ), numberOfBytes); \ if (outputStream.tellp() != numberOfBytes ) { @@ -1131,7 +876,7 @@ void WasmMeshIO ::Write() { - if (this->FileNameIsCBOR()) + if (fileNameIsCBOR(this->GetFileName())) { unsigned char* cborBuffer; size_t cborBufferSize; diff --git a/src/itkWasmMeshIOBase.cxx b/src/itkWasmMeshIOBase.cxx index fc06561aa..96d82ada5 100644 --- a/src/itkWasmMeshIOBase.cxx +++ b/src/itkWasmMeshIOBase.cxx @@ -18,6 +18,7 @@ #include "itkWasmMeshIOBase.h" #include "itkWasmMeshIO.h" +#include "itkWasmIOCommon.h" #include #include "rapidjson/prettywriter.h" @@ -65,7 +66,7 @@ WasmMeshIOBase::SetMeshIO(MeshIOBase * meshIO, bool readMesh) rapidjson::Document::AllocatorType& allocator = document.GetAllocator(); size_t pointsAddress = 0; - SizeValueType numberOfBytes = meshIO->GetNumberOfPoints() * meshIO->GetPointDimension() * WasmMeshIO::ITKComponentSize( meshIO->GetPointComponentType() ); + SizeValueType numberOfBytes = meshIO->GetNumberOfPoints() * meshIO->GetPointDimension() * ITKComponentSize( meshIO->GetPointComponentType() ); if (numberOfBytes) { this->m_PointsContainer->resize( numberOfBytes ); @@ -81,7 +82,7 @@ WasmMeshIOBase::SetMeshIO(MeshIOBase * meshIO, bool readMesh) document.RemoveMember( "points" ); document.AddMember( "points", pointsString.Move(), allocator ); - numberOfBytes = static_cast< SizeValueType >( meshIO->GetCellBufferSize() * WasmMeshIO::ITKComponentSize( meshIO->GetCellComponentType() )); + numberOfBytes = static_cast< SizeValueType >( meshIO->GetCellBufferSize() * ITKComponentSize( meshIO->GetCellComponentType() )); size_t cellsAddress = 0; if (numberOfBytes) @@ -100,7 +101,7 @@ WasmMeshIOBase::SetMeshIO(MeshIOBase * meshIO, bool readMesh) document.AddMember( "cells", cellsString.Move(), allocator ); numberOfBytes = - static_cast< SizeValueType >( meshIO->GetNumberOfPointPixels() * meshIO->GetNumberOfPointPixelComponents() * WasmMeshIO::ITKComponentSize( meshIO->GetPointPixelComponentType() )); + static_cast< SizeValueType >( meshIO->GetNumberOfPointPixels() * meshIO->GetNumberOfPointPixelComponents() * ITKComponentSize( meshIO->GetPointPixelComponentType() )); size_t pointDataAddress = 0; if (numberOfBytes) @@ -119,7 +120,7 @@ WasmMeshIOBase::SetMeshIO(MeshIOBase * meshIO, bool readMesh) document.AddMember( "pointData", pointDataString.Move(), allocator ); numberOfBytes = - static_cast< SizeValueType >( meshIO->GetNumberOfCellPixels() * meshIO->GetNumberOfCellPixelComponents() * WasmMeshIO::ITKComponentSize( meshIO->GetCellPixelComponentType() )); + static_cast< SizeValueType >( meshIO->GetNumberOfCellPixels() * meshIO->GetNumberOfCellPixelComponents() * ITKComponentSize( meshIO->GetCellPixelComponentType() )); size_t cellDataAddress = 0; if (numberOfBytes) diff --git a/src/itkWasmMeshIOFactory.cxx b/src/itkWasmMeshIOFactory.cxx index dff938360..2d71935e4 100644 --- a/src/itkWasmMeshIOFactory.cxx +++ b/src/itkWasmMeshIOFactory.cxx @@ -50,7 +50,7 @@ const char * WasmMeshIOFactory ::GetDescription() const { - return "Wasm MeshIO Factory, allows the loading of Wasm images into Insight"; + return "Wasm MeshIO Factory, allows the loading of Wasm meshes into Insight"; } diff --git a/src/itkWasmTransformIO.cxx b/src/itkWasmTransformIO.cxx new file mode 100644 index 000000000..9e59f2634 --- /dev/null +++ b/src/itkWasmTransformIO.cxx @@ -0,0 +1,1355 @@ +/*========================================================================= + * + * 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. + * + *=========================================================================*/ + +#define ITK_TEMPLATE_EXPLICIT_WasmTransformIO +#include "itkWasmTransformIO.h" + +#include "itkCompositeTransform.h" +#include "itkCompositeTransformIOHelper.h" +#include "itkVersion.h" +#include "itkMakeUniqueForOverwrite.h" +#include + +#include "itkWasmComponentTypeFromIOComponentEnum.h" +#include "itkIOComponentEnumFromWasmComponentType.h" +#include "itkWasmPixelTypeFromIOPixelEnum.h" +#include "itkIOPixelEnumFromWasmPixelType.h" +#include "itkWasmIOCommon.h" + +#include "itkMetaDataObject.h" +#include "itkIOCommon.h" +#include "itksys/SystemTools.hxx" + +#include "itksys/SystemTools.hxx" + +#include "cbor.h" + +namespace itk +{ + +template +WasmTransformIOTemplate::WasmTransformIOTemplate() = default; + +template +WasmTransformIOTemplate::~WasmTransformIOTemplate() = default; + +template +void +WasmTransformIOTemplate::PrintSelf(std::ostream & os, Indent indent) const +{ + Superclass::PrintSelf(os, indent); +} + +template +bool +WasmTransformIOTemplate::CanReadFile(const char * filename) +{ + // Check the extension first to avoid opening files that do not + // look like JSON. The file must have an appropriate extension to be + // recognized. + std::string fname = filename; + // + bool extensionFound = false; + std::string::size_type extensionPos = fname.rfind(".iwt"); + if (extensionPos != std::string::npos) + { + extensionFound = true; + } + + if (!extensionFound) + { + itkDebugMacro(<< "The filename extension is not recognized"); + return false; + } + + return true; +} + +template +void +WasmTransformIOTemplate::ReadCBOR() +{ + FILE * file = fopen(this->GetFileName(), "rb"); + if (file == NULL) + { + itkExceptionMacro("Could not read file: " << this->GetFileName()); + } + fseek(file, 0, SEEK_END); + size_t length = (size_t)ftell(file); + fseek(file, 0, SEEK_SET); + unsigned char * cborBuffer = static_cast(malloc(length)); + if (!fread(cborBuffer, length, 1, file)) + { + itkExceptionMacro("Could not successfully read " << this->GetFileName()); + } + fclose(file); + + if (this->m_CBORRoot != nullptr) + { + cbor_decref(&(this->m_CBORRoot)); + } + struct cbor_load_result result; + this->m_CBORRoot = cbor_load(cborBuffer, length, &result); + free(cborBuffer); + if (result.error.code != CBOR_ERR_NONE) + { + std::string errorDescription; + switch (result.error.code) + { + case CBOR_ERR_MALFORMATED: + { + errorDescription = "Malformed data\n"; + break; + } + case CBOR_ERR_MEMERROR: + { + errorDescription = "Memory error -- perhaps the input is too large?\n"; + break; + } + case CBOR_ERR_NODATA: + { + errorDescription = "The input is empty\n"; + break; + } + case CBOR_ERR_NOTENOUGHDATA: + { + errorDescription = "Data seem to be missing -- is the input complete?\n"; + break; + } + case CBOR_ERR_SYNTAXERROR: + { + errorDescription = "Syntactically malformed data -- see https://tools.ietf.org/html/rfc7049\n"; + break; + } + case CBOR_ERR_NONE: + { + break; + } + } + itkExceptionMacro("" << errorDescription << "There was an error while reading the input near byte " + << result.error.position << " (read " << result.read << " bytes in total): "); + } + + TransformListJSON transformListJSON; + cbor_item_t * index = this->m_CBORRoot; + const size_t transformCount = cbor_array_size(index); + for (size_t ii = 0; ii < transformCount; ++ii) + { + const cbor_item_t * transformItem = cbor_array_get(index, ii); + const size_t transformPropertyCount = cbor_map_size(transformItem); + const struct cbor_pair * transformHandle = cbor_map_handle(transformItem); + TransformJSON transformJSON; + for (size_t jj = 0; jj < transformPropertyCount; ++jj) + { + const std::string_view key(reinterpret_cast(cbor_string_handle(transformHandle[jj].key)), + cbor_string_length(transformHandle[jj].key)); + if (key == "transformType") + { + const cbor_item_t * transformTypeItem = transformHandle[jj].value; + const size_t transformTypePropertyCount = cbor_map_size(transformTypeItem); + const struct cbor_pair * transformTypeHandle = cbor_map_handle(transformTypeItem); + for (size_t kk = 0; kk < transformTypePropertyCount; ++kk) + { + const std::string_view transformTypeKey( + reinterpret_cast(cbor_string_handle(transformTypeHandle[kk].key)), + cbor_string_length(transformTypeHandle[kk].key)); + if (transformTypeKey == "transformParameterization") + { + const std::string transformParameterization( + reinterpret_cast(cbor_string_handle(transformTypeHandle[kk].value)), + cbor_string_length(transformTypeHandle[kk].value)); + if (transformParameterization == "Identity") + { + transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Identity; + } + else if (transformParameterization == "Translation") + { + transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Translation; + } + else if (transformParameterization == "Euler2D") + { + transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Euler2D; + } + else if (transformParameterization == "Euler3D") + { + transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Euler3D; + } + else if (transformParameterization == "Rigid2D") + { + transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Rigid2D; + } + else if (transformParameterization == "Rigid3DPerspective") + { + transformJSON.transformType.transformParameterization = + JSONTransformParameterizationEnum::Rigid3DPerspective; + } + else if (transformParameterization == "VersorRigid3D") + { + transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::VersorRigid3D; + } + else if (transformParameterization == "Versor") + { + transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Versor; + } + else if (transformParameterization == "ScaleLogarithmic") + { + transformJSON.transformType.transformParameterization = + JSONTransformParameterizationEnum::ScaleLogarithmic; + } + else if (transformParameterization == "ScaleSkewVersor3D") + { + transformJSON.transformType.transformParameterization = + JSONTransformParameterizationEnum::ScaleSkewVersor3D; + } + else if (transformParameterization == "Scale") + { + transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Scale; + } + else if (transformParameterization == "Similarity2D") + { + transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Similarity2D; + } + else if (transformParameterization == "Similarity3D") + { + transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Similarity3D; + } + else if (transformParameterization == "QuaternionRigid") + { + transformJSON.transformType.transformParameterization = + JSONTransformParameterizationEnum::QuaternionRigid; + } + else if (transformParameterization == "Affine") + { + transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Affine; + } + else if (transformParameterization == "ScalableAffine") + { + transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::ScalableAffine; + } + else if (transformParameterization == "AzimuthElevationToCartesian") + { + transformJSON.transformType.transformParameterization = + JSONTransformParameterizationEnum::AzimuthElevationToCartesian; + } + else if (transformParameterization == "BSpline") + { + transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::BSpline; + } + else if (transformParameterization == "BSplineSmoothingOnUpdateDisplacementField") + { + transformJSON.transformType.transformParameterization = + JSONTransformParameterizationEnum::BSplineSmoothingOnUpdateDisplacementField; + } + else if (transformParameterization == "ConstantVelocityField") + { + transformJSON.transformType.transformParameterization = + JSONTransformParameterizationEnum::ConstantVelocityField; + } + else if (transformParameterization == "DisplacementField") + { + transformJSON.transformType.transformParameterization = + JSONTransformParameterizationEnum::DisplacementField; + } + else if (transformParameterization == "GaussianExponentialDiffeomorphic") + { + transformJSON.transformType.transformParameterization = + JSONTransformParameterizationEnum::GaussianExponentialDiffeomorphic; + } + else if (transformParameterization == "GaussianSmoothingOnUpdateDisplacementField") + { + transformJSON.transformType.transformParameterization = + JSONTransformParameterizationEnum::GaussianSmoothingOnUpdateDisplacementField; + } + else if (transformParameterization == "GaussianSmoothingOnUpdateTimeVaryingVelocityField") + { + transformJSON.transformType.transformParameterization = + JSONTransformParameterizationEnum::GaussianSmoothingOnUpdateTimeVaryingVelocityField; + } + else if (transformParameterization == "TimeVaryingVelocityField") + { + transformJSON.transformType.transformParameterization = + JSONTransformParameterizationEnum::TimeVaryingVelocityField; + } + else if (transformParameterization == "VelocityField") + { + transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::VelocityField; + } + else + { + itkExceptionMacro("Unexpected transformParameterization: " << transformParameterization); + } + } + else if (transformTypeKey == "parametersValueType") + { + const std::string parametersValueType( + reinterpret_cast(cbor_string_handle(transformTypeHandle[kk].value)), + cbor_string_length(transformTypeHandle[kk].value)); + if (parametersValueType == "float32") + { + transformJSON.transformType.parametersValueType = JSONFloatTypesEnum::float32; + } + else if (parametersValueType == "float64") + { + transformJSON.transformType.parametersValueType = JSONFloatTypesEnum::float64; + } + else + { + itkExceptionMacro("Unexpected parametersValueType: " << parametersValueType); + } + } + else if (transformTypeKey == "inputDimension") + { + const auto inputDimension = cbor_get_uint32(transformTypeHandle[kk].value); + transformJSON.transformType.inputDimension = inputDimension; + } + else if (transformTypeKey == "outputDimension") + { + const auto outputDimension = cbor_get_uint32(transformTypeHandle[kk].value); + transformJSON.transformType.outputDimension = outputDimension; + } + else + { + itkExceptionMacro("Unexpected transformType cbor map key: " << transformTypeKey); + } + } + } + else if (key == "numberOfFixedParameters") + { + const auto numberOfFixedParameters = cbor_get_uint64(transformHandle[jj].value); + transformJSON.numberOfFixedParameters = numberOfFixedParameters; + } + else if (key == "numberOfParameters") + { + const auto numberOfParameters = cbor_get_uint64(transformHandle[jj].value); + transformJSON.numberOfParameters = numberOfParameters; + } + else if (key == "name") + { + const std::string name(reinterpret_cast(cbor_string_handle(transformHandle[jj].value)), + cbor_string_length(transformHandle[jj].value)); + transformJSON.name = name; + } + else if (key == "inputSpaceName") + { + const std::string inputSpaceName(reinterpret_cast(cbor_string_handle(transformHandle[jj].value)), + cbor_string_length(transformHandle[jj].value)); + transformJSON.inputSpaceName = inputSpaceName; + } + else if (key == "outputSpaceName") + { + const std::string outputSpaceName(reinterpret_cast(cbor_string_handle(transformHandle[jj].value)), + cbor_string_length(transformHandle[jj].value)); + transformJSON.outputSpaceName = outputSpaceName; + } + } + transformListJSON.push_back(transformJSON); + } + this->SetJSON(transformListJSON); + + auto readTransformList = this->GetReadTransformList(); + unsigned int count = 0; + for (auto [transformIt, jsonIt] = std::tuple{ readTransformList.begin(), transformListJSON.begin() }; + transformIt != readTransformList.end(); + ++transformIt, ++jsonIt) + { + const auto transformJSON = *jsonIt; + FixedParametersType fixedParams(transformJSON.numberOfFixedParameters); + const SizeValueType numberOfFixedBytes = transformJSON.numberOfFixedParameters * sizeof(FixedParametersValueType); + ParametersType params(transformJSON.numberOfParameters); + const SizeValueType numberOfBytes = transformJSON.numberOfParameters * sizeof(ParametersValueType); + const auto valueBytes = sizeof(ParametersValueType); + + const cbor_item_t * transformItem = cbor_array_get(index, count); + const size_t transformPropertyCount = cbor_map_size(transformItem); + const struct cbor_pair * transformHandle = cbor_map_handle(transformItem); + for (size_t jj = 0; jj < transformPropertyCount; ++jj) + { + const std::string_view key(reinterpret_cast(cbor_string_handle(transformHandle[jj].key)), + cbor_string_length(transformHandle[jj].key)); + if (key == "fixedParameters") + { + readCBORBuffer(transformItem, "fixedParameters", fixedParams.data_block(), numberOfFixedBytes); + (*transformIt)->SetFixedParameters(fixedParams); + } + else if (key == "parameters") + { + if (transformJSON.transformType.parametersValueType == JSONFloatTypesEnum::float32) + { + if (valueBytes != sizeof(float)) + { + std::vector floatParams(transformJSON.numberOfParameters); + readCBORBuffer(transformItem, "parameters", floatParams.data(), numberOfBytes); + for (SizeValueType i = 0; i < transformJSON.numberOfParameters; ++i) + { + params[i] = static_cast(floatParams[i]); + } + } + else + { + readCBORBuffer(transformItem, "parameters", params.data_block(), numberOfBytes); + } + } + else if (transformJSON.transformType.parametersValueType == JSONFloatTypesEnum::float64) + { + if (valueBytes != sizeof(double)) + { + std::vector doubleParams(transformJSON.numberOfParameters); + readCBORBuffer(transformItem, "parameters", doubleParams.data(), numberOfBytes); + for (SizeValueType i = 0; i < transformJSON.numberOfParameters; ++i) + { + params[i] = static_cast(doubleParams[i]); + } + } + else + { + readCBORBuffer(transformItem, "parameters", params.data_block(), numberOfBytes); + } + } + (*transformIt)->SetParameters(params); + } + } + ++count; + } + + + // const struct cbor_pair *indexHandle = cbor_map_handle(index); + // for (size_t ii = 0; ii < indexCount; ++ii) + // { + // const std::string_view key(reinterpret_cast(cbor_string_handle(indexHandle[ii].key)), + // cbor_string_length(indexHandle[ii].key)); if (key == "transformType") + // { + // const cbor_item_t *transformTypeItem = indexHandle[ii].value; + // const size_t transformPropertyCount = cbor_map_size(transformTypeItem); + // const struct cbor_pair *transformTypeHandle = cbor_map_handle(transformTypeItem); + // for (size_t jj = 0; jj < transformPropertyCount; ++jj) + // { + // const std::string_view transformTypeKey(reinterpret_cast(cbor_string_handle(transformTypeHandle[jj].key)), cbor_string_length(transformTypeHandle[jj].key)); if + // (transformTypeKey == "transformType") + // { + // const std::string transformType(reinterpret_cast(cbor_string_handle(transformTypeHandle[jj].value)), cbor_string_length(transformTypeHandle[jj].value)); + // const CommonEnums::IOComponent pointIOComponentType = + // IOComponentEnumFromWasmComponentType(pointComponentType); this->SetPointDimension(dimension); + // } + // else if (transformTypeKey == "inputDimension") + // { + // const auto inputDimension = cbor_get_uint32(transformTypeHandle[jj].value); + // this->SetPointDimension(inputDimension); + // } + // else if (transformTypeKey == "pointComponentType") + // { + // const std::string pointComponentType(reinterpret_cast(cbor_string_handle(transformTypeHandle[jj].value)), cbor_string_length(transformTypeHandle[jj].value)); + // const CommonEnums::IOComponent pointIOComponentType = + // IOComponentEnumFromWasmComponentType(pointComponentType); + // this->SetPointComponentType(pointIOComponentType); + // } + // else if (transformTypeKey == "pointPixelType") + // { + // const std::string pointPixelType(reinterpret_cast(cbor_string_handle(transformTypeHandle[jj].value)), cbor_string_length(transformTypeHandle[jj].value)); + // const CommonEnums::IOPixel pointIOPixelType = IOPixelEnumFromWasmPixelType(pointPixelType); + // this->SetPointPixelType(pointIOPixelType); + // } + // else if (transformTypeKey == "pointPixelComponentType") + // { + // const std::string pointPixelComponentType(reinterpret_cast(cbor_string_handle(transformTypeHandle[jj].value)), cbor_string_length(transformTypeHandle[jj].value)); + // const CommonEnums::IOComponent pointPixelIOComponentType = + // IOComponentEnumFromWasmComponentType(pointPixelComponentType); + // this->SetPointPixelComponentType(pointPixelIOComponentType); + // } + // else if (transformTypeKey == "pointPixelComponents") + // { + // const auto components = cbor_get_uint32(transformTypeHandle[jj].value); + // this->SetNumberOfPointPixelComponents(components); + // } + // else if (transformTypeKey == "cellComponentType") + // { + // const std::string cellComponentType(reinterpret_cast(cbor_string_handle(transformTypeHandle[jj].value)), cbor_string_length(transformTypeHandle[jj].value)); + // const CommonEnums::IOComponent cellIOComponentType = + // IOComponentEnumFromWasmComponentType(cellComponentType); this->SetCellComponentType(cellIOComponentType); + // } + // else if (transformTypeKey == "cellPixelType") + // { + // const std::string cellPixelType(reinterpret_cast(cbor_string_handle(transformTypeHandle[jj].value)), cbor_string_length(transformTypeHandle[jj].value)); + // const CommonEnums::IOPixel cellIOPixelType = IOPixelEnumFromWasmPixelType(cellPixelType); + // this->SetCellPixelType(cellIOPixelType); + // } + // else if (transformTypeKey == "cellPixelComponentType") + // { + // const std::string cellPixelComponentType(reinterpret_cast(cbor_string_handle(transformTypeHandle[jj].value)), cbor_string_length(transformTypeHandle[jj].value)); + // const CommonEnums::IOComponent cellPixelIOComponentType = + // IOComponentEnumFromWasmComponentType(cellPixelComponentType); + // this->SetCellPixelComponentType(cellPixelIOComponentType); + // } + // else if (transformTypeKey == "cellPixelComponents") + // { + // const auto components = cbor_get_uint32(transformTypeHandle[jj].value); + // this->SetNumberOfCellPixelComponents(components); + // } + // else + // { + // itkExceptionMacro("Unexpected transformType cbor map key: " << transformTypeKey); + // } + // } + // } + // else if (key == "numberOfPoints") + // { + // const auto components = cbor_get_uint64(indexHandle[ii].value); + // this->SetNumberOfPoints(components); + // if (components) + // { + // this->m_UpdatePoints = true; + // } + // } + // else if (key == "numberOfPointPixels") + // { + // const auto components = cbor_get_uint64(indexHandle[ii].value); + // this->SetNumberOfPointPixels(components); + // if (components) + // { + // this->m_UpdatePointData = true; + // } + // } + // else if (key == "numberOfCells") + // { + // const auto components = cbor_get_uint64(indexHandle[ii].value); + // this->SetNumberOfCells(components); + // if (components) + // { + // this->m_UpdateCells = true; + // } + // } + // else if (key == "numberOfCellPixels") + // { + // const auto components = cbor_get_uint64(indexHandle[ii].value); + // this->SetNumberOfCellPixels(components); + // if (components) + // { + // this->m_UpdateCellData = true; + // } + // } + // else if (key == "cellBufferSize") + // { + // const auto components = cbor_get_uint64(indexHandle[ii].value); + // this->SetCellBufferSize(components); + // } + // } +} + +template +auto +WasmTransformIOTemplate::GetJSON() -> TransformListJSON +{ + TransformListJSON transformListJSON; + + ConstTransformListType & transformList = this->GetWriteTransformList(); + std::string compositeTransformType = transformList.front()->GetTransformTypeAsString(); + CompositeTransformIOHelperTemplate 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 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 == "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); + } + + return transformListJSON; +} + +template +std::string +WasmTransformIOTemplate::TransformParameterizationString(const TransformTypeJSON & json) +{ + std::string transformParameterization; + switch (json.transformParameterization) + { + case JSONTransformParameterizationEnum::Identity: + { + transformParameterization = "Identity"; + 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 +void +WasmTransformIOTemplate::SetJSON(const TransformListJSON & json) +{ + // iterate over the JSON and set the transform list + TransformListType transformList; + for (const auto & transformJSON : json) + { + std::string transformPrecision; + switch (transformJSON.transformType.parametersValueType) + { + case JSONFloatTypesEnum::float32: + { + transformPrecision = "float"; + break; + } + case JSONFloatTypesEnum::float64: + { + transformPrecision = "double"; + break; + } + default: + { + itkExceptionMacro("Unknown parameters value type"); + } + } + const std::string transformParameterization = TransformParameterizationString(transformJSON.transformType); + // itk::Transform::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); + // Transform name should be modified to have the output precision type. + Superclass::CorrectTransformPrecisionType(transformType); + + TransformPointer transform; + this->CreateTransform(transform, transformType); + transform->SetObjectName(transformJSON.name); + this->GetReadTransformList().push_back(transform); + } +} + +template +void +WasmTransformIOTemplate::WriteCBOR() +{ + auto transformListJSON = this->GetJSON(); + + if (this->m_CBORRoot != nullptr) + { + cbor_decref(&(this->m_CBORRoot)); + } + // create a container for a javascript array + this->m_CBORRoot = cbor_new_definite_array(transformListJSON.size()); + + unsigned int count = 0; + cbor_item_t * index = this->m_CBORRoot; + // write the transformListJSON into the cbor array + ConstTransformListType & writeTransformList = this->GetWriteTransformList(); + std::string compositeTransformType = writeTransformList.front()->GetTransformTypeAsString(); + CompositeTransformIOHelperTemplate 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) + { + writeTransformList = helper.GetTransformList(writeTransformList.front().GetPointer()); + } + + for (auto [transformIt, jsonIt] = std::tuple{ writeTransformList.begin(), transformListJSON.begin() }; + transformIt != writeTransformList.end(); + ++transformIt, ++jsonIt) + { + const auto & transformJSON = *jsonIt; + 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); + cbor_map_add(transformTypeItem, + cbor_pair{ cbor_move(cbor_build_string("transformParameterization")), + cbor_move(cbor_build_string(transformParameterization.c_str())) }); + switch (transformJSON.transformType.parametersValueType) + { + case JSONFloatTypesEnum::float32: + { + cbor_map_add( + transformTypeItem, + cbor_pair{ cbor_move(cbor_build_string("parametersValueType")), cbor_move(cbor_build_string("float32")) }); + break; + } + case JSONFloatTypesEnum::float64: + { + cbor_map_add( + transformTypeItem, + cbor_pair{ cbor_move(cbor_build_string("parametersValueType")), cbor_move(cbor_build_string("float64")) }); + break; + } + default: + { + itkExceptionMacro("Unknown parameters value type"); + } + } + cbor_map_add(transformTypeItem, + cbor_pair{ cbor_move(cbor_build_string("inputDimension")), + cbor_move(cbor_build_uint32(transformJSON.transformType.inputDimension)) }); + cbor_map_add(transformTypeItem, + cbor_pair{ cbor_move(cbor_build_string("outputDimension")), + cbor_move(cbor_build_uint32(transformJSON.transformType.outputDimension)) }); + cbor_map_add(transformItem, + cbor_pair{ cbor_move(cbor_build_string("transformType")), cbor_move(transformTypeItem) }); + + cbor_map_add(transformItem, + cbor_pair{ cbor_move(cbor_build_string("numberOfFixedParameters")), + cbor_move(cbor_build_uint64(transformJSON.numberOfFixedParameters)) }); + cbor_map_add(transformItem, + cbor_pair{ cbor_move(cbor_build_string("numberOfParameters")), + cbor_move(cbor_build_uint64(transformJSON.numberOfParameters)) }); + cbor_map_add( + transformItem, + cbor_pair{ cbor_move(cbor_build_string("name")), cbor_move(cbor_build_string(transformJSON.name.c_str())) }); + cbor_map_add(transformItem, + cbor_pair{ cbor_move(cbor_build_string("inputSpaceName")), + cbor_move(cbor_build_string(transformJSON.inputSpaceName.c_str())) }); + cbor_map_add(transformItem, + cbor_pair{ cbor_move(cbor_build_string("outputSpaceName")), + cbor_move(cbor_build_string(transformJSON.outputSpaceName.c_str())) }); + + const auto fixedNumberOfBytes = transformJSON.numberOfFixedParameters * sizeof(FixedParametersValueType); + const auto fixedParams = (*transformIt)->GetFixedParameters(); + writeCBORBuffer(transformItem, + "fixedParameters", + reinterpret_cast(fixedParams.data_block()), + fixedNumberOfBytes, + IOComponentEnum::DOUBLE); + const auto numberOfBytes = transformJSON.numberOfParameters * sizeof(ParametersValueType); + const auto params = (*transformIt)->GetParameters(); + if (transformJSON.transformType.parametersValueType == JSONFloatTypesEnum::float32) + { + writeCBORBuffer(transformItem, + "parameters", + reinterpret_cast(params.data_block()), + numberOfBytes, + IOComponentEnum::FLOAT); + } + else if (transformJSON.transformType.parametersValueType == JSONFloatTypesEnum::float64) + { + writeCBORBuffer(transformItem, + "parameters", + reinterpret_cast(params.data_block()), + numberOfBytes, + IOComponentEnum::DOUBLE); + } + + cbor_array_push(index, cbor_move(transformItem)); + + ++count; + } + + unsigned char * cborBuffer; + size_t cborBufferSize; + size_t length = cbor_serialize_alloc(this->m_CBORRoot, &cborBuffer, &cborBufferSize); + + FILE * file = fopen(this->GetFileName(), "wb"); + fwrite(cborBuffer, 1, length, file); + free(cborBuffer); + fclose(file); + + cbor_decref(&(this->m_CBORRoot)); +} + +template +auto +WasmTransformIOTemplate::ReadTransformInformation() -> const TransformListJSON +{ + const std::string path = this->GetFileName(); + const auto indexPath = path + "/index.json"; + const auto dataPath = path + "/data"; + + std::ifstream inputStream; + openFileForReading(inputStream, indexPath.c_str(), true); + std::string str((std::istreambuf_iterator(inputStream)), std::istreambuf_iterator()); + auto deserializedAttempt = glz::read_json(str); + if (!deserializedAttempt) + { + itkExceptionMacro("Failed to deserialize TransformListJSON"); + } + auto transformListJSON = deserializedAttempt.value(); + + this->SetJSON(transformListJSON); + + return transformListJSON; +} + +template +void +WasmTransformIOTemplate::ReadFixedParameters(const TransformListJSON & json) +{ + auto readTransformList = this->GetReadTransformList(); + unsigned int count = 0; + for (auto [transformIt, jsonIt] = std::tuple{ readTransformList.begin(), json.begin() }; + transformIt != readTransformList.end(); + ++transformIt, ++jsonIt) + { + const auto transformJSON = *jsonIt; + FixedParametersType fixedParams(transformJSON.numberOfFixedParameters); + const SizeValueType numberOfBytes = transformJSON.numberOfFixedParameters * sizeof(FixedParametersValueType); + + std::string path(this->GetFileName()); + path = path + "/data/" + std::to_string(count); + const std::string filePath = path + "/fixed-parameters.raw"; + std::ifstream dataStream; + openFileForReading(dataStream, filePath.c_str()); + dataStream.read(reinterpret_cast(fixedParams.data_block()), numberOfBytes); + const auto readBytes = dataStream.gcount(); + if (readBytes != numberOfBytes) + { + itkExceptionMacro(<< "Read failed: Wanted " << numberOfBytes << " bytes, but read " << readBytes << " bytes."); + } + (*transformIt)->SetFixedParameters(fixedParams); + ++count; + } +} + +template +void +WasmTransformIOTemplate::ReadParameters(const TransformListJSON & json) +{ + auto readTransformList = this->GetReadTransformList(); + unsigned int count = 0; + for (auto [transformIt, jsonIt] = std::tuple{ readTransformList.begin(), json.begin() }; + transformIt != readTransformList.end(); + ++transformIt, ++jsonIt) + { + const auto transformJSON = *jsonIt; + ParametersType params(transformJSON.numberOfParameters); + const auto valueBytes = sizeof(ParametersValueType); + const SizeValueType numberOfBytes = transformJSON.numberOfParameters * valueBytes; + + std::string path(this->GetFileName()); + path = path + "/data/" + std::to_string(count); + const std::string filePath = path + "/parameters.raw"; + std::ifstream dataStream; + openFileForReading(dataStream, filePath.c_str()); + if (transformJSON.transformType.parametersValueType == JSONFloatTypesEnum::float32) + { + if (valueBytes != sizeof(float)) + { + std::vector floatParams(transformJSON.numberOfParameters); + dataStream.read(reinterpret_cast(floatParams.data()), numberOfBytes); + for (SizeValueType i = 0; i < transformJSON.numberOfParameters; ++i) + { + params[i] = static_cast(floatParams[i]); + } + } + else + { + dataStream.read(reinterpret_cast(params.data_block()), numberOfBytes); + } + } + else if (transformJSON.transformType.parametersValueType == JSONFloatTypesEnum::float64) + { + if (valueBytes != sizeof(double)) + { + std::vector doubleParams(transformJSON.numberOfParameters); + dataStream.read(reinterpret_cast(doubleParams.data()), numberOfBytes); + for (SizeValueType i = 0; i < transformJSON.numberOfParameters; ++i) + { + params[i] = static_cast(doubleParams[i]); + } + } + else + { + dataStream.read(reinterpret_cast(params.data_block()), numberOfBytes); + } + } + else + { + itkExceptionMacro("Unknown parameters value type"); + } + (*transformIt)->SetParameters(params); + ++count; + } +} + +template +void +WasmTransformIOTemplate::Read() +{ + if (fileNameIsCBOR(this->GetFileName())) + { + this->ReadCBOR(); + return; + } + + const auto json = this->ReadTransformInformation(); + this->ReadFixedParameters(json); + this->ReadParameters(json); +} + +template +bool +WasmTransformIOTemplate::CanWriteFile(const char * name) +{ + std::string filename = name; + + if (filename == "") + { + return false; + } + + bool extensionFound = false; + std::string::size_type extensionPos = filename.rfind(".iwt"); + if (extensionPos != std::string::npos) + { + extensionFound = true; + } + + if (!extensionFound) + { + itkDebugMacro(<< "The filename extension is not recognized"); + return false; + } + + return true; +} + +template +void +WasmTransformIOTemplate::WriteTransformInformation() +{ + const std::string path = this->GetFileName(); + const auto indexPath = path + "/index.json"; + const auto dataPath = path + "/data"; + if (!itksys::SystemTools::FileExists(path, false)) + { + itksys::SystemTools::MakeDirectory(path); + } + if (!itksys::SystemTools::FileExists(dataPath, false)) + { + itksys::SystemTools::MakeDirectory(dataPath); + } + + auto transformListJSON = this->GetJSON(); + + std::string serialized{}; + glz::write(transformListJSON, serialized); + std::ofstream outputStream; + openFileForWriting(outputStream, indexPath.c_str(), true, true); + outputStream << serialized; + outputStream.close(); +} + +template +void +WasmTransformIOTemplate::WriteFixedParameters() +{ + ConstTransformListType & transformList = this->GetWriteTransformList(); + std::string compositeTransformType = transformList.front()->GetTransformTypeAsString(); + CompositeTransformIOHelperTemplate 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()); + } + + unsigned int count = 0; + typename ConstTransformListType::const_iterator end = transformList.end(); + for (typename ConstTransformListType::const_iterator it = transformList.begin(); it != end; ++it, ++count) + { + const TransformType * currentTransform = it->GetPointer(); + auto fixedParams = currentTransform->GetFixedParameters(); + // Fixed parameters are always double per itk::TransformBaseTemplate + const SizeValueType numberOfBytes = fixedParams.Size() * sizeof(FixedParametersValueType); + + std::string path(this->GetFileName()); + path = path + "/data/" + std::to_string(count); + const std::string filePath = path + "/fixed-parameters.raw"; + if (!itksys::SystemTools::FileExists(path, false)) + { + itksys::SystemTools::MakeDirectory(path); + } + + std::ofstream outputStream; + openFileForWriting(outputStream, filePath, true, false); + outputStream.write(reinterpret_cast(fixedParams.data_block()), numberOfBytes); + if (outputStream.tellp() != numberOfBytes) + { + itkExceptionMacro(<< "Write failed: Wanted to write " << numberOfBytes << " bytes, but wrote " + << outputStream.tellp() << " bytes."); + } + } +} + +template +void +WasmTransformIOTemplate::WriteParameters() +{ + ConstTransformListType & transformList = this->GetWriteTransformList(); + std::string compositeTransformType = transformList.front()->GetTransformTypeAsString(); + CompositeTransformIOHelperTemplate 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()); + } + + unsigned int count = 0; + typename ConstTransformListType::const_iterator end = transformList.end(); + for (typename ConstTransformListType::const_iterator it = transformList.begin(); it != end; ++it, ++count) + { + const TransformType * currentTransform = it->GetPointer(); + auto params = currentTransform->GetParameters(); + const SizeValueType numberOfBytes = params.Size() * sizeof(ParametersValueType); + + std::string path(this->GetFileName()); + path = path + "/data/" + std::to_string(count); + const std::string filePath = path + "/parameters.raw"; + if (!itksys::SystemTools::FileExists(path, false)) + { + itksys::SystemTools::MakeDirectory(path); + } + + std::ofstream outputStream; + openFileForWriting(outputStream, filePath, true, false); + outputStream.write(reinterpret_cast(params.data_block()), numberOfBytes); + if (outputStream.tellp() != numberOfBytes) + { + itkExceptionMacro(<< "Write failed: Wanted to write " << numberOfBytes << " bytes, but wrote " + << outputStream.tellp() << " bytes."); + } + } +} + +template +void +WasmTransformIOTemplate::Write() +{ + if (fileNameIsCBOR(this->GetFileName())) + { + this->WriteCBOR(); + return; + } + + this->WriteTransformInformation(); + this->WriteFixedParameters(); + this->WriteParameters(); +} + +ITK_GCC_PRAGMA_DIAG_PUSH() +ITK_GCC_PRAGMA_DIAG(ignored "-Wattributes") + +template class WebAssemblyInterface_EXPORT WasmTransformIOTemplate; +template class WebAssemblyInterface_EXPORT WasmTransformIOTemplate; + +ITK_GCC_PRAGMA_DIAG_POP() + +} // end namespace itk diff --git a/src/itkWasmTransformIOFactory.cxx b/src/itkWasmTransformIOFactory.cxx index ff95f8c40..9cf326227 100644 --- a/src/itkWasmTransformIOFactory.cxx +++ b/src/itkWasmTransformIOFactory.cxx @@ -25,11 +25,17 @@ namespace itk WasmTransformIOFactory ::WasmTransformIOFactory() { - this->RegisterOverride( "itkTransformIOBase", - "itkWasmTransformIO", - "Wasm Transform IO", - 1, - CreateObjectFunction< WasmTransformIO >::New() ); + this->RegisterOverride("itkTransformIOBaseTemplate", + "itkWasmTransformIOTemplate", + "Wasm Transform float IO", + true, + CreateObjectFunction>::New()); + + this->RegisterOverride("itkTransformIOBaseTemplate", + "itkWasmTransformIOTemplate", + "Wasm Transform double IO", + true, + CreateObjectFunction>::New()); } @@ -50,7 +56,7 @@ const char * WasmTransformIOFactory ::GetDescription() const { - return "Wasm TransformIO Factory, allows the loading of Wasm images into Insight"; + return "Wasm TransformIO Factory, allows the loading of Wasm transforms into Insight"; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index cea2baad6..bc535358d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -62,6 +62,7 @@ set(WebAssemblyInterfaceTests itkWasmPolyDataInterfaceTest.cxx itkWasmImageIOTest.cxx itkWasmMeshIOTest.cxx + itkWasmTransformIOTest.cxx itkPipelineTest.cxx itkPipelineMemoryIOTest.cxx itkSupportInputImageTypesTest.cxx @@ -69,6 +70,7 @@ set(WebAssemblyInterfaceTests itkSupportInputMeshTypesTest.cxx itkSupportInputMeshTypesMemoryIOTest.cxx itkSupportInputPolyDataTypesTest.cxx + itkTransformJSONTest.cxx ) if (EMSCRIPTEN) @@ -167,6 +169,16 @@ itk_add_test(NAME itkWasmMeshIOTest ${ITK_TEST_OUTPUT_DIR}/itkWasmMeshIOTest.cbor.vtk ) +itk_add_test(NAME itkWasmTransformIOTest + COMMAND WebAssemblyInterfaceTestDriver + itkWasmTransformIOTest + DATA{Input/LinearTransform.h5} + ${ITK_TEST_OUTPUT_DIR}/itkWasmTransformIOTest.iwt + ${ITK_TEST_OUTPUT_DIR}/itkWasmTransformIOTest.h5 + ${ITK_TEST_OUTPUT_DIR}/itkWasmTransformIOTest.iwt.cbor + ${ITK_TEST_OUTPUT_DIR}/itkWasmTransformIOTest.cbor.h5 +) + itk_add_test(NAME itkPipelineTest COMMAND WebAssemblyInterfaceTestDriver itkPipelineTest @@ -239,6 +251,11 @@ itk_add_test(NAME itkSupportInputPolyDataTypesTest ${ITK_TEST_OUTPUT_DIR}/itkSupportInputPolyDataTypesTest.iwm ) +itk_add_test(NAME itkTransformJSONTest + COMMAND WebAssemblyInterfaceTestDriver + itkTransformJSONTest +) + if(EMSCRIPTEN) # setjmp workaround set_property(TARGET WebAssemblyInterfaceTestDriver APPEND_STRING diff --git a/test/itkTransformJSONTest.cxx b/test/itkTransformJSONTest.cxx new file mode 100644 index 000000000..d291c398b --- /dev/null +++ b/test/itkTransformJSONTest.cxx @@ -0,0 +1,59 @@ +/*========================================================================= + * + * 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 "itkTransformJSON.h" +#include "itkTestingMacros.h" + +#include + +int +itkTransformJSONTest(int argc, char * argv[]) +{ + + itk::JSONTransformParameterizationEnum parameterization = itk::JSONTransformParameterizationEnum::Affine; + itk::JSONFloatTypesEnum floatType = itk::JSONFloatTypesEnum::float64; + + itk::TransformTypeJSON transformType(parameterization, floatType, 3, 3); + + itk::TransformJSON transform(transformType, 3, 12); + + itk::TransformListJSON transformList{ transform }; + + std::string serialized{}; + glz::write(transformList, serialized); + std::cout << serialized << std::endl; + + auto deserializedAttempt = glz::read_json(serialized); + if (!deserializedAttempt) + { + std::cerr << "Failed to deserialize TransformListJSON" << std::endl; + return EXIT_FAILURE; + } + + auto deserialized = deserializedAttempt.value(); + + ITK_TEST_EXPECT_EQUAL(deserialized.size(), 1); + const auto & deserializedTransform = deserialized.front(); + ITK_TEST_EXPECT_TRUE(deserializedTransform.transformType.transformParameterization == itk::JSONTransformParameterizationEnum::Affine); + ITK_TEST_EXPECT_TRUE(deserializedTransform.transformType.parametersValueType == itk::JSONFloatTypesEnum::float64); + ITK_TEST_EXPECT_EQUAL(deserializedTransform.transformType.inputDimension, 3); + ITK_TEST_EXPECT_EQUAL(deserializedTransform.transformType.outputDimension, 3); + ITK_TEST_EXPECT_EQUAL(deserializedTransform.numberOfFixedParameters, 3); + ITK_TEST_EXPECT_EQUAL(deserializedTransform.numberOfParameters, 12); + + return EXIT_SUCCESS; +} diff --git a/test/itkWasmTransformIOTest.cxx b/test/itkWasmTransformIOTest.cxx index cc2cb5c15..4f1e1f782 100644 --- a/test/itkWasmTransformIOTest.cxx +++ b/test/itkWasmTransformIOTest.cxx @@ -20,60 +20,126 @@ #include "itkTransformFileReader.h" #include "itkTransformFileWriter.h" #include "itkTestingMacros.h" +#include "itkTransform.h" +#include "itkHDF5TransformIOFactory.h" -int -itkWasmTransformIOTest(int argc, char * argv[]) +int itkWasmTransformIOTest(int argc, char *argv[]) { if (argc < 6) { std::cerr << "Missing parameters" << std::endl; - std::cerr << "Usage: " << itkNameOfTestExecutableMacro(argv) << " InputTransform TransformDirectory ConvertedDirectory TransformCBOR ConvertedCBOR" << std::endl; + std::cerr << "Usage: " << itkNameOfTestExecutableMacro(argv) << " InputTransform TransformDirectory ConvertedDirectory TransformZip ConvertedZip" << std::endl; return EXIT_FAILURE; } - const char * inputTransformFile = argv[1]; - const char * imageDirectory = argv[2]; - const char * convertedDirectoryFile = argv[3]; - const char * imageCBOR = argv[4]; - const char * convertedCBORFile = argv[5]; + const char *inputTransformFile = argv[1]; + const char *transformDirectory = argv[2]; + const char *convertedDirectoryFile = argv[3]; + const char *transformCbor = argv[4]; + const char *convertedCbor = argv[5]; itk::WasmTransformIOFactory::RegisterOneFactory(); + itk::HDF5TransformIOFactory::RegisterOneFactory(); constexpr unsigned int Dimension = 3; - using PixelType = unsigned char; - using TransformType = itk::Transform; + using ParametersValueType = double; + using TransformType = itk::Transform; using TransformPointer = TransformType::Pointer; - TransformPointer inputTransform = nullptr; - ITK_TRY_EXPECT_NO_EXCEPTION(inputTransform = itk::ReadTransform(inputTransformFile)); + using ReaderType = itk::TransformFileReaderTemplate; + auto transformReader = ReaderType::New(); + // LinearTransform.h5 + transformReader->SetFileName(inputTransformFile); + ITK_TRY_EXPECT_NO_EXCEPTION(transformReader->Update()); + auto inputTransforms = transformReader->GetTransformList(); - auto imageIO = itk::WasmTransformIO::New(); + using WasmTransformIOType = itk::WasmTransformIOTemplate; + auto transformIO = WasmTransformIOType::New(); - using WriterType = itk::TransformFileWriter; + using WriterType = itk::TransformFileWriterTemplate; auto wasmWriter = WriterType::New(); - //wasmWriter->SetTransformIO( imageIO ); - wasmWriter->SetFileName( imageDirectory ); - wasmWriter->SetInput( inputTransform ); + // wasmWriter->SetTransformIO( transformIO ); + // itkWasmTransformIOTest.iwt + wasmWriter->SetFileName(transformDirectory); + for (auto transform : *inputTransforms) + { + std::cout << "Input back transform:" << std::endl; + transform->Print(std::cout); + wasmWriter->AddTransform(transform); + } ITK_TRY_EXPECT_NO_EXCEPTION(wasmWriter->Update()); - using ReaderType = itk::TransformFileReader; auto wasmReader = ReaderType::New(); - //wasmReader->SetTransformIO( imageIO ); - wasmReader->SetFileName( imageDirectory ); - + // wasmReader->SetTransformIO( transformIO ); + // itkWasmTransformIOTest.iwt + wasmReader->SetFileName(transformDirectory); ITK_TRY_EXPECT_NO_EXCEPTION(wasmReader->Update()); + auto inputTransformsBack = wasmReader->GetTransformList(); - TransformPointer writtenReadTransform = wasmReader->GetOutput(); + auto transformWriter = WriterType::New(); + for (auto [transformIt, inputTransformIt] = std::tuple{ inputTransformsBack->begin(), inputTransforms->begin() }; + transformIt != inputTransformsBack->end(); + ++transformIt, ++inputTransformIt) + { + std::cout << "Read back transform:" << std::endl; + (*transformIt)->Print(std::cout); + + const auto numFixedParams = (*transformIt)->GetFixedParameters().Size(); + const auto numParams = (*transformIt)->GetParameters().Size(); + const auto numFixedParamsInput = (*inputTransformIt)->GetFixedParameters().Size(); + const auto numParamsInput = (*inputTransformIt)->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((*transformIt)->GetFixedParameters()[i], (*inputTransformIt)->GetFixedParameters()[i]); + } + for (unsigned int i = 0; i < numParams; ++i) + { + ITK_TEST_EXPECT_EQUAL((*transformIt)->GetParameters()[i], (*inputTransformIt)->GetParameters()[i]); + } - ITK_TRY_EXPECT_NO_EXCEPTION(itk::WriteTransform(writtenReadTransform, convertedDirectoryFile)); + transformWriter->AddTransform(*transformIt); + } + // itkWasmTransformIOTest.h5 + transformWriter->SetFileName(convertedDirectoryFile); + ITK_TRY_EXPECT_NO_EXCEPTION(transformWriter->Update()); - wasmWriter->SetFileName( imageCBOR ); + wasmWriter->SetFileName(transformCbor); ITK_TRY_EXPECT_NO_EXCEPTION(wasmWriter->Update()); - wasmReader->SetFileName( imageCBOR ); + wasmReader->SetFileName(transformCbor); ITK_TRY_EXPECT_NO_EXCEPTION(wasmReader->Update()); - ITK_TRY_EXPECT_NO_EXCEPTION(itk::WriteTransform(wasmReader->GetOutput(), convertedCBORFile)); + auto inputTransformsBackCbor = wasmReader->GetTransformList(); + auto transformWriterBack = WriterType::New(); + for (auto [transformIt, inputTransformIt] = std::tuple{ inputTransformsBackCbor->begin(), inputTransforms->begin() }; + transformIt != inputTransformsBackCbor->end(); + ++transformIt, ++inputTransformIt) + { + std::cout << "Read back cbor transform:" << std::endl; + (*transformIt)->Print(std::cout); + + const auto numFixedParams = (*transformIt)->GetFixedParameters().Size(); + const auto numParams = (*transformIt)->GetParameters().Size(); + const auto numFixedParamsInput = (*inputTransformIt)->GetFixedParameters().Size(); + const auto numParamsInput = (*inputTransformIt)->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((*transformIt)->GetFixedParameters()[i], (*inputTransformIt)->GetFixedParameters()[i]); + } + for (unsigned int i = 0; i < numParams; ++i) + { + ITK_TEST_EXPECT_EQUAL((*transformIt)->GetParameters()[i], (*inputTransformIt)->GetParameters()[i]); + } + + transformWriterBack->AddTransform(*transformIt); + } + + transformWriterBack->SetFileName(convertedCbor); + ITK_TRY_EXPECT_NO_EXCEPTION(transformWriterBack->Update()); return EXIT_SUCCESS; } From 7c608e5e3bb1dc21d963cb8584db2efffb0d19df Mon Sep 17 00:00:00 2001 From: Matt McCormick Date: Fri, 28 Jun 2024 15:34:52 -0400 Subject: [PATCH 05/15] feat(WasmTransformIO): support Composite transforms --- include/itkTransformJSON.h | 2 + model/itk-wasm.yml | 1 + src/itkWasmTransformIO.cxx | 173 +++++++-------------------- test/CMakeLists.txt | 20 ++++ test/Input/CompositeTransform.h5.cid | 1 + test/Input/TransformSequence.h5.cid | 1 + test/itkWasmTransformIOTest.cxx | 2 +- 7 files changed, 69 insertions(+), 131 deletions(-) create mode 100644 test/Input/CompositeTransform.h5.cid create mode 100644 test/Input/TransformSequence.h5.cid diff --git a/include/itkTransformJSON.h b/include/itkTransformJSON.h index 7f20a72e3..41c110338 100644 --- a/include/itkTransformJSON.h +++ b/include/itkTransformJSON.h @@ -29,6 +29,7 @@ namespace itk enum class JSONTransformParameterizationEnum { Identity, + Composite, Translation, Euler2D, Euler3D, @@ -101,6 +102,7 @@ template <> struct glz::meta { using enum itk::JSONTransformParameterizationEnum; static constexpr auto value = glz::enumerate(Identity, + Composite, Translation, Euler2D, Euler3D, diff --git a/model/itk-wasm.yml b/model/itk-wasm.yml index cdeb0bb47..ff803ede1 100644 --- a/model/itk-wasm.yml +++ b/model/itk-wasm.yml @@ -639,6 +639,7 @@ enums: A detailed description of each transform type can be found in the ITK Software Guide: https://itk.org/ITKSoftwareGuide/html/Book1/ITKSoftwareGuide-Book1ch4.html permissible_values: Identity: + Composite: Translation: Euler2D: Euler3D: diff --git a/src/itkWasmTransformIO.cxx b/src/itkWasmTransformIO.cxx index 9e59f2634..b042958b8 100644 --- a/src/itkWasmTransformIO.cxx +++ b/src/itkWasmTransformIO.cxx @@ -177,6 +177,10 @@ WasmTransformIOTemplate::ReadCBOR() { transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Identity; } + else if (transformParameterization == "Composite") + { + transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Composite; + } else if (transformParameterization == "Translation") { transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Translation; @@ -359,6 +363,7 @@ WasmTransformIOTemplate::ReadCBOR() } transformListJSON.push_back(transformJSON); } + this->SetJSON(transformListJSON); auto readTransformList = this->GetReadTransformList(); @@ -368,6 +373,11 @@ WasmTransformIOTemplate::ReadCBOR() ++transformIt, ++jsonIt) { const auto transformJSON = *jsonIt; + if (transformJSON.transformType.transformParameterization == JSONTransformParameterizationEnum::Composite) + { + ++count; + continue; + } FixedParametersType fixedParams(transformJSON.numberOfFixedParameters); const SizeValueType numberOfFixedBytes = transformJSON.numberOfFixedParameters * sizeof(FixedParametersValueType); ParametersType params(transformJSON.numberOfParameters); @@ -425,136 +435,6 @@ WasmTransformIOTemplate::ReadCBOR() } ++count; } - - - // const struct cbor_pair *indexHandle = cbor_map_handle(index); - // for (size_t ii = 0; ii < indexCount; ++ii) - // { - // const std::string_view key(reinterpret_cast(cbor_string_handle(indexHandle[ii].key)), - // cbor_string_length(indexHandle[ii].key)); if (key == "transformType") - // { - // const cbor_item_t *transformTypeItem = indexHandle[ii].value; - // const size_t transformPropertyCount = cbor_map_size(transformTypeItem); - // const struct cbor_pair *transformTypeHandle = cbor_map_handle(transformTypeItem); - // for (size_t jj = 0; jj < transformPropertyCount; ++jj) - // { - // const std::string_view transformTypeKey(reinterpret_cast(cbor_string_handle(transformTypeHandle[jj].key)), cbor_string_length(transformTypeHandle[jj].key)); if - // (transformTypeKey == "transformType") - // { - // const std::string transformType(reinterpret_cast(cbor_string_handle(transformTypeHandle[jj].value)), cbor_string_length(transformTypeHandle[jj].value)); - // const CommonEnums::IOComponent pointIOComponentType = - // IOComponentEnumFromWasmComponentType(pointComponentType); this->SetPointDimension(dimension); - // } - // else if (transformTypeKey == "inputDimension") - // { - // const auto inputDimension = cbor_get_uint32(transformTypeHandle[jj].value); - // this->SetPointDimension(inputDimension); - // } - // else if (transformTypeKey == "pointComponentType") - // { - // const std::string pointComponentType(reinterpret_cast(cbor_string_handle(transformTypeHandle[jj].value)), cbor_string_length(transformTypeHandle[jj].value)); - // const CommonEnums::IOComponent pointIOComponentType = - // IOComponentEnumFromWasmComponentType(pointComponentType); - // this->SetPointComponentType(pointIOComponentType); - // } - // else if (transformTypeKey == "pointPixelType") - // { - // const std::string pointPixelType(reinterpret_cast(cbor_string_handle(transformTypeHandle[jj].value)), cbor_string_length(transformTypeHandle[jj].value)); - // const CommonEnums::IOPixel pointIOPixelType = IOPixelEnumFromWasmPixelType(pointPixelType); - // this->SetPointPixelType(pointIOPixelType); - // } - // else if (transformTypeKey == "pointPixelComponentType") - // { - // const std::string pointPixelComponentType(reinterpret_cast(cbor_string_handle(transformTypeHandle[jj].value)), cbor_string_length(transformTypeHandle[jj].value)); - // const CommonEnums::IOComponent pointPixelIOComponentType = - // IOComponentEnumFromWasmComponentType(pointPixelComponentType); - // this->SetPointPixelComponentType(pointPixelIOComponentType); - // } - // else if (transformTypeKey == "pointPixelComponents") - // { - // const auto components = cbor_get_uint32(transformTypeHandle[jj].value); - // this->SetNumberOfPointPixelComponents(components); - // } - // else if (transformTypeKey == "cellComponentType") - // { - // const std::string cellComponentType(reinterpret_cast(cbor_string_handle(transformTypeHandle[jj].value)), cbor_string_length(transformTypeHandle[jj].value)); - // const CommonEnums::IOComponent cellIOComponentType = - // IOComponentEnumFromWasmComponentType(cellComponentType); this->SetCellComponentType(cellIOComponentType); - // } - // else if (transformTypeKey == "cellPixelType") - // { - // const std::string cellPixelType(reinterpret_cast(cbor_string_handle(transformTypeHandle[jj].value)), cbor_string_length(transformTypeHandle[jj].value)); - // const CommonEnums::IOPixel cellIOPixelType = IOPixelEnumFromWasmPixelType(cellPixelType); - // this->SetCellPixelType(cellIOPixelType); - // } - // else if (transformTypeKey == "cellPixelComponentType") - // { - // const std::string cellPixelComponentType(reinterpret_cast(cbor_string_handle(transformTypeHandle[jj].value)), cbor_string_length(transformTypeHandle[jj].value)); - // const CommonEnums::IOComponent cellPixelIOComponentType = - // IOComponentEnumFromWasmComponentType(cellPixelComponentType); - // this->SetCellPixelComponentType(cellPixelIOComponentType); - // } - // else if (transformTypeKey == "cellPixelComponents") - // { - // const auto components = cbor_get_uint32(transformTypeHandle[jj].value); - // this->SetNumberOfCellPixelComponents(components); - // } - // else - // { - // itkExceptionMacro("Unexpected transformType cbor map key: " << transformTypeKey); - // } - // } - // } - // else if (key == "numberOfPoints") - // { - // const auto components = cbor_get_uint64(indexHandle[ii].value); - // this->SetNumberOfPoints(components); - // if (components) - // { - // this->m_UpdatePoints = true; - // } - // } - // else if (key == "numberOfPointPixels") - // { - // const auto components = cbor_get_uint64(indexHandle[ii].value); - // this->SetNumberOfPointPixels(components); - // if (components) - // { - // this->m_UpdatePointData = true; - // } - // } - // else if (key == "numberOfCells") - // { - // const auto components = cbor_get_uint64(indexHandle[ii].value); - // this->SetNumberOfCells(components); - // if (components) - // { - // this->m_UpdateCells = true; - // } - // } - // else if (key == "numberOfCellPixels") - // { - // const auto components = cbor_get_uint64(indexHandle[ii].value); - // this->SetNumberOfCellPixels(components); - // if (components) - // { - // this->m_UpdateCellData = true; - // } - // } - // else if (key == "cellBufferSize") - // { - // const auto components = cbor_get_uint64(indexHandle[ii].value); - // this->SetCellBufferSize(components); - // } - // } } template @@ -597,6 +477,10 @@ WasmTransformIOTemplate::GetJSON() -> TransformListJSON { transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Identity; } + else if (pString == "CompositeTransform") + { + transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Composite; + } else if (pString == "TranslationTransform") { transformJSON.transformType.transformParameterization = JSONTransformParameterizationEnum::Translation; @@ -752,6 +636,11 @@ WasmTransformIOTemplate::TransformParameterizationString(c transformParameterization = "Identity"; break; } + case JSONTransformParameterizationEnum::Composite: + { + transformParameterization = "Composite"; + break; + } case JSONTransformParameterizationEnum::Translation: { transformParameterization = "Translation"; @@ -1015,6 +904,12 @@ WasmTransformIOTemplate::WriteCBOR() cbor_pair{ cbor_move(cbor_build_string("outputSpaceName")), cbor_move(cbor_build_string(transformJSON.outputSpaceName.c_str())) }); + if (transformJSON.transformType.transformParameterization == JSONTransformParameterizationEnum::Composite) + { + cbor_array_push(index, cbor_move(transformItem)); + ++count; + continue; + } const auto fixedNumberOfBytes = transformJSON.numberOfFixedParameters * sizeof(FixedParametersValueType); const auto fixedParams = (*transformIt)->GetFixedParameters(); writeCBORBuffer(transformItem, @@ -1092,6 +987,11 @@ WasmTransformIOTemplate::ReadFixedParameters(const Transfo ++transformIt, ++jsonIt) { const auto transformJSON = *jsonIt; + if ((*jsonIt).transformType.transformParameterization == itk::JSONTransformParameterizationEnum::Composite) + { + ++count; + continue; + } FixedParametersType fixedParams(transformJSON.numberOfFixedParameters); const SizeValueType numberOfBytes = transformJSON.numberOfFixedParameters * sizeof(FixedParametersValueType); @@ -1121,6 +1021,11 @@ WasmTransformIOTemplate::ReadParameters(const TransformLis transformIt != readTransformList.end(); ++transformIt, ++jsonIt) { + if ((*jsonIt).transformType.transformParameterization == itk::JSONTransformParameterizationEnum::Composite) + { + ++count; + continue; + } const auto transformJSON = *jsonIt; ParametersType params(transformJSON.numberOfParameters); const auto valueBytes = sizeof(ParametersValueType); @@ -1262,6 +1167,10 @@ WasmTransformIOTemplate::WriteFixedParameters() for (typename ConstTransformListType::const_iterator it = transformList.begin(); it != end; ++it, ++count) { const TransformType * currentTransform = it->GetPointer(); + if (currentTransform->GetTransformTypeAsString().find("CompositeTransform") != std::string::npos) + { + continue; + } auto fixedParams = currentTransform->GetFixedParameters(); // Fixed parameters are always double per itk::TransformBaseTemplate const SizeValueType numberOfBytes = fixedParams.Size() * sizeof(FixedParametersValueType); @@ -1307,6 +1216,10 @@ WasmTransformIOTemplate::WriteParameters() for (typename ConstTransformListType::const_iterator it = transformList.begin(); it != end; ++it, ++count) { const TransformType * currentTransform = it->GetPointer(); + if (currentTransform->GetTransformTypeAsString().find("CompositeTransform") != std::string::npos) + { + continue; + } auto params = currentTransform->GetParameters(); const SizeValueType numberOfBytes = params.Size() * sizeof(ParametersValueType); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index bc535358d..609622974 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -179,6 +179,26 @@ itk_add_test(NAME itkWasmTransformIOTest ${ITK_TEST_OUTPUT_DIR}/itkWasmTransformIOTest.cbor.h5 ) +itk_add_test(NAME itkWasmTransformIOSequenceTest + COMMAND WebAssemblyInterfaceTestDriver + itkWasmTransformIOTest + DATA{Input/TransformSequence.h5} + ${ITK_TEST_OUTPUT_DIR}/itkWasmTransformIOSequenceTest.iwt + ${ITK_TEST_OUTPUT_DIR}/itkWasmTransformIOSequenceTest.h5 + ${ITK_TEST_OUTPUT_DIR}/itkWasmTransformIOSequenceTest.iwt.cbor + ${ITK_TEST_OUTPUT_DIR}/itkWasmTransformIOSequenceTest.cbor.h5 +) + +itk_add_test(NAME itkWasmTransformIOCompositeTest + COMMAND WebAssemblyInterfaceTestDriver + itkWasmTransformIOTest + DATA{Input/CompositeTransform.h5} + ${ITK_TEST_OUTPUT_DIR}/itkWasmTransformIOCompositeTest.iwt + ${ITK_TEST_OUTPUT_DIR}/itkWasmTransformIOCompositeTest.h5 + ${ITK_TEST_OUTPUT_DIR}/itkWasmTransformIOCompositeTest.iwt.cbor + ${ITK_TEST_OUTPUT_DIR}/itkWasmTransformIOCompositeTest.cbor.h5 +) + itk_add_test(NAME itkPipelineTest COMMAND WebAssemblyInterfaceTestDriver itkPipelineTest diff --git a/test/Input/CompositeTransform.h5.cid b/test/Input/CompositeTransform.h5.cid new file mode 100644 index 000000000..05ec226cf --- /dev/null +++ b/test/Input/CompositeTransform.h5.cid @@ -0,0 +1 @@ +bafkreibq75hrj3xoemt2lof5fla3vjysmkiq4yy4s74k3q54wf264n6prm diff --git a/test/Input/TransformSequence.h5.cid b/test/Input/TransformSequence.h5.cid new file mode 100644 index 000000000..228a8d0e1 --- /dev/null +++ b/test/Input/TransformSequence.h5.cid @@ -0,0 +1 @@ +bafkreidoegsttlbmdcgg5rhodbaq52urxkirgzbehzrs2jxhrynlwal64u diff --git a/test/itkWasmTransformIOTest.cxx b/test/itkWasmTransformIOTest.cxx index 4f1e1f782..714c0e9e0 100644 --- a/test/itkWasmTransformIOTest.cxx +++ b/test/itkWasmTransformIOTest.cxx @@ -62,7 +62,7 @@ int itkWasmTransformIOTest(int argc, char *argv[]) wasmWriter->SetFileName(transformDirectory); for (auto transform : *inputTransforms) { - std::cout << "Input back transform:" << std::endl; + std::cout << "Input transform:" << std::endl; transform->Print(std::cout); wasmWriter->AddTransform(transform); } From a4f6b49a65de9cd689a43215ac31d04a413d767d Mon Sep 17 00:00:00 2001 From: Matt McCormick Date: Sun, 30 Jun 2024 21:36:58 -0400 Subject: [PATCH 06/15] ci: Do not build macos-12 in CI Does not support C++20. --- .github/workflows/cxx-python.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cxx-python.yml b/.github/workflows/cxx-python.yml index 5489c24f6..4e243c5ff 100644 --- a/.github/workflows/cxx-python.yml +++ b/.github/workflows/cxx-python.yml @@ -13,13 +13,13 @@ env: jobs: cxx-build-workflow: - uses: InsightSoftwareConsortium/ITKRemoteModuleBuildTestPackageAction/.github/workflows/build-test-cxx.yml@v5.4.0 + uses: thewtex/ITKRemoteModuleBuildTestPackageAction/.github/workflows/build-test-cxx.yml@0b6e8918614196db3b8dde499cf16f2398c78474 with: itk-module-deps: 'MeshToPolyData@v0.11.0' ctest-options: '-E itkPipelineTest' python-build-workflow: - uses: InsightSoftwareConsortium/ITKRemoteModuleBuildTestPackageAction/.github/workflows/build-test-package-python.yml@v5.4.0 + uses: thewtex/ITKRemoteModuleBuildTestPackageAction/.github/workflows/build-test-package-python.yml@0b6e8918614196db3b8dde499cf16f2398c78474 with: itk-module-deps: 'InsightSoftwareConsortium/ITKMeshToPolyData@v0.11.0' secrets: From e3b37ef9320efcca3d4291f66aced464316dacdb Mon Sep 17 00:00:00 2001 From: Matt McCormick Date: Mon, 1 Jul 2024 11:36:34 -0400 Subject: [PATCH 07/15] feat(TransformJSON): specify initializers --- include/itkTransformJSON.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/itkTransformJSON.h b/include/itkTransformJSON.h index 41c110338..e2a72dcb5 100644 --- a/include/itkTransformJSON.h +++ b/include/itkTransformJSON.h @@ -65,10 +65,10 @@ namespace itk */ struct TransformTypeJSON { - JSONTransformParameterizationEnum transformParameterization; - JSONFloatTypesEnum parametersValueType; - unsigned int inputDimension; - unsigned int outputDimension; + JSONTransformParameterizationEnum transformParameterization{ JSONTransformParameterizationEnum::Identity }; + JSONFloatTypesEnum parametersValueType { JSONFloatTypesEnum::float64 }; + unsigned int inputDimension { 3 }; + unsigned int outputDimension { 3 }; }; /** \class TransformJSON @@ -80,8 +80,8 @@ namespace itk struct TransformJSON { TransformTypeJSON transformType; - uint64_t numberOfFixedParameters; - uint64_t numberOfParameters; + uint64_t numberOfFixedParameters{ 0 }; + uint64_t numberOfParameters{ 0 }; std::string name; std::string inputSpaceName; std::string outputSpaceName; From 0c9402941c7513a4d5fef2dfff2c61443bf855dc Mon Sep 17 00:00:00 2001 From: Matt McCormick Date: Mon, 1 Jul 2024 11:51:25 -0400 Subject: [PATCH 08/15] fix: use initialization with braces in itkTransformJSONTest Performance and attempt to address compilation errors related to constructor definition on the macos-14 build. --- test/itkTransformJSONTest.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/itkTransformJSONTest.cxx b/test/itkTransformJSONTest.cxx index d291c398b..2c45a5b53 100644 --- a/test/itkTransformJSONTest.cxx +++ b/test/itkTransformJSONTest.cxx @@ -27,9 +27,9 @@ itkTransformJSONTest(int argc, char * argv[]) itk::JSONTransformParameterizationEnum parameterization = itk::JSONTransformParameterizationEnum::Affine; itk::JSONFloatTypesEnum floatType = itk::JSONFloatTypesEnum::float64; - itk::TransformTypeJSON transformType(parameterization, floatType, 3, 3); + itk::TransformTypeJSON transformType{parameterization, floatType, 3, 3}; - itk::TransformJSON transform(transformType, 3, 12); + itk::TransformJSON transform{transformType, 3, 12}; itk::TransformListJSON transformList{ transform }; From e18ac542e2b6f0a3d45b9369b25c9540d5791564 Mon Sep 17 00:00:00 2001 From: Matt McCormick Date: Mon, 1 Jul 2024 11:52:39 -0400 Subject: [PATCH 09/15] feat: bump glaze to 2.9.3 --- CMakeLists.txt | 4 ++-- test/itkTransformJSONTest.cxx | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ddf44aec4..1e9a86708 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,8 +79,8 @@ FetchContent_Declare( ) set(glaze_GIT_REPOSITORY "https://github.com/stephenberry/glaze") -# glaze v2.6.9 -set(glaze_GIT_TAG 6bcc20ce7eb59df60b94488a4f6be723731db00e) +# glaze v2.9.3 +set(glaze_GIT_TAG fe49c8e1a057d11484e0bd88ffcbe60277e356fd) FetchContent_Declare( glaze GIT_REPOSITORY ${glaze_GIT_REPOSITORY} diff --git a/test/itkTransformJSONTest.cxx b/test/itkTransformJSONTest.cxx index 2c45a5b53..0783ddd4e 100644 --- a/test/itkTransformJSONTest.cxx +++ b/test/itkTransformJSONTest.cxx @@ -34,7 +34,12 @@ itkTransformJSONTest(int argc, char * argv[]) itk::TransformListJSON transformList{ transform }; std::string serialized{}; - glz::write(transformList, serialized); + auto ec = glz::write(transformList, serialized); + if (ec) + { + std::cerr << "Failed to serialize TransformListJSON" << std::endl; + return EXIT_FAILURE; + } std::cout << serialized << std::endl; auto deserializedAttempt = glz::read_json(serialized); From 81f3a7ddef715ee3e37ae351d3df2f8d2302d437 Mon Sep 17 00:00:00 2001 From: Matt McCormick Date: Mon, 1 Jul 2024 13:38:09 -0400 Subject: [PATCH 10/15] build: use XCode 15.3 for std::hash To address: /Applications/Xcode_15.0.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.0.sdk/usr/include/c++/v1/unordered_map:577:17: error: type 'const std::hash' does not provide a call operator --- .github/workflows/cxx-python.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cxx-python.yml b/.github/workflows/cxx-python.yml index 4e243c5ff..07e14ce00 100644 --- a/.github/workflows/cxx-python.yml +++ b/.github/workflows/cxx-python.yml @@ -13,13 +13,15 @@ env: jobs: cxx-build-workflow: - uses: thewtex/ITKRemoteModuleBuildTestPackageAction/.github/workflows/build-test-cxx.yml@0b6e8918614196db3b8dde499cf16f2398c78474 + # itk-wasm branch + uses: thewtex/ITKRemoteModuleBuildTestPackageAction/.github/workflows/build-test-cxx.yml@6868c9879405def5d7532e0437b57e78cbe0b6ea with: itk-module-deps: 'MeshToPolyData@v0.11.0' ctest-options: '-E itkPipelineTest' python-build-workflow: - uses: thewtex/ITKRemoteModuleBuildTestPackageAction/.github/workflows/build-test-package-python.yml@0b6e8918614196db3b8dde499cf16f2398c78474 + # itk-wasm branch + uses: thewtex/ITKRemoteModuleBuildTestPackageAction/.github/workflows/build-test-package-python.yml@6868c9879405def5d7532e0437b57e78cbe0b6ea with: itk-module-deps: 'InsightSoftwareConsortium/ITKMeshToPolyData@v0.11.0' secrets: From f9c4441de4fb35e956b4ec27e83f9c0941a75b16 Mon Sep 17 00:00:00 2001 From: Matt McCormick Date: Mon, 1 Jul 2024 13:53:42 -0400 Subject: [PATCH 11/15] build: add itkFloatTypesJSON.h to KWStyle namespace disable For the glaze specialization. --- ITKKWStyleOverwrite.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/ITKKWStyleOverwrite.txt b/ITKKWStyleOverwrite.txt index d56968212..3b776e50a 100644 --- a/ITKKWStyleOverwrite.txt +++ b/ITKKWStyleOverwrite.txt @@ -5,3 +5,4 @@ itkSupportInputImageTypesMemoryIOTest\.cxx Namespace Disable itkSupportInputMeshTypesTest\.cxx Namespace Disable itkSupportInputMeshTypesMemoryIOTest\.cxx Namespace Disable itkSupportInputPolyDataTypesTest\.cxx Namespace Disable +itkFloatTypesJSON\.h Namespace Disable From 0e819119e05532449ecf7c3624986eb5e770cab2 Mon Sep 17 00:00:00 2001 From: Matt McCormick Date: Mon, 1 Jul 2024 14:04:59 -0400 Subject: [PATCH 12/15] build: do not build manylinux2014 native binary wheels C++ standard support is insufficient for glaze. --- .github/workflows/cxx-python.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cxx-python.yml b/.github/workflows/cxx-python.yml index 07e14ce00..492640c3c 100644 --- a/.github/workflows/cxx-python.yml +++ b/.github/workflows/cxx-python.yml @@ -24,5 +24,6 @@ jobs: uses: thewtex/ITKRemoteModuleBuildTestPackageAction/.github/workflows/build-test-package-python.yml@6868c9879405def5d7532e0437b57e78cbe0b6ea with: itk-module-deps: 'InsightSoftwareConsortium/ITKMeshToPolyData@v0.11.0' + manylinux-platforms: '["_2_28-x64","_2_28-aarch64"]' secrets: pypi_password: ${{ secrets.pypi_password }} From 757c0256a2cae2cf8466dcb351b37947aeb5fbce Mon Sep 17 00:00:00 2001 From: Matt McCormick Date: Mon, 1 Jul 2024 17:50:57 -0400 Subject: [PATCH 13/15] build(glaze): add flags required for MSVC Based or glaze's CMake configuration. --- CMakeLists.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e9a86708..acf9c994a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,6 +87,15 @@ FetchContent_Declare( GIT_TAG ${glaze_GIT_TAG} GIT_SHALLOW TRUE ) +if (MSVC) + string(REGEX MATCH "\/cl(.exe)?$" matched_cl ${CMAKE_CXX_COMPILER}) + if (matched_cl) + # for a C++ standards compliant preprocessor, not needed for clang-cl + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zc:preprocessor /permissive- /Zc:lambda") + endif() + # avoid warnings + set(CMAKE_CXX_STANDARD 23) +endif() list(APPEND CMAKE_MODULE_PATH ${libcbor_SOURCE_DIR}/CMakeModules) FetchContent_MakeAvailable(rapidjson_lib cli11 rang libcbor cpp_base64 glaze) From 58a0303fc3880303808fcbced70a19ead171aaef Mon Sep 17 00:00:00 2001 From: Matt McCormick Date: Mon, 1 Jul 2024 17:57:32 -0400 Subject: [PATCH 14/15] build: check the result of glz::write in itkWasmTransformIO --- src/itkWasmTransformIO.cxx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/itkWasmTransformIO.cxx b/src/itkWasmTransformIO.cxx index b042958b8..a45a19a61 100644 --- a/src/itkWasmTransformIO.cxx +++ b/src/itkWasmTransformIO.cxx @@ -1138,7 +1138,11 @@ WasmTransformIOTemplate::WriteTransformInformation() auto transformListJSON = this->GetJSON(); std::string serialized{}; - glz::write(transformListJSON, serialized); + auto ec = glz::write(transformListJSON, serialized); + if (ec) + { + itkExceptionMacro("Failed to serialize TransformListJSON"); + } std::ofstream outputStream; openFileForWriting(outputStream, indexPath.c_str(), true, true); outputStream << serialized; From 3005861d772bf7dd2374fccfd21e0d54933fe253 Mon Sep 17 00:00:00 2001 From: Matt McCormick Date: Mon, 1 Jul 2024 22:02:59 -0400 Subject: [PATCH 15/15] ci: disable native python build workflow Updates needed for CastXML C++20 support, CastXML GCC C++20. --- .github/workflows/cxx-python.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/cxx-python.yml b/.github/workflows/cxx-python.yml index 492640c3c..e37a70b7a 100644 --- a/.github/workflows/cxx-python.yml +++ b/.github/workflows/cxx-python.yml @@ -19,11 +19,11 @@ jobs: itk-module-deps: 'MeshToPolyData@v0.11.0' ctest-options: '-E itkPipelineTest' - python-build-workflow: - # itk-wasm branch - uses: thewtex/ITKRemoteModuleBuildTestPackageAction/.github/workflows/build-test-package-python.yml@6868c9879405def5d7532e0437b57e78cbe0b6ea - with: - itk-module-deps: 'InsightSoftwareConsortium/ITKMeshToPolyData@v0.11.0' - manylinux-platforms: '["_2_28-x64","_2_28-aarch64"]' - secrets: - pypi_password: ${{ secrets.pypi_password }} + #python-build-workflow: + ## itk-wasm branch + #uses: thewtex/ITKRemoteModuleBuildTestPackageAction/.github/workflows/build-test-package-python.yml@6868c9879405def5d7532e0437b57e78cbe0b6ea + #with: + #itk-module-deps: 'InsightSoftwareConsortium/ITKMeshToPolyData@v0.11.0' + #manylinux-platforms: '["_2_28-x64","_2_28-aarch64"]' + #secrets: + #pypi_password: ${{ secrets.pypi_password }}