diff --git a/Modules/IO/IOFDF/CMakeLists.txt b/Modules/IO/IOFDF/CMakeLists.txt new file mode 100644 index 000000000000..6634dc019cba --- /dev/null +++ b/Modules/IO/IOFDF/CMakeLists.txt @@ -0,0 +1,4 @@ +project(IOFDF) +set(IOFDF_LIBRARIES IOFDF) + +itk_module_impl() diff --git a/Modules/IO/IOFDF/LICENSE b/Modules/IO/IOFDF/LICENSE new file mode 100644 index 000000000000..62589edd12a3 --- /dev/null +++ b/Modules/IO/IOFDF/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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 + + 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. diff --git a/Modules/IO/IOFDF/README.md b/Modules/IO/IOFDF/README.md new file mode 100644 index 000000000000..d18486112a13 --- /dev/null +++ b/Modules/IO/IOFDF/README.md @@ -0,0 +1,41 @@ +# IOFDF + +In-tree ITK module providing read support for the **FDF** (Flexible +Data Format) image files produced by Varian / Agilent MR scanners. +Implements `itk::FDFImageIO` and registers `itk::FDFImageIOFactory` +so FDF files are auto-detected by `itk::ImageFileReader`. + +## Origin + +Ingested from the standalone remote module +[**InsightSoftwareConsortium/ITKIOFDF**](https://github.com/InsightSoftwareConsortium/ITKIOFDF) +on 2026-05-05, at upstream commit +[`3a3ff84c`](https://github.com/InsightSoftwareConsortium/ITKIOFDF/commit/3a3ff84c803d4226176078d32ddd0760c71a5b2c). +The upstream repository will be archived read-only after this PR +merges; it remains reachable at the URL above. + +## What lives here + +Per the v4 ingestion strategy (see +`Utilities/Maintenance/RemoteModuleIngest/INGESTION_STRATEGY_v4.md`), +only paths matching the narrow whitelist +(`Utilities/Maintenance/RemoteModuleIngest/whitelists/IOFDF.list`) +crossed the merge boundary: + +- `include/` — public C++ headers (`itkFDFImageIO.h`, + `itkFDFImageIOFactory.h`, `itkFDFCommonImageIO.h`). +- `src/` — implementation sources. +- `test/` — CTest drivers and content-link stubs. +- `wrapping/` — Python wrapping descriptors. +- `CMakeLists.txt`, `itk-module.cmake` — module metadata. +- `LICENSE` — original Apache-2.0 license text. + +Upstream-only artifacts (CI dashboards, packaging, +`pyproject.toml`, `.github/`, etc.) were explicitly excluded; the +upstream commit graph and authorship history are otherwise preserved +through a `--no-ff --allow-unrelated-histories` Mode-A merge. + +## Building + +IOFDF is enabled by default once `Module_IOFDF=ON` is set; in CI it +is enabled via `pyproject.toml`'s `configure-ci` task. diff --git a/Modules/IO/IOFDF/include/itkFDFCommonImageIO.h b/Modules/IO/IOFDF/include/itkFDFCommonImageIO.h new file mode 100644 index 000000000000..23abae67226f --- /dev/null +++ b/Modules/IO/IOFDF/include/itkFDFCommonImageIO.h @@ -0,0 +1,100 @@ +/*========================================================================= + * + * 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 itkFDFCommonImageIO_h +#define itkFDFCommonImageIO_h + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace itk +{ + +std::string +RemoveCharacters(std::string, char); + +void +Tokenize(const std::string & str, std::vector & tokens, const std::string & delimiters = " "); + +std::string +ParseLine(std::string line); + +template +void +ConvertFromString(std::string s, T & value) +{ + std::stringstream str; + str << s; + str >> value; +} + +template +void +StringToVector(std::string value, std::vector & values) +{ + std::vector tokens; + + // value consists of something like {256,256} + std::string::size_type startBracketPosition = value.find_first_of("{", 0); + std::string::size_type endBracketPosition = value.find_first_of("}", startBracketPosition); + + if (startBracketPosition != std::string::npos && endBracketPosition != std::string::npos) + { + std::string elements = value.substr(startBracketPosition + 1, endBracketPosition - startBracketPosition - 1); + + + Tokenize(elements, tokens, ","); + } + + T element; + + for (auto & token : tokens) + { + ConvertFromString(token, element); + values.push_back(element); + } +} + +template +void +PrintVector(std::ostream & os, std::string name, const std::vector & vect) +{ + int size = vect.size(); + + os << name << " {"; + + for (int i = 0; i < size; i++) + { + os << vect[i]; + + if (i < size - 1) + os << ", "; + } + + os << "}" << std::endl; +} + +} // namespace itk + +#endif diff --git a/Modules/IO/IOFDF/include/itkFDFImageIO.h b/Modules/IO/IOFDF/include/itkFDFImageIO.h new file mode 100644 index 000000000000..734e46818be6 --- /dev/null +++ b/Modules/IO/IOFDF/include/itkFDFImageIO.h @@ -0,0 +1,146 @@ +/*========================================================================= + * + * 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 itkFDFImageIO_h +#define itkFDFImageIO_h +#include "IOFDFExport.h" +#include "itkImageIOBase.h" + +namespace itk +{ + +/** \class FDFImageIO + * + * \brief ImageIO object for reading and writing FDF images + * + * \ingroup IOFDF + * + */ +class IOFDF_EXPORT FDFImageIO : public ImageIOBase +{ +public: + ITK_DISALLOW_COPY_AND_MOVE(FDFImageIO); + + /** Standard class type alias. */ + using Self = FDFImageIO; + using Superclass = ImageIOBase; + using Pointer = SmartPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self); + + /** Run-time type information (and related methods). */ + itkOverrideGetNameOfClassMacro(FDFImageIO); + + bool + SupportsDimension(unsigned long dim) override + { + if (dim == 2 || dim == 3) + { + return true; + } + else + { + return false; + } + } + + + /*-------- This part of the interface deals with reading data. ------ */ + + /** Determine the file type. Returns true if this ImageIO can read the + * file specified. */ + bool + CanReadFile(const char *) override; + + /** Set the spacing and diemention information for the set filename. */ + void + ReadImageInformation() override; + + /** Get the type of the pixel. */ + // virtual const std::type_info& GetPixelType() const; + + /** Reads the data from disk into the memory buffer provided. */ + void + Read(void * buffer) override; + + /** Reads 3D data from multiple files assuming one slice per file. */ + virtual void + ReadVolume(void * buffer); + + /** Compute the size (in bytes) of the components of a pixel. For + * example, and RGB pixel of unsigned char would have a + * component size of 1 byte. */ + // virtual unsigned int GetComponentSize() const; + + /*-------- This part of the interfaces deals with writing data. ----- */ + + /** Determine the file type. Returns true if this ImageIO can read the + * file specified. */ + bool + CanWriteFile(const char *) override; + + /** Writes the spacing and dimentions of the image. + * Assumes SetFileName has been called with a valid file name. */ + void + WriteImageInformation() override; + + /** Writes the data to disk from the memory buffer provided. Make sure + * that the IORegion has been set properly. */ + void + Write(const void * buffer) override; + +protected: + FDFImageIO(); + ~FDFImageIO() override; + void + PrintSelf(std::ostream & os, Indent indent) const override; + + void + WriteSlice(std::string & fileName, const void * buffer); + + int + ReadHeader(const char * FileNameToRead); + +private: + void + SwapBytesIfNecessary(void * buffer, unsigned long numberOfPixels); + + // Position after ReadImageInformation. + size_t m_InputPosition; + + std::string m_SpatialRank; + std::string m_Checksum; + std::string m_Bits; + std::vector m_Size; + std::vector m_Location; + std::vector m_Span; + std::vector m_Roi; +}; + +} // end namespace itk + + +#define RAISE_EXCEPTION() \ + { \ + ExceptionObject exception(__FILE__, __LINE__); \ + exception.SetDescription("File cannot be read"); \ + throw exception; \ + } + +#endif diff --git a/Modules/IO/IOFDF/include/itkFDFImageIOFactory.h b/Modules/IO/IOFDF/include/itkFDFImageIOFactory.h new file mode 100644 index 000000000000..0f5eac043309 --- /dev/null +++ b/Modules/IO/IOFDF/include/itkFDFImageIOFactory.h @@ -0,0 +1,69 @@ +/*========================================================================= + * + * 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 itkFDFImageIOFactory_h +#define itkFDFImageIOFactory_h +#include "IOFDFExport.h" +#include "itkObjectFactoryBase.h" +#include "itkImageIOBase.h" + +namespace itk +{ +/** \class FDFImageIOFactory + * \brief Create instances of FDFImageIO objects using an object factory. + * \ingroup IOFDF + */ +class IOFDF_EXPORT FDFImageIOFactory : public ObjectFactoryBase +{ +public: + ITK_DISALLOW_COPY_AND_MOVE(FDFImageIOFactory); + + /** Standard class type alias. */ + using Self = FDFImageIOFactory; + using Superclass = ObjectFactoryBase; + using Pointer = SmartPointer; + using ConstPointer = SmartPointer; + + /** 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). */ + itkOverrideGetNameOfClassMacro(FDFImageIOFactory); + + /** Register one factory of this type */ + static void + RegisterOneFactory() + { + FDFImageIOFactory::Pointer FdfFactory = FDFImageIOFactory::New(); + ObjectFactoryBase::RegisterFactoryInternal(FdfFactory); + } + +protected: + FDFImageIOFactory(); + ~FDFImageIOFactory() override; +}; + + +} // end namespace itk + +#endif diff --git a/Modules/IO/IOFDF/itk-module.cmake b/Modules/IO/IOFDF/itk-module.cmake new file mode 100644 index 000000000000..7a70bffc890a --- /dev/null +++ b/Modules/IO/IOFDF/itk-module.cmake @@ -0,0 +1,24 @@ +# the top-level README is used for describing this module, just +# re-used it for documentation here +get_filename_component(MY_CURRENT_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) +file(READ "${MY_CURRENT_DIR}/README.md" DOCUMENTATION) + +# itk_module() defines the module dependencies in IOFDF +# The testing module in IOFDF depends on ITKTestKernel +# By convention those modules outside of ITK are not prefixed with +# ITK + +# define the dependencies of the include module and the tests +itk_module( + IOFDF + ENABLE_SHARED + DEPENDS + ITKIOImageBase + TEST_DEPENDS + ITKTestKernel + ITKTransform + FACTORY_NAMES + ImageIO::FDF + DESCRIPTION "${DOCUMENTATION}" + EXCLUDE_FROM_DEFAULT +) diff --git a/Modules/IO/IOFDF/src/CMakeLists.txt b/Modules/IO/IOFDF/src/CMakeLists.txt new file mode 100644 index 000000000000..d7d5756fcadb --- /dev/null +++ b/Modules/IO/IOFDF/src/CMakeLists.txt @@ -0,0 +1,8 @@ +set( + IOFDF_SRC + itkFDFImageIO.cxx + itkFDFCommonImageIO.cxx + itkFDFImageIOFactory.cxx +) + +itk_module_add_library(IOFDF ${IOFDF_SRC}) diff --git a/Modules/IO/IOFDF/src/itkFDFCommonImageIO.cxx b/Modules/IO/IOFDF/src/itkFDFCommonImageIO.cxx new file mode 100644 index 000000000000..f5c3e5bf7fe8 --- /dev/null +++ b/Modules/IO/IOFDF/src/itkFDFCommonImageIO.cxx @@ -0,0 +1,80 @@ +/*========================================================================= + * + * 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 "itkFDFCommonImageIO.h" + +namespace itk +{ + +// Remove a particular type of character from a string +std::string +RemoveCharacters(std::string line, char character) +{ + line.erase(std::remove(line.begin(), line.end(), character), line.end()); + return line; +} + +void +Tokenize(const std::string & str, std::vector & tokens, const std::string & delimiters) +{ + // Skip delimiters at beginning. + std::string::size_type lastPos = str.find_first_not_of(delimiters, 0); + // Find first "non-delimiter". + std::string::size_type pos = str.find_first_of(delimiters, lastPos); + + while (std::string::npos != pos || std::string::npos != lastPos) + { + // Found a token, add it to the vector. + tokens.push_back(str.substr(lastPos, pos - lastPos)); + // Skip delimiters. Note the "not_of" + lastPos = str.find_first_not_of(delimiters, pos); + // Find next "non-delimiter" + pos = str.find_first_of(delimiters, lastPos); + } +} + +std::string +ParseLine(std::string line) +{ + // strip * + line = RemoveCharacters(line, '*'); + line = RemoveCharacters(line, '\"'); + line = RemoveCharacters(line, '['); + line = RemoveCharacters(line, ']'); + + // Need to deal with space between {} + std::string::size_type startBracketPosition = line.find_first_of("{", 0); + std::string::size_type endBracketPosition = line.find_first_of("}", startBracketPosition); + + if (startBracketPosition != std::string::npos && endBracketPosition != std::string::npos) + { + std::string element = line.substr(startBracketPosition, endBracketPosition - startBracketPosition); + + // Find whitespace within {} and erase + std::string::size_type whiteSpacePosition = line.find_first_of(" ", startBracketPosition); + + while (whiteSpacePosition != std::string::npos) + { + line.erase(whiteSpacePosition, 1); + whiteSpacePosition = line.find_first_of(" ", whiteSpacePosition); + } + } + + return line; +} +} // namespace itk diff --git a/Modules/IO/IOFDF/src/itkFDFImageIO.cxx b/Modules/IO/IOFDF/src/itkFDFImageIO.cxx new file mode 100644 index 000000000000..c66ff0cd3f80 --- /dev/null +++ b/Modules/IO/IOFDF/src/itkFDFImageIO.cxx @@ -0,0 +1,594 @@ +/*========================================================================= + * + * 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 "itkFDFImageIO.h" +#include "itkFDFCommonImageIO.h" + +#include "itkByteSwapper.h" +#include "itkRGBPixel.h" +#include "itkRGBAPixel.h" +#include "vnl/vnl_matrix.h" +#include +#include + +namespace itk +{ + +bool +FDFImageIO::CanReadFile(const char * file) +{ + this->SetFileName(file); + + // First check the extension + std::string filename = file; + if (filename.empty()) + { + itkDebugMacro(<< "No filename specified."); + return false; + } + + bool extensionFound = false; + std::string::size_type FDFPos = filename.rfind(".fdf"); + if ((FDFPos != std::string::npos) && (FDFPos == filename.length() - 4)) + { + extensionFound = true; + } + + FDFPos = filename.rfind(".FDF"); + if ((FDFPos != std::string::npos) && (FDFPos == filename.length() - 4)) + { + extensionFound = true; + } + + if (!extensionFound) + { + itkDebugMacro(<< "The filename extension is not recognized"); + return false; + } + + std::ifstream inFile; + inFile.open(m_FileName.c_str(), std::ios::in | std::ios::binary); + if (!inFile) + { + itkDebugMacro(<< "File \"" << m_FileName << "\" cannot be opened."); + return false; + } + + return true; +} + +void +FDFImageIO::ReadImageInformation() +{ + if (!this->CanReadFile(m_FileName.c_str())) + RAISE_EXCEPTION(); + + std::string line; + std::vector tokens; + std::string type, name, value; + + ImageIORegion region; + + std::ifstream inFile(m_FileName.c_str(), std::ios::in | std::ios::binary); + + // Check if there was an error opening the file + if (!inFile) + { + std::cout << "Unable to open the file\n"; + RAISE_EXCEPTION(); + } + + this->SetFileTypeToBinary(); + + while (getline(inFile, line, '\n')) + { + if (line == "\0") + { + break; + } + + // Formats the lines in the FDF header such as removing whitespace between {} + line = ParseLine(line); + Tokenize(line, tokens, " ;"); + + if (tokens.size() >= 4) + { + type = tokens[0]; + name = tokens[1]; + // Re-join tokens[3..] so multi-word values (e.g. storage = + // "unsigned char") are not silently dropped by the size==4 + // gate. Header values like vector matrices are also space- + // separated; the StringToVector callers below parse them again. + value = tokens[3]; + for (size_t k = 4; k < tokens.size(); ++k) + { + value += ' '; + value += tokens[k]; + } + + if (name == "spatial_rank") + { + this->m_SpatialRank = value; + } + + if (name == "matrix") + { + std::vector dimensions; + StringToVector(value, dimensions); + + // Set the number of dimensions + if (this->GetNumberOfDimensions() < dimensions.size()) + { + this->SetNumberOfDimensions(dimensions.size()); + } + + ImageIORegion::SizeType size(dimensions.size()); + ImageIORegion::IndexType index(dimensions.size()); + + for (unsigned int i = 0; i < dimensions.size(); i++) + { + this->SetDimensions(i, dimensions[i]); + size[i] = dimensions[i]; + index[i] = 0; + } + + region.SetSize(size); + region.SetIndex(index); + this->SetIORegion(region); + } + + if (name == "orientation") + { + const unsigned numDim(this->GetNumberOfDimensions()); + std::vector orientation; + StringToVector(value, orientation); + + vnl_matrix testDirections(numDim, numDim); + + for (unsigned int i = 0; i < numDim; i++) + { + std::vector componentVector; + for (unsigned int j = 0; j < numDim; j++) + { + double val = orientation[i * numDim + j]; + testDirections(j, i) = val; + componentVector.push_back(val); + } + this->SetDirection(i, componentVector); + } + // check for degenerate dimensions. this will happen + // if the dimension of the image is 2 but the + // direction matrix in the file is 3x3. + // if direction matrix is degenerate, punt and set + // directions to identity + if (vnl_determinant(testDirections) == 0) + { + for (unsigned int i = 0; i < numDim; i++) + { + std::vector componentVector; + for (unsigned int j = 0; j < numDim; j++) + { + double val = i == j ? 1.0 : 0.0; + componentVector.push_back(val); + } + this->SetDirection(i, componentVector); + } + } + } + if (name == "span") + { + StringToVector(value, this->m_Span); + } + + if (name == "origin") + { + std::vector origin; + StringToVector(value, origin); + + if (this->GetNumberOfDimensions() < origin.size()) + { + this->SetNumberOfDimensions(origin.size()); + } + + for (unsigned int i = 0; i < origin.size(); i++) + { + this->SetOrigin(i, origin[i] / 10.0); + } + } + + if (name == "roi") + { + StringToVector(value, this->m_Roi); + } + + if (name == "location") + { + StringToVector(value, this->m_Location); + } + + if (name == "bigendian") + { + if (value == "0") + { + this->SetByteOrderToLittleEndian(); + } + else + { + this->SetByteOrderToBigEndian(); + } + } + + // Get the binary data type + if (name == "storage") + { + this->SetPixelType(IOPixelEnum::SCALAR); + + if (value == "double") + { + this->SetComponentType(IOComponentEnum::DOUBLE); + } + else if (value == "float") + { + this->SetComponentType(IOComponentEnum::FLOAT); + } + else if (value == "long") + { + this->SetComponentType(IOComponentEnum::LONG); + } + else if (value == "unsigned long") + { + this->SetComponentType(IOComponentEnum::ULONG); + } + else if (value == "int") + { + this->SetComponentType(IOComponentEnum::INT); + } + else if (value == "unsigned int") + { + this->SetComponentType(IOComponentEnum::UINT); + } + else if (value == "short") + { + this->SetComponentType(IOComponentEnum::SHORT); + } + else if (value == "unsigned short") + { + this->SetComponentType(IOComponentEnum::USHORT); + } + else if (value == "char") + { + this->SetComponentType(IOComponentEnum::CHAR); + } + else if (value == "unsigned char") + { + this->SetComponentType(IOComponentEnum::UCHAR); + } + else + { + itkExceptionMacro("Unknown component type: " << value); + } + } + + // Get the bits + if (name == "bits") + { + ConvertFromString(value, this->m_Bits); + } + + // Get the checksum + if (name == "checksum") + { + ConvertFromString(value, this->m_Checksum); + } + } + + tokens.clear(); + } + + inFile.seekg(0, std::ios::end); + long int fileSize = inFile.tellg(); + this->m_InputPosition = fileSize - this->GetImageSizeInBytes(); + + if (this->m_Roi.size() < this->GetNumberOfDimensions()) + { + itkExceptionMacro("FDF header missing 'roi' entries: have " << this->m_Roi.size() << ", need " + << this->GetNumberOfDimensions()); + } + for (unsigned int i = 0; i < this->GetNumberOfDimensions(); i++) + { + this->SetSpacing(i, (this->m_Roi[i] * 10) / this->GetDimensions(i)); + } +} + + +void +FDFImageIO::ReadVolume(void *) +{} + +// const std::type_info& FDFImageIO::GetPixelType() const +// { +// switch(m_PixelType) +// { +// case UCHAR: +// return typeid(unsigned char); +// case USHORT: +// return typeid(unsigned short); +// case CHAR: +// return typeid(char); +// case SHORT: +// return typeid(short); +// case UINT: +// return typeid(unsigned int); +// case INT: +// return typeid(int); +// case ULONG: +// return typeid(unsigned long); +// case LONG: +// return typeid(long); +// case FLOAT: +// return typeid(float); +// case DOUBLE: +// return typeid(double); +// case RGB: +// return typeid(RGBPixel); +// case RGBA: +// return typeid(RGBAPixel); +// default: +// { +// itkExceptionMacro ("Invalid type: " << m_PixelType << ", only unsigned char, unsigned short, RGB +// are allowed."); return this->ConvertToTypeInfo(m_PixelType); +// } +// case UNKNOWN: +// itkExceptionMacro ("Unknown pixel type: " << m_PixelType); +// } +// return typeid(ImageIOBase::UnknownType); +// } + +// unsigned int FDFImageIO::GetComponentSize() const +// { +// switch(m_PixelType) +// { +// case UCHAR: +// return sizeof(unsigned char); +// case USHORT: +// return sizeof(unsigned short); +// case CHAR: +// return sizeof(char); +// case SHORT: +// return sizeof(short); +// case UINT: +// return sizeof(unsigned int); +// case INT: +// return sizeof(int); +// case ULONG: +// return sizeof(unsigned long); +// case LONG: +// return sizeof(long); +// case FLOAT: +// return sizeof(float); +// case DOUBLE: +// return sizeof(double); +// case RGB: +// return sizeof(unsigned char); +// case RGBA: +// return sizeof(unsigned char); +// case UNKNOWNPIXELTYPE: +// default: +// { +// itkExceptionMacro ("Invalid type: " << m_PixelType +// << ", only unsigned char and unsigned short are allowed."); +// return 0; +// } +// } +// +// return 1; +// } + +void +FDFImageIO::Read(void * buffer) +{ + std::ifstream inFile(m_FileName.c_str(), std::ios::in | std::ios::binary); + + // Check if there was an error opening the file + if (!inFile) + { + RAISE_EXCEPTION(); + } + + inFile.seekg(this->m_InputPosition); + + if (!inFile) + { + RAISE_EXCEPTION(); + } + + auto * p = static_cast(buffer); + + inFile.read(p, this->GetImageSizeInBytes()); + + bool success = !inFile.bad(); + inFile.close(); + if (!success) + { + itkExceptionMacro("Error reading image data."); + } + + this->SwapBytesIfNecessary(buffer, this->GetImageSizeInPixels()); +} + + +FDFImageIO::FDFImageIO() = default; + +FDFImageIO::~FDFImageIO() = default; + +void +FDFImageIO::PrintSelf(std::ostream & os, Indent indent) const +{ + Superclass::PrintSelf(os, indent); + // os << indent << "PixelType " << m_PixelType << "\n"; + // os << indent << "Start of image in bytes from start of file " << this->m_InputPosition << "\n"; + // os << indent << "Number of pixels in image: " << this->GetImageSizeInPixels() << "\n"; + // os << indent << "Image size in bytes: " << this->GetImageSizeInBytes() << "\n"; + // os << indent << "Checksum: " << this->checksum << "\n"; + // os << indent << "Spatial Rank: " << this->spatial_rank << "\n"; + // os << indent << "Bits: " << this->bits << "\n"; + // os << indent; PrintVector(os, "Matrix", this->matrix); + // os << indent; PrintVector(os, "Location", this->location); + // os << indent; PrintVector(os, "ROI", this->roi); + // os << indent; PrintVector(os, "Span", this->span); +} + +bool +FDFImageIO::CanWriteFile(const char * /* name */) +{ + // not possible to write a fdf file + return false; +} + +void +FDFImageIO::SwapBytesIfNecessary(void * buffer, unsigned long numberOfPixels) +{ + switch (this->GetComponentType()) + { + case IOComponentEnum::CHAR: + { + if (this->m_ByteOrder == IOByteOrderEnum::LittleEndian) + { + ByteSwapper::SwapRangeFromSystemToLittleEndian((char *)buffer, numberOfPixels); + } + else if (this->m_ByteOrder == IOByteOrderEnum::BigEndian) + { + ByteSwapper::SwapRangeFromSystemToBigEndian((char *)buffer, numberOfPixels); + } + break; + } + case IOComponentEnum::FLOAT: + { + if (this->m_ByteOrder == IOByteOrderEnum::LittleEndian) + { + ByteSwapper::SwapRangeFromSystemToLittleEndian((float *)buffer, numberOfPixels); + } + else if (this->m_ByteOrder == IOByteOrderEnum::BigEndian) + { + ByteSwapper::SwapRangeFromSystemToBigEndian((float *)buffer, numberOfPixels); + } + break; + } + case IOComponentEnum::UCHAR: + { + if (this->m_ByteOrder == IOByteOrderEnum::LittleEndian) + { + ByteSwapper::SwapRangeFromSystemToLittleEndian((unsigned char *)buffer, numberOfPixels); + } + else if (this->m_ByteOrder == IOByteOrderEnum::BigEndian) + { + ByteSwapper::SwapRangeFromSystemToBigEndian((unsigned char *)buffer, numberOfPixels); + } + break; + } + case IOComponentEnum::SHORT: + { + if (this->m_ByteOrder == IOByteOrderEnum::LittleEndian) + { + ByteSwapper::SwapRangeFromSystemToLittleEndian((short *)buffer, numberOfPixels); + } + else if (this->m_ByteOrder == IOByteOrderEnum::BigEndian) + { + ByteSwapper::SwapRangeFromSystemToBigEndian((short *)buffer, numberOfPixels); + } + break; + } + case IOComponentEnum::USHORT: + { + if (this->m_ByteOrder == IOByteOrderEnum::LittleEndian) + { + ByteSwapper::SwapRangeFromSystemToLittleEndian((unsigned short *)buffer, numberOfPixels); + } + else if (this->m_ByteOrder == IOByteOrderEnum::BigEndian) + { + ByteSwapper::SwapRangeFromSystemToBigEndian((unsigned short *)buffer, numberOfPixels); + } + break; + } + case IOComponentEnum::INT: + { + if (this->m_ByteOrder == IOByteOrderEnum::LittleEndian) + { + ByteSwapper::SwapRangeFromSystemToLittleEndian((int *)buffer, numberOfPixels); + } + else if (this->m_ByteOrder == IOByteOrderEnum::BigEndian) + { + ByteSwapper::SwapRangeFromSystemToBigEndian((int *)buffer, numberOfPixels); + } + break; + } + case IOComponentEnum::UINT: + { + if (this->m_ByteOrder == IOByteOrderEnum::LittleEndian) + { + ByteSwapper::SwapRangeFromSystemToLittleEndian((unsigned int *)buffer, numberOfPixels); + } + else if (this->m_ByteOrder == IOByteOrderEnum::BigEndian) + { + ByteSwapper::SwapRangeFromSystemToBigEndian((unsigned int *)buffer, numberOfPixels); + } + break; + } + case IOComponentEnum::LONG: + { + if (this->m_ByteOrder == IOByteOrderEnum::LittleEndian) + { + ByteSwapper::SwapRangeFromSystemToLittleEndian((long *)buffer, numberOfPixels); + } + else if (this->m_ByteOrder == IOByteOrderEnum::BigEndian) + { + ByteSwapper::SwapRangeFromSystemToBigEndian((long *)buffer, numberOfPixels); + } + break; + } + case IOComponentEnum::ULONG: + { + if (this->m_ByteOrder == IOByteOrderEnum::LittleEndian) + { + ByteSwapper::SwapRangeFromSystemToLittleEndian((unsigned long *)buffer, numberOfPixels); + } + else if (this->m_ByteOrder == IOByteOrderEnum::BigEndian) + { + ByteSwapper::SwapRangeFromSystemToBigEndian((unsigned long *)buffer, numberOfPixels); + } + break; + } + default: + ExceptionObject exception(__FILE__, __LINE__); + exception.SetDescription("Pixel Type Unknown"); + throw exception; + } +} + +void +FDFImageIO::WriteImageInformation() +{ + // not possible to write a fdf file +} + +void +FDFImageIO::Write(const void * /* buffer */) +{ + // not possible to write a fdf file +} + +} // end namespace itk diff --git a/Modules/IO/IOFDF/src/itkFDFImageIOFactory.cxx b/Modules/IO/IOFDF/src/itkFDFImageIOFactory.cxx new file mode 100644 index 000000000000..4cd6f1cf669a --- /dev/null +++ b/Modules/IO/IOFDF/src/itkFDFImageIOFactory.cxx @@ -0,0 +1,64 @@ +/*========================================================================= + * + * 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 "itkFDFImageIOFactory.h" +#include "itkCreateObjectFunction.h" +#include "itkFDFImageIO.h" +#include "itkVersion.h" + + +namespace itk +{ +FDFImageIOFactory::FDFImageIOFactory() +{ + this->RegisterOverride( + "itkImageIOBase", "itkFDFImageIO", "FDF Image IO", true, CreateObjectFunction::New()); +} + +FDFImageIOFactory::~FDFImageIOFactory() = default; + +const char * +FDFImageIOFactory::GetITKSourceVersion() const +{ + return ITK_SOURCE_VERSION; +} + +const char * +FDFImageIOFactory::GetDescription() const +{ + return "FDF ImageIO Factory, allows the loading of Varian FDF images into Insight"; +} + + +// Undocumented API used to register during static initialization. +// DO NOT CALL DIRECTLY. + +static bool FDFImageIOFactoryHasBeenRegistered; + +void IOFDF_EXPORT +FDFImageIOFactoryRegister__Private() +{ + if (!FDFImageIOFactoryHasBeenRegistered) + { + FDFImageIOFactoryHasBeenRegistered = true; + FDFImageIOFactory::RegisterOneFactory(); + } +} + + +} // end namespace itk diff --git a/Modules/IO/IOFDF/test/CMakeLists.txt b/Modules/IO/IOFDF/test/CMakeLists.txt new file mode 100644 index 000000000000..dd8b99cab77a --- /dev/null +++ b/Modules/IO/IOFDF/test/CMakeLists.txt @@ -0,0 +1,13 @@ +itk_module_test() +set(IOFDFTests itkFDFImageIOTest.cxx) + +createtestdriver(IOFDF "${IOFDF-Test_LIBRARIES}" "${IOFDFTests}") + +itk_add_test( + NAME itkFDFImageIOTest + COMMAND + IOFDFTestDriver + itkFDFImageIOTest + ${ITK_TEST_OUTPUT_DIR} + ${CMAKE_CURRENT_LIST_DIR}/test.fdf +) diff --git a/Modules/IO/IOFDF/test/itkFDFImageIOTest.cxx b/Modules/IO/IOFDF/test/itkFDFImageIOTest.cxx new file mode 100644 index 000000000000..50cb957769e9 --- /dev/null +++ b/Modules/IO/IOFDF/test/itkFDFImageIOTest.cxx @@ -0,0 +1,61 @@ +/*========================================================================= + * + * 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 "itkImageFileReader.h" + +#include "itkFDFImageIOFactory.h" +#include "itkFDFImageIO.h" + +#include "itkImage.h" + +int +itkFDFImageIOTest(int argc, char * argv[]) +{ + if (argc < 3) + { + std::cerr << "Usage: itkFDFImageIO ; + using ReaderType = itk::ImageFileReader; + + // Register FDF Factory + itk::FDFImageIOFactory::RegisterOneFactory(); + + ReaderType::Pointer reader = ReaderType::New(); + + reader->SetFileName(argv[2]); + + try + { + reader->Update(); + } + catch (itk::ExceptionObject & exp) + { + std::cerr << "Exception caught" << std::endl; + std::cerr << exp << std::endl; + return EXIT_FAILURE; + } + ImageType::Pointer im = reader->GetOutput(); + + std::cerr << im->GetDirection() << std::endl << im->GetOrigin() << std::endl << im->GetSpacing() << std::endl; + + return EXIT_SUCCESS; +} diff --git a/Modules/IO/IOFDF/test/test.fdf b/Modules/IO/IOFDF/test/test.fdf new file mode 100644 index 000000000000..06a385f4d7bb Binary files /dev/null and b/Modules/IO/IOFDF/test/test.fdf differ diff --git a/Modules/IO/IOFDF/wrapping/CMakeLists.txt b/Modules/IO/IOFDF/wrapping/CMakeLists.txt new file mode 100644 index 000000000000..20a5cc43f489 --- /dev/null +++ b/Modules/IO/IOFDF/wrapping/CMakeLists.txt @@ -0,0 +1,3 @@ +itk_wrap_module(IOFDF) +itk_auto_load_submodules() +itk_end_wrap_module() diff --git a/Modules/IO/IOFDF/wrapping/itkFDFImageIO.wrap b/Modules/IO/IOFDF/wrapping/itkFDFImageIO.wrap new file mode 100644 index 000000000000..bdaad6f2abb2 --- /dev/null +++ b/Modules/IO/IOFDF/wrapping/itkFDFImageIO.wrap @@ -0,0 +1,2 @@ +itk_wrap_simple_class("itk::FDFImageIO" POINTER) +itk_wrap_simple_class("itk::FDFImageIOFactory" POINTER) diff --git a/Modules/Remote/IOFDF.remote.cmake b/Modules/Remote/IOFDF.remote.cmake deleted file mode 100644 index 5159e664d681..000000000000 --- a/Modules/Remote/IOFDF.remote.cmake +++ /dev/null @@ -1,50 +0,0 @@ -#-- # Grading Level Criteria Report -#-- EVALUATION DATE: 2020-03-01 -#-- EVALUATORS: [<>,<>] -#-- -#-- ## Compliance level 5 star (AKA ITK main modules, or remote modules that could become core modules) -#-- - [ ] Widespread community dependance -#-- - [ ] Above 90% code coverage -#-- - [ ] CI dashboards and testing monitored rigorously -#-- - [ ] Key API features are exposed in wrapping interface -#-- - [ ] All requirements of Levels 4,3,2,1 -#-- -#-- ## Compliance Level 4 star (Very high-quality code, perhaps small community dependance) -#-- - [ ] Meets all ITK code style standards -#-- - [ ] No external requirements beyond those needed by ITK proper -#-- - [ ] Builds and passes tests on all supported platforms within 1 month of each core tagged release -#-- - [ ] Windows Shared Library Build with Visual Studio -#-- - [ ] Mac with clang compiller -#-- - [ ] Linux with gcc compiler -#-- - [ ] Active developer community dedicated to maintaining code-base -#-- - [ ] 75% code coverage demonstrated for testing suite -#-- - [ ] Continuous integration testing performed -#-- - [ ] All requirements of Levels 3,2,1 -#-- -#-- ## Compliance Level 3 star (Quality beta code) -#-- - [ ] API | executable interface is considered mostly stable and feature complete -#-- - [ ] 10% C0-code coverage demonstrated for testing suite -#-- - [ ] Some tests exist and pass on at least some platform -#-- - [X] All requirements of Levels 2,1 -#-- -#-- ## Compliance Level 2 star (Alpha code feature API development or niche community/execution environment dependance ) -#-- - [X] Compiles for at least 1 niche set of execution envirionments, and perhaps others -#-- (may depend on specific external tools like a java environment, or specific external libraries to work ) -#-- - [X] All requirements of Levels 1 -#-- -#-- ## Compliance Level 1 star (Pre-alpha features under development and code of unknown quality) -#-- - [X] Code complies on at least 1 platform -#-- -#-- ## Compliance Level 0 star ( Code/Feature of known poor-quality or deprecated status ) -#-- - [ ] Code reviewed and explicitly identified as not recommended for use -#-- -#-- ### Please document here any justification for the criteria above -# Code style enforced by clang-format on 2020-02-19, and clang-tidy modernizations completed - -itk_fetch_module( - IOFDF - "FDFImageIO plugin for ITK. Authors Gleen Pierce/Nick Tustison/Kent Williams" - MODULE_COMPLIANCE_LEVEL 2 - GIT_REPOSITORY https://github.com/InsightSoftwareConsortium/ITKIOFDF.git - GIT_TAG 8e0bafbcabb1eca99a8fd6a3747971bb87b2da32 - ) diff --git a/pyproject.toml b/pyproject.toml index a2ee7171614a..cf4dc7d016b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,6 +63,7 @@ cmd = '''cmake -DModule_MGHIO:BOOL=ON -DModule_SplitComponents:BOOL=ON -DModule_IOMeshMZ3:BOOL=ON + -DModule_IOFDF:BOOL=ON -DITK_COMPUTER_MEMORY_SIZE:STRING=11 -DModule_StructuralSimilarity:BOOL=ON''' description = "Configure ITK for CI (with ccache compiler launcher)"