├── .clang-format ├── .gitattributes ├── .github └── workflows │ ├── build-test-package.yml │ └── clang-format-linter.yml ├── CMakeLists.txt ├── CTestConfig.cmake ├── LICENSE ├── README.rst ├── include ├── itkNormalizedCorrelationTwoImageToOneImageMetric.h ├── itkNormalizedCorrelationTwoImageToOneImageMetric.hxx ├── itkSiddonJacobsRayCastInterpolateImageFunction.h ├── itkSiddonJacobsRayCastInterpolateImageFunction.hxx ├── itkTwoImageToOneImageMetric.h ├── itkTwoImageToOneImageMetric.hxx ├── itkTwoProjectionImageRegistrationMethod.h └── itkTwoProjectionImageRegistrationMethod.hxx ├── itk-module.cmake ├── pyproject.toml ├── test ├── Baseline │ ├── BoxheadDRRFullDev1_G0_Reg-Author.tif.md5 │ ├── BoxheadDRRFullDev1_G90_Reg-Author.tif.md5 │ ├── BoxheadDRRFullDev1_G90_Reg.tif.md5 │ ├── boxheadDRRDev1_G0_Reg-Author.tif.md5 │ └── boxheadDRRDev1_G90_Reg-Author.tif.md5 ├── CMakeLists.txt ├── DicomSeriesReadNiftiImageWrite.cxx ├── Docker │ ├── Dockerfile │ ├── build.sh │ ├── run.sh │ └── test.sh ├── GetDRRSiddonJacobsRayTracing.cxx ├── Input │ ├── BoxheadCT.hdr.md5 │ ├── BoxheadCT.img.md5 │ ├── BoxheadCTFull.hdr.md5 │ ├── BoxheadCTFull.img.md5 │ ├── BoxheadDRRFullDev1_G0.tif.md5 │ ├── BoxheadDRRFullDev1_G90.tif.md5 │ ├── boxheadDRRDev1_G0.tif.md5 │ └── boxheadDRRDev1_G90.tif.md5 ├── ReadResampleWriteNifti.cxx └── TwoProjection2D3DRegistration.cxx └── wrapping ├── CMakeLists.txt ├── itkNormalizedCorrelationTwoImageToOneImageMetric.wrap ├── itkSiddonJacobsRayCastInterpolateImageFunction.wrap ├── itkTwoImageToOneImageMetric.wrap └── itkTwoProjectionImageRegistrationMethod.wrap /.clang-format: -------------------------------------------------------------------------------- 1 | ## This config file is only relevant for clang-format version 8.0.0 2 | ## 3 | ## Examples of each format style can be found on the in the clang-format documentation 4 | ## See: https://clang.llvm.org/docs/ClangFormatStyleOptions.html for details of each option 5 | ## 6 | ## The clang-format binaries can be downloaded as part of the clang binary distributions 7 | ## from https://releases.llvm.org/download.html 8 | ## 9 | ## Use the script Utilities/Maintenance/clang-format.bash to faciliate 10 | ## maintaining a consistent code style. 11 | ## 12 | ## EXAMPLE apply code style enforcement before commit: 13 | # Utilities/Maintenance/clang-format.bash --clang ${PATH_TO_CLANG_FORMAT_8.0.0} --modified 14 | ## EXAMPLE apply code style enforcement after commit: 15 | # Utilities/Maintenance/clang-format.bash --clang ${PATH_TO_CLANG_FORMAT_8.0.0} --last 16 | --- 17 | # This configuration requires clang-format version 8.0.0 exactly. 18 | BasedOnStyle: Mozilla 19 | Language: Cpp 20 | AccessModifierOffset: -2 21 | AlignAfterOpenBracket: Align 22 | AlignConsecutiveAssignments: false 23 | AlignConsecutiveDeclarations: true 24 | AlignEscapedNewlines: Right 25 | AlignOperands: true 26 | AlignTrailingComments: true 27 | # clang 9.0 AllowAllArgumentsOnNextLine: true 28 | # clang 9.0 AllowAllConstructorInitializersOnNextLine: true 29 | AllowAllParametersOfDeclarationOnNextLine: false 30 | AllowShortBlocksOnASingleLine: false 31 | AllowShortCaseLabelsOnASingleLine: false 32 | AllowShortFunctionsOnASingleLine: Inline 33 | # clang 9.0 AllowShortLambdasOnASingleLine: All 34 | # clang 9.0 features AllowShortIfStatementsOnASingleLine: Never 35 | AllowShortIfStatementsOnASingleLine: false 36 | AllowShortLoopsOnASingleLine: false 37 | AlwaysBreakAfterDefinitionReturnType: None 38 | AlwaysBreakAfterReturnType: All 39 | AlwaysBreakBeforeMultilineStrings: false 40 | AlwaysBreakTemplateDeclarations: Yes 41 | BinPackArguments: false 42 | BinPackParameters: false 43 | BreakBeforeBraces: Custom 44 | BraceWrapping: 45 | # clang 9.0 feature AfterCaseLabel: false 46 | AfterClass: true 47 | AfterControlStatement: true 48 | AfterEnum: true 49 | AfterFunction: true 50 | AfterNamespace: true 51 | AfterObjCDeclaration: true 52 | AfterStruct: true 53 | AfterUnion: true 54 | AfterExternBlock: true 55 | BeforeCatch: true 56 | BeforeElse: true 57 | ## This is the big change from historical ITK formatting! 58 | # Historically ITK used a style similar to https://en.wikipedia.org/wiki/Indentation_style#Whitesmiths_style 59 | # with indented braces, and not indented code. This style is very difficult to automatically 60 | # maintain with code beautification tools. Not indenting braces is more common among 61 | # formatting tools. 62 | IndentBraces: false 63 | SplitEmptyFunction: false 64 | SplitEmptyRecord: false 65 | SplitEmptyNamespace: false 66 | BreakBeforeBinaryOperators: None 67 | #clang 6.0 BreakBeforeInheritanceComma: true 68 | BreakInheritanceList: BeforeComma 69 | BreakBeforeTernaryOperators: true 70 | #clang 6.0 BreakConstructorInitializersBeforeComma: true 71 | BreakConstructorInitializers: BeforeComma 72 | BreakAfterJavaFieldAnnotations: false 73 | BreakStringLiterals: true 74 | ## The following line allows larger lines in non-documentation code 75 | ColumnLimit: 120 76 | CommentPragmas: '^ IWYU pragma:' 77 | CompactNamespaces: false 78 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 79 | ConstructorInitializerIndentWidth: 2 80 | ContinuationIndentWidth: 2 81 | Cpp11BracedListStyle: false 82 | DerivePointerAlignment: false 83 | DisableFormat: false 84 | ExperimentalAutoDetectBinPacking: false 85 | FixNamespaceComments: true 86 | ForEachMacros: 87 | - foreach 88 | - Q_FOREACH 89 | - BOOST_FOREACH 90 | IncludeBlocks: Preserve 91 | IncludeCategories: 92 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 93 | Priority: 2 94 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 95 | Priority: 3 96 | - Regex: '.*' 97 | Priority: 1 98 | IncludeIsMainRegex: '(Test)?$' 99 | IndentCaseLabels: true 100 | IndentPPDirectives: AfterHash 101 | IndentWidth: 2 102 | IndentWrappedFunctionNames: false 103 | JavaScriptQuotes: Leave 104 | JavaScriptWrapImports: true 105 | KeepEmptyLinesAtTheStartOfBlocks: true 106 | MacroBlockBegin: '' 107 | MacroBlockEnd: '' 108 | MaxEmptyLinesToKeep: 2 109 | NamespaceIndentation: None 110 | ObjCBinPackProtocolList: Auto 111 | ObjCBlockIndentWidth: 2 112 | ObjCSpaceAfterProperty: true 113 | ObjCSpaceBeforeProtocolList: false 114 | PenaltyBreakAssignment: 2 115 | PenaltyBreakBeforeFirstCallParameter: 19 116 | PenaltyBreakComment: 300 117 | ## The following line allows larger lines in non-documentation code 118 | PenaltyBreakFirstLessLess: 120 119 | PenaltyBreakString: 1000 120 | PenaltyBreakTemplateDeclaration: 10 121 | PenaltyExcessCharacter: 1000000 122 | PenaltyReturnTypeOnItsOwnLine: 200 123 | PointerAlignment: Middle 124 | ReflowComments: true 125 | # We may want to sort the includes as a separate pass 126 | SortIncludes: false 127 | # We may want to revisit this later 128 | SortUsingDeclarations: false 129 | SpaceAfterCStyleCast: false 130 | # SpaceAfterLogicalNot: false 131 | SpaceAfterTemplateKeyword: true 132 | SpaceBeforeAssignmentOperators: true 133 | SpaceBeforeCpp11BracedList: false 134 | SpaceBeforeCtorInitializerColon: true 135 | SpaceBeforeInheritanceColon: true 136 | SpaceBeforeParens: ControlStatements 137 | SpaceBeforeRangeBasedForLoopColon: true 138 | SpaceInEmptyParentheses: false 139 | SpacesBeforeTrailingComments: 1 140 | SpacesInAngles: false 141 | SpacesInContainerLiterals: false 142 | SpacesInCStyleCastParentheses: false 143 | SpacesInParentheses: false 144 | SpacesInSquareBrackets: false 145 | Standard: Cpp11 146 | StatementMacros: 147 | - Q_UNUSED 148 | - QT_REQUIRE_VERSION 149 | TabWidth: 2 150 | UseTab: Never 151 | ... 152 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Custom attribute to mark sources as using our C++/C code style. 2 | [attr]our-c-style whitespace=tab-in-indent,no-lf-at-eof hooks.style=KWStyle,clangformat 3 | 4 | *.c our-c-style 5 | *.h our-c-style 6 | *.cxx our-c-style 7 | *.hxx our-c-style 8 | *.txx our-c-style 9 | *.txt whitespace=tab-in-indent,no-lf-at-eof 10 | *.cmake whitespace=tab-in-indent,no-lf-at-eof 11 | 12 | # ExternalData content links must have LF newlines 13 | *.md5 crlf=input 14 | *.sha512 crlf=input 15 | -------------------------------------------------------------------------------- /.github/workflows/build-test-package.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Build, test, package 3 | 4 | on: [push,pull_request] 5 | 6 | jobs: 7 | cxx-build-workflow: 8 | uses: InsightSoftwareConsortium/ITKRemoteModuleBuildTestPackageAction/.github/workflows/build-test-cxx.yml@v5.4.0 9 | 10 | python-build-workflow: 11 | uses: InsightSoftwareConsortium/ITKRemoteModuleBuildTestPackageAction/.github/workflows/build-test-package-python.yml@v5.4.0 12 | with: 13 | test-notebooks: false 14 | secrets: 15 | pypi_password: ${{ secrets.pypi_password }} 16 | -------------------------------------------------------------------------------- /.github/workflows/clang-format-linter.yml: -------------------------------------------------------------------------------- 1 | name: clang-format linter 2 | 3 | on: [push,pull_request] 4 | 5 | jobs: 6 | lint: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v1 11 | with: 12 | fetch-depth: 1 13 | - uses: InsightSoftwareConsortium/ITKClangFormatLinterAction@master 14 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10.2) 2 | project(TwoProjectionRegistration) 3 | 4 | if(NOT ITK_SOURCE_DIR) 5 | find_package(ITK 5.1 REQUIRED) 6 | list(APPEND CMAKE_MODULE_PATH ${ITK_CMAKE_DIR}) 7 | include(ITKModuleExternal) 8 | else() 9 | set(ITK_DIR ${CMAKE_BINARY_DIR}) 10 | itk_module_impl() 11 | endif() 12 | -------------------------------------------------------------------------------- /CTestConfig.cmake: -------------------------------------------------------------------------------- 1 | set(CTEST_PROJECT_NAME "ITK") 2 | set(CTEST_NIGHTLY_START_TIME "1:00:00 UTC") 3 | 4 | set(CTEST_DROP_METHOD "https") 5 | set(CTEST_DROP_SITE "open.cdash.org") 6 | set(CTEST_DROP_LOCATION "/submit.php?project=Insight") 7 | set(CTEST_DROP_SITE_CDASH TRUE) 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | https://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | https://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ITKTwoProjectionRegistration 2 | ================================= 3 | 4 | .. image:: https://github.com/InsightSoftwareConsortium/ITKTwoProjectionRegistration/workflows/Build,%20test,%20package/badge.svg 5 | 6 | .. image:: https://img.shields.io/pypi/v/itk-twoprojectionregistration.svg 7 | :target: https://pypi.python.org/pypi/itk-twoprojectionregistration 8 | :alt: PyPI Version 9 | 10 | .. image:: https://img.shields.io/badge/License-Apache%202.0-blue.svg 11 | :target: https://github.com/InsightSoftwareConsortium/TwoProjectionRegistration/blob/master/LICENSE) 12 | :alt: License 13 | 14 | Overview 15 | -------- 16 | 17 | An `ITK `_-based implementation of intensity-based 2D/3D rigid 18 | image registration for patient setup assessment in external beam radiotherapy. 19 | The registration framework was designed to simultaneously register two 20 | projection images to a 3D image volume. The projection geometry was set up to 21 | simulate the X-ray imaging system that attached to a medical linear 22 | accelerator for cancer treatment. The normalized correlation was used as the 23 | similarity measure and the Powell's optimizer was used as the optimization 24 | method. Siddon-Jacobs fast ray-tracing algorithm was implemented to compute 25 | projection images from a 3D image volume. 26 | 27 | A more detailed description can be found in the Insight Journal article:: 28 | 29 | Wu, J. 30 | ITK-Based Implementation of Two-Projection 2D/3D Registration Method with an 31 | Application in Patient Setup for External Beam Radiotherapy 32 | The Insight Journal. July-December. 2010. 33 | https://hdl.handle.net/10380/3245 34 | https://www.insight-journal.org/browse/publication/784 35 | 36 | 37 | Installation 38 | ------------ 39 | 40 | Python 41 | ^^^^^^ 42 | 43 | Install the Python packages:: 44 | 45 | pip install itk-twoprojectionregistration 46 | 47 | C++ 48 | ^^^ 49 | 50 | Since ITK 4.10.0, this module is available in the ITK source tree as a Remote 51 | module. To enable it, set:: 52 | 53 | Module_TwoProjectionRegistration:BOOL=ON 54 | 55 | in ITK's CMake build configuration. 56 | 57 | 58 | License 59 | ------- 60 | 61 | This software is distributed under the Apache 2.0 license. Please see 62 | the *LICENSE* file for details. 63 | -------------------------------------------------------------------------------- /include/itkNormalizedCorrelationTwoImageToOneImageMetric.h: -------------------------------------------------------------------------------- 1 | /*========================================================================= 2 | * 3 | * Copyright NumFOCUS 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * https://www.apache.org/licenses/LICENSE-2.0.txt 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *=========================================================================*/ 18 | #ifndef itkNormalizedCorrelationTwoImageToOneImageMetric_h 19 | #define itkNormalizedCorrelationTwoImageToOneImageMetric_h 20 | 21 | #include "itkTwoImageToOneImageMetric.h" 22 | #include "itkCovariantVector.h" 23 | #include "itkPoint.h" 24 | 25 | 26 | namespace itk 27 | { 28 | /** \class NormalizedCorrelationTwoImageToOneImageMetric 29 | * \brief Computes similarity between two fixed images and one moving image 30 | * 31 | * This metric computes the correlation between pixels in the two fixed images 32 | * and pixels in the moving image. The spatial correspondance between 33 | * two fixed images and the moving image is established through a Transform. Pixel values are 34 | * taken from the fixed images, their positions are mapped to the moving 35 | * image and result in general in non-grid position on it. Values at these 36 | * non-grid position of the moving image are interpolated using user-selected 37 | * Interpolators. The correlation is normalized by the autocorrelations of both 38 | * the fixed and moving images. 39 | * 40 | * \ingroup RegistrationMetrics 41 | * \ingroup TwoProjectionRegistration 42 | */ 43 | template 44 | class NormalizedCorrelationTwoImageToOneImageMetric : public TwoImageToOneImageMetric 45 | { 46 | public: 47 | ITK_DISALLOW_COPY_AND_MOVE(NormalizedCorrelationTwoImageToOneImageMetric); 48 | 49 | /** Standard class type alias. */ 50 | using Self = NormalizedCorrelationTwoImageToOneImageMetric; 51 | using Superclass = TwoImageToOneImageMetric; 52 | 53 | using Pointer = SmartPointer; 54 | using ConstPointer = SmartPointer; 55 | 56 | /** Method for creation through the object factory. */ 57 | itkNewMacro(Self); 58 | 59 | /** Run-time type information (and related methods). */ 60 | itkTypeMacro(NormalizedCorrelationTwoImageToOneImageMetric, Object); 61 | 62 | 63 | /** Types transferred from the base class */ 64 | using RealType = typename Superclass::RealType; 65 | using TransformType = typename Superclass::TransformType; 66 | using TransformPointer = typename Superclass::TransformPointer; 67 | using TransformParametersType = typename Superclass::TransformParametersType; 68 | using TransformJacobianType = typename Superclass::TransformJacobianType; 69 | using GradientPixelType = typename Superclass::GradientPixelType; 70 | 71 | using MeasureType = typename Superclass::MeasureType; 72 | using DerivativeType = typename Superclass::DerivativeType; 73 | using FixedImageType = typename Superclass::FixedImageType; 74 | using MovingImageType = typename Superclass::MovingImageType; 75 | using FixedImageConstPointer = typename Superclass::FixedImageConstPointer; 76 | using MovingImageConstPointer = typename Superclass::MovingImageConstPointer; 77 | 78 | 79 | /** Get the derivatives of the match measure. */ 80 | void 81 | GetDerivative(const TransformParametersType & parameters, DerivativeType & Derivative) const override; 82 | 83 | /** Get the value for single valued optimizers. */ 84 | MeasureType 85 | GetValue(const TransformParametersType & parameters) const override; 86 | 87 | /** Get value and derivatives for multiple valued optimizers. */ 88 | void 89 | GetValueAndDerivative(const TransformParametersType & parameters, 90 | MeasureType & Value, 91 | DerivativeType & Derivative) const override; 92 | 93 | /** Set/Get SubtractMean boolean. If true, the sample mean is subtracted 94 | * from the sample values in the cross-correlation formula and 95 | * typically results in narrower valleys in the cost fucntion. 96 | * Default value is false. */ 97 | itkSetMacro(SubtractMean, bool); 98 | itkGetConstReferenceMacro(SubtractMean, bool); 99 | itkBooleanMacro(SubtractMean); 100 | 101 | protected: 102 | NormalizedCorrelationTwoImageToOneImageMetric(); 103 | ~NormalizedCorrelationTwoImageToOneImageMetric() override = default; 104 | void 105 | PrintSelf(std::ostream & os, Indent indent) const override; 106 | 107 | private: 108 | bool m_SubtractMean; 109 | }; 110 | 111 | } // end namespace itk 112 | 113 | #ifndef ITK_MANUAL_INSTANTIATION 114 | # include "itkNormalizedCorrelationTwoImageToOneImageMetric.hxx" 115 | #endif 116 | 117 | #endif 118 | -------------------------------------------------------------------------------- /include/itkNormalizedCorrelationTwoImageToOneImageMetric.hxx: -------------------------------------------------------------------------------- 1 | /*========================================================================= 2 | * 3 | * Copyright NumFOCUS 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * https://www.apache.org/licenses/LICENSE-2.0.txt 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *=========================================================================*/ 18 | #ifndef itkNormalizedCorrelationTwoImageToOneImageMetric_hxx 19 | #define itkNormalizedCorrelationTwoImageToOneImageMetric_hxx 20 | 21 | #include "itkImageRegionConstIteratorWithIndex.h" 22 | 23 | namespace itk 24 | { 25 | 26 | template 27 | NormalizedCorrelationTwoImageToOneImageMetric::NormalizedCorrelationTwoImageToOneImageMetric() 29 | { 30 | m_SubtractMean = false; 31 | } 32 | 33 | 34 | template 35 | typename NormalizedCorrelationTwoImageToOneImageMetric::MeasureType 36 | NormalizedCorrelationTwoImageToOneImageMetric::GetValue( 37 | const TransformParametersType & parameters) const 38 | { 39 | 40 | FixedImageConstPointer fixedImage1 = this->m_FixedImage1; 41 | 42 | if (!fixedImage1) 43 | { 44 | itkExceptionMacro(<< "Fixed image1 has not been assigned"); 45 | } 46 | 47 | FixedImageConstPointer fixedImage2 = this->m_FixedImage2; 48 | 49 | if (!fixedImage2) 50 | { 51 | itkExceptionMacro(<< "Fixed image2 has not been assigned"); 52 | } 53 | 54 | using FixedIteratorType = itk::ImageRegionConstIteratorWithIndex; 55 | 56 | using AccumulateType = typename NumericTraits::AccumulateType; 57 | 58 | 59 | // Calculate the measure value between fixed image 1 and the moving image 60 | 61 | FixedIteratorType ti1(fixedImage1, this->GetFixedImageRegion1()); 62 | 63 | typename FixedImageType::IndexType index; 64 | 65 | MeasureType measure1; 66 | 67 | this->m_NumberOfPixelsCounted = 0; 68 | 69 | this->SetTransformParameters(parameters); 70 | 71 | AccumulateType sff = NumericTraits::ZeroValue(); 72 | AccumulateType smm = NumericTraits::ZeroValue(); 73 | AccumulateType sfm = NumericTraits::ZeroValue(); 74 | AccumulateType sf = NumericTraits::ZeroValue(); 75 | AccumulateType sm = NumericTraits::ZeroValue(); 76 | typename Superclass::InputPointType inputPoint; 77 | 78 | while (!ti1.IsAtEnd()) 79 | { 80 | 81 | index = ti1.GetIndex(); 82 | 83 | fixedImage1->TransformIndexToPhysicalPoint(index, inputPoint); 84 | 85 | if (this->m_FixedImageMask1 && !this->m_FixedImageMask1->IsInsideInWorldSpace(inputPoint)) 86 | { 87 | ++ti1; 88 | continue; 89 | } 90 | 91 | // typename Superclass::OutputPointType transformedPoint = this->m_Transform->TransformPoint( inputPoint ); 92 | 93 | if (this->m_MovingImageMask && !this->m_MovingImageMask->IsInsideInWorldSpace(inputPoint)) 94 | { 95 | ++ti1; 96 | continue; 97 | } 98 | 99 | if (this->m_Interpolator1->IsInsideBuffer(inputPoint)) 100 | { 101 | const RealType movingValue = this->m_Interpolator1->Evaluate(inputPoint); 102 | const RealType fixedValue = ti1.Get(); 103 | sff += fixedValue * fixedValue; 104 | smm += movingValue * movingValue; 105 | sfm += fixedValue * movingValue; 106 | if (this->m_SubtractMean) 107 | { 108 | sf += fixedValue; 109 | sm += movingValue; 110 | } 111 | this->m_NumberOfPixelsCounted++; 112 | } 113 | 114 | ++ti1; 115 | } 116 | 117 | if (this->m_SubtractMean && this->m_NumberOfPixelsCounted > 0) 118 | { 119 | sff -= (sf * sf / this->m_NumberOfPixelsCounted); 120 | smm -= (sm * sm / this->m_NumberOfPixelsCounted); 121 | sfm -= (sf * sm / this->m_NumberOfPixelsCounted); 122 | } 123 | 124 | RealType denom = -1.0 * sqrt(sff * smm); 125 | 126 | if (this->m_NumberOfPixelsCounted > 0 && denom != 0.0) 127 | { 128 | measure1 = sfm / denom; 129 | } 130 | else 131 | { 132 | measure1 = NumericTraits::Zero; 133 | } 134 | 135 | 136 | // Calculate the measure value between fixed image 2 and the moving image 137 | 138 | FixedIteratorType ti2(fixedImage2, this->GetFixedImageRegion2()); 139 | 140 | MeasureType measure2; 141 | 142 | this->m_NumberOfPixelsCounted = 0; 143 | 144 | this->SetTransformParameters(parameters); 145 | 146 | sff = NumericTraits::ZeroValue(); 147 | smm = NumericTraits::ZeroValue(); 148 | sfm = NumericTraits::ZeroValue(); 149 | sf = NumericTraits::ZeroValue(); 150 | sm = NumericTraits::ZeroValue(); 151 | 152 | while (!ti2.IsAtEnd()) 153 | { 154 | 155 | index = ti2.GetIndex(); 156 | 157 | // typename Superclass::InputPointType inputPoint; 158 | fixedImage2->TransformIndexToPhysicalPoint(index, inputPoint); 159 | 160 | if (this->m_FixedImageMask2 && !this->m_FixedImageMask2->IsInsideInWorldSpace(inputPoint)) 161 | { 162 | ++ti2; 163 | continue; 164 | } 165 | 166 | // typename Superclass::OutputPointType transformedPoint = this->m_Transform->TransformPoint( inputPoint ); 167 | 168 | if (this->m_MovingImageMask && !this->m_MovingImageMask->IsInsideInWorldSpace(inputPoint)) 169 | { 170 | ++ti2; 171 | continue; 172 | } 173 | 174 | if (this->m_Interpolator2->IsInsideBuffer(inputPoint)) 175 | { 176 | const RealType movingValue = this->m_Interpolator2->Evaluate(inputPoint); 177 | const RealType fixedValue = ti2.Get(); 178 | sff += fixedValue * fixedValue; 179 | smm += movingValue * movingValue; 180 | sfm += fixedValue * movingValue; 181 | if (this->m_SubtractMean) 182 | { 183 | sf += fixedValue; 184 | sm += movingValue; 185 | } 186 | this->m_NumberOfPixelsCounted++; 187 | } 188 | 189 | ++ti2; 190 | } 191 | 192 | if (this->m_SubtractMean && this->m_NumberOfPixelsCounted > 0) 193 | { 194 | sff -= (sf * sf / this->m_NumberOfPixelsCounted); 195 | smm -= (sm * sm / this->m_NumberOfPixelsCounted); 196 | sfm -= (sf * sm / this->m_NumberOfPixelsCounted); 197 | } 198 | 199 | denom = -1.0 * sqrt(sff * smm); 200 | 201 | if (this->m_NumberOfPixelsCounted > 0 && denom != 0.0) 202 | { 203 | measure2 = sfm / denom; 204 | } 205 | else 206 | { 207 | measure2 = NumericTraits::Zero; 208 | } 209 | 210 | return (measure1 + measure2) / 2.0; 211 | } 212 | 213 | 214 | template 215 | void 216 | NormalizedCorrelationTwoImageToOneImageMetric::GetDerivative( 217 | const TransformParametersType & itkNotUsed(parameters), 218 | DerivativeType & itkNotUsed(derivative)) const 219 | { 220 | // under construction 221 | } 222 | 223 | 224 | template 225 | void 226 | NormalizedCorrelationTwoImageToOneImageMetric::GetValueAndDerivative( 227 | const TransformParametersType & itkNotUsed(parameters), 228 | MeasureType & itkNotUsed(value), 229 | DerivativeType & itkNotUsed(derivative)) const 230 | { 231 | // under construction 232 | } 233 | 234 | 235 | template 236 | void 237 | NormalizedCorrelationTwoImageToOneImageMetric::PrintSelf(std::ostream & os, 238 | Indent indent) const 239 | { 240 | Superclass::PrintSelf(os, indent); 241 | os << indent << "SubtractMean: " << m_SubtractMean << std::endl; 242 | } 243 | 244 | } // end namespace itk 245 | 246 | 247 | #endif 248 | -------------------------------------------------------------------------------- /include/itkSiddonJacobsRayCastInterpolateImageFunction.h: -------------------------------------------------------------------------------- 1 | /*========================================================================= 2 | * 3 | * Copyright NumFOCUS 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * https://www.apache.org/licenses/LICENSE-2.0.txt 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *=========================================================================*/ 18 | /*========================================================================= 19 | Calculate DRR from a CT dataset using incremental ray-tracing algorithm 20 | The algorithm was initially proposed by Robert Siddon and improved by 21 | Filip Jacobs etc. 22 | 23 | ------------------------------------------------------------------------- 24 | References: 25 | 26 | R. L. Siddon, "Fast calculation of the exact radiological path for a 27 | threedimensional CT array," Medical Physics 12, 252-55 (1985). 28 | 29 | F. Jacobs, E. Sundermann, B. De Sutter, M. Christiaens, and I. Lemahieu, 30 | "A fast algorithm to calculate the exact radiological path through a pixel 31 | or voxel space," Journal of Computing and Information Technology ? 32 | CIT 6, 89-94 (1998). 33 | 34 | =========================================================================*/ 35 | #ifndef itkSiddonJacobsRayCastInterpolateImageFunction_h 36 | #define itkSiddonJacobsRayCastInterpolateImageFunction_h 37 | 38 | #include "itkInterpolateImageFunction.h" 39 | #include "itkTransform.h" 40 | #include "itkVector.h" 41 | #include "itkEuler3DTransform.h" 42 | 43 | namespace itk 44 | { 45 | 46 | /** \class SiddonJacobsRayCastInterpolateImageFunction 47 | * \brief Projective interpolation of an image at specified positions. 48 | * 49 | * SiddonJacobsRayCastInterpolateImageFunction casts rays through a 3-dimensional 50 | * image 51 | * \warning This interpolator works for 3-dimensional images only. 52 | * 53 | * \ingroup ImageFunctions 54 | * \ingroup TwoProjectionRegistration 55 | */ 56 | template 57 | class SiddonJacobsRayCastInterpolateImageFunction : public InterpolateImageFunction 58 | { 59 | public: 60 | ITK_DISALLOW_COPY_AND_MOVE(SiddonJacobsRayCastInterpolateImageFunction); 61 | 62 | /** Standard class type alias. */ 63 | using Self = SiddonJacobsRayCastInterpolateImageFunction; 64 | using Superclass = InterpolateImageFunction; 65 | using Pointer = SmartPointer; 66 | using ConstPointer = SmartPointer; 67 | 68 | /** Constants for the image dimensions */ 69 | static constexpr unsigned int InputImageDimension = TInputImage::ImageDimension; 70 | 71 | 72 | using TransformType = Euler3DTransform; 73 | 74 | using TransformPointer = typename TransformType::Pointer; 75 | using InputPointType = typename TransformType::InputPointType; 76 | using OutputPointType = typename TransformType::OutputPointType; 77 | using TransformParametersType = typename TransformType::ParametersType; 78 | using TransformJacobianType = typename TransformType::JacobianType; 79 | 80 | using PixelType = typename Superclass::InputPixelType; 81 | 82 | using SizeType = typename TInputImage::SizeType; 83 | 84 | using DirectionType = Vector; 85 | 86 | /** Type of the Interpolator Base class */ 87 | using InterpolatorType = InterpolateImageFunction; 88 | 89 | using InterpolatorPointer = typename InterpolatorType::Pointer; 90 | 91 | 92 | /** Run-time type information (and related methods). */ 93 | itkTypeMacro(SiddonJacobsRayCastInterpolateImageFunction, InterpolateImageFunction); 94 | 95 | /** Method for creation through the object factory. */ 96 | itkNewMacro(Self); 97 | 98 | /** OutputType type alias support. */ 99 | using OutputType = typename Superclass::OutputType; 100 | 101 | /** InputImageType type alias support. */ 102 | using InputImageType = typename Superclass::InputImageType; 103 | 104 | /** InputImageConstPointer type alias support. */ 105 | using InputImageConstPointer = typename Superclass::InputImageConstPointer; 106 | 107 | /** RealType type alias support. */ 108 | using RealType = typename Superclass::RealType; 109 | 110 | /** Dimension underlying input image. */ 111 | static constexpr unsigned int ImageDimension = Superclass::ImageDimension; 112 | 113 | /** Point type alias support. */ 114 | using PointType = typename Superclass::PointType; 115 | 116 | /** Index type alias support. */ 117 | using IndexType = typename Superclass::IndexType; 118 | 119 | /** ContinuousIndex type alias support. */ 120 | using ContinuousIndexType = typename Superclass::ContinuousIndexType; 121 | 122 | 123 | /** \brief 124 | * Interpolate the image at a point position. 125 | * 126 | * Returns the interpolated image intensity at a 127 | * specified point position. No bounds checking is done. 128 | * The point is assume to lie within the image buffer. 129 | * 130 | * ImageFunction::IsInsideBuffer() can be used to check bounds before 131 | * calling the method. 132 | */ 133 | OutputType 134 | Evaluate(const PointType & point) const override; 135 | 136 | /** Interpolate the image at a continuous index position 137 | * 138 | * Returns the interpolated image intensity at a 139 | * specified index position. No bounds checking is done. 140 | * The point is assume to lie within the image buffer. 141 | * 142 | * Subclasses must override this method. 143 | * 144 | * ImageFunction::IsInsideBuffer() can be used to check bounds before 145 | * calling the method. 146 | */ 147 | OutputType 148 | EvaluateAtContinuousIndex(const ContinuousIndexType & index) const override; 149 | 150 | virtual void 151 | Initialize(); 152 | 153 | /** Connect the Transform. */ 154 | itkSetObjectMacro(Transform, TransformType); 155 | /** Get a pointer to the Transform. */ 156 | itkGetConstObjectMacro(Transform, TransformType); 157 | 158 | /** Set and get the focal point to isocenter distance in mm */ 159 | itkSetMacro(FocalPointToIsocenterDistance, double); 160 | itkGetMacro(FocalPointToIsocenterDistance, double); 161 | 162 | /** Set and get the Lianc grantry rotation angle in radians */ 163 | itkSetMacro(ProjectionAngle, double); 164 | itkGetMacro(ProjectionAngle, double); 165 | 166 | /** Set and get the Threshold */ 167 | itkSetMacro(Threshold, double); 168 | itkGetMacro(Threshold, double); 169 | 170 | /** Check if a point is inside the image buffer. 171 | * \warning For efficiency, no validity checking of 172 | * the input image pointer is done. */ 173 | inline bool 174 | IsInsideBuffer(const PointType &) const override 175 | { 176 | return true; 177 | } 178 | bool 179 | IsInsideBuffer(const ContinuousIndexType &) const override 180 | { 181 | return true; 182 | } 183 | bool 184 | IsInsideBuffer(const IndexType &) const override 185 | { 186 | return true; 187 | } 188 | 189 | #if !defined(ITKV4_COMPATIBILITY) 190 | SizeType 191 | GetRadius() const override 192 | { 193 | const InputImageType * input = this->GetInputImage(); 194 | if (!input) 195 | { 196 | itkExceptionMacro("Input image required!"); 197 | } 198 | return input->GetLargestPossibleRegion().GetSize(); 199 | } 200 | #endif 201 | 202 | protected: 203 | SiddonJacobsRayCastInterpolateImageFunction(); 204 | 205 | ~SiddonJacobsRayCastInterpolateImageFunction() override = default; 206 | 207 | void 208 | PrintSelf(std::ostream & os, Indent indent) const override; 209 | 210 | /// Transformation used to calculate the new focal point position 211 | TransformPointer m_Transform; // Displacement of the volume 212 | // Overall inverse transform used to calculate the ray position in the input space 213 | TransformPointer m_InverseTransform; 214 | 215 | // The threshold above which voxels along the ray path are integrated 216 | double m_Threshold; 217 | double m_FocalPointToIsocenterDistance; // Focal point to isocenter distance 218 | double m_ProjectionAngle; // Linac gantry rotation angle in radians 219 | 220 | private: 221 | void 222 | ComputeInverseTransform() const; 223 | TransformPointer m_GantryRotTransform; // Gantry rotation transform 224 | TransformPointer m_CamShiftTransform; // Camera shift transform camRotTransform 225 | TransformPointer m_CamRotTransform; // Camera rotation transform 226 | TransformPointer m_ComposedTransform; // Composed transform 227 | PointType m_SourcePoint; // Coordinate of the source in the standard Z projection geometry 228 | PointType m_SourceWorld; // Coordinate of the source in the world coordinate system 229 | }; 230 | 231 | } // namespace itk 232 | 233 | #ifndef ITK_MANUAL_INSTANTIATION 234 | # include "itkSiddonJacobsRayCastInterpolateImageFunction.hxx" 235 | #endif 236 | 237 | #endif 238 | -------------------------------------------------------------------------------- /include/itkSiddonJacobsRayCastInterpolateImageFunction.hxx: -------------------------------------------------------------------------------- 1 | /*========================================================================= 2 | * 3 | * Copyright NumFOCUS 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * https://www.apache.org/licenses/LICENSE-2.0.txt 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *=========================================================================*/ 18 | /*========================================================================= 19 | Calculate DRR from a CT dataset using incremental ray-tracing algorithm 20 | The algorithm was initially proposed by Robert Siddon and improved by 21 | Filip Jacobs etc. 22 | 23 | ------------------------------------------------------------------------- 24 | References: 25 | 26 | R. L. Siddon, "Fast calculation of the exact radiological path for a 27 | threedimensional CT array," Medical Physics 12, 252-55 (1985). 28 | 29 | F. Jacobs, E. Sundermann, B. De Sutter, M. Christiaens, and I. Lemahieu, 30 | "A fast algorithm to calculate the exact radiological path through a pixel 31 | or voxel space," Journal of Computing and Information Technology ? 32 | CIT 6, 89-94 (1998). 33 | 34 | =========================================================================*/ 35 | 36 | #ifndef itkSiddonJacobsRayCastInterpolateImageFunction_hxx 37 | #define itkSiddonJacobsRayCastInterpolateImageFunction_hxx 38 | 39 | 40 | #include "itkMath.h" 41 | #include 42 | 43 | namespace itk 44 | { 45 | 46 | template 47 | SiddonJacobsRayCastInterpolateImageFunction::SiddonJacobsRayCastInterpolateImageFunction() 48 | { 49 | m_FocalPointToIsocenterDistance = 1000.; // Focal point to isocenter distance in mm. 50 | m_ProjectionAngle = 0.; // Angle in radians betweeen projection central axis and reference axis 51 | m_Threshold = 0.; // Intensity threshold, below which is ignored. 52 | 53 | m_SourcePoint[0] = 0.; 54 | m_SourcePoint[1] = 0.; 55 | m_SourcePoint[2] = 0.; 56 | 57 | m_InverseTransform = TransformType::New(); 58 | m_InverseTransform->SetComputeZYX(true); 59 | 60 | m_ComposedTransform = TransformType::New(); 61 | m_ComposedTransform->SetComputeZYX(true); 62 | 63 | m_GantryRotTransform = TransformType::New(); 64 | m_GantryRotTransform->SetComputeZYX(true); 65 | m_GantryRotTransform->SetIdentity(); 66 | 67 | m_CamShiftTransform = TransformType::New(); 68 | m_CamShiftTransform->SetComputeZYX(true); 69 | m_CamShiftTransform->SetIdentity(); 70 | 71 | m_CamRotTransform = TransformType::New(); 72 | m_CamRotTransform->SetComputeZYX(true); 73 | m_CamRotTransform->SetIdentity(); 74 | // constant for converting degrees into radians 75 | const float dtr = (atan(1.0) * 4.0) / 180.0; 76 | m_CamRotTransform->SetRotation(dtr * (-90.0), 0.0, 0.0); 77 | 78 | m_Threshold = 0; 79 | } 80 | 81 | 82 | template 83 | void 84 | SiddonJacobsRayCastInterpolateImageFunction::PrintSelf(std::ostream & os, Indent indent) const 85 | { 86 | this->Superclass::PrintSelf(os, indent); 87 | 88 | os << indent << "Threshold: " << m_Threshold << std::endl; 89 | os << indent << "Transform: " << m_Transform.GetPointer() << std::endl; 90 | } 91 | 92 | 93 | template 94 | typename SiddonJacobsRayCastInterpolateImageFunction::OutputType 95 | SiddonJacobsRayCastInterpolateImageFunction::Evaluate(const PointType & point) const 96 | { 97 | float rayVector[3]; 98 | IndexType cIndex; 99 | 100 | PointType drrPixelWorld; // Coordinate of a DRR pixel in the world coordinate system 101 | OutputType pixval; 102 | 103 | 104 | float firstIntersection[3]; 105 | float alphaX1, alphaXN, alphaXmin, alphaXmax; 106 | float alphaY1, alphaYN, alphaYmin, alphaYmax; 107 | float alphaZ1, alphaZN, alphaZmin, alphaZmax; 108 | float alphaMin, alphaMax; 109 | float alphaX, alphaY, alphaZ, alphaCmin, alphaCminPrev; 110 | float alphaUx, alphaUy, alphaUz; 111 | float alphaIntersectionUp[3], alphaIntersectionDown[3]; 112 | float d12, value; 113 | float firstIntersectionIndex[3]; 114 | int firstIntersectionIndexUp[3], firstIntersectionIndexDown[3]; 115 | int iU, jU, kU; 116 | 117 | 118 | // Min/max values of the output pixel type AND these values 119 | // represented as the output type of the interpolator 120 | const OutputType minOutputValue = itk::NumericTraits::NonpositiveMin(); 121 | const OutputType maxOutputValue = itk::NumericTraits::max(); 122 | 123 | // If the volume was shifted, recalculate the overall inverse transform 124 | unsigned long int interpMTime = this->GetMTime(); 125 | unsigned long int vTransformMTime = m_Transform->GetMTime(); 126 | 127 | if (interpMTime < vTransformMTime) 128 | { 129 | this->ComputeInverseTransform(); 130 | // The m_SourceWorld should be computed here to avoid the repeatedly calculation 131 | // for each projection ray. However, we are in a const function, which prohibits 132 | // the modification of class member variables. So the world coordiate of the source 133 | // point is calculated for each ray as below. Performance improvement may be made 134 | // by using a static variable? 135 | // m_SourceWorld = m_InverseTransform->TransformPoint(m_SourcePoint); 136 | } 137 | 138 | PointType SourceWorld = m_InverseTransform->TransformPoint(m_SourcePoint); 139 | 140 | // Get ths input pointers 141 | InputImageConstPointer inputPtr = this->GetInputImage(); 142 | 143 | typename InputImageType::SizeType sizeCT; 144 | typename InputImageType::RegionType regionCT; 145 | typename InputImageType::SpacingType ctPixelSpacing; 146 | typename InputImageType::PointType ctOrigin; 147 | 148 | ctPixelSpacing = inputPtr->GetSpacing(); 149 | ctOrigin = inputPtr->GetOrigin(); 150 | regionCT = inputPtr->GetLargestPossibleRegion(); 151 | sizeCT = regionCT.GetSize(); 152 | 153 | drrPixelWorld = m_InverseTransform->TransformPoint(point); 154 | 155 | 156 | // The following is the Siddon-Jacob fast ray-tracing algorithm 157 | 158 | rayVector[0] = drrPixelWorld[0] - SourceWorld[0]; 159 | rayVector[1] = drrPixelWorld[1] - SourceWorld[1]; 160 | rayVector[2] = drrPixelWorld[2] - SourceWorld[2]; 161 | 162 | /* Calculate the parametric values of the first and the last 163 | intersection points of the ray with the X, Y, and Z-planes that 164 | define the CT volume. */ 165 | if (rayVector[0] != 0) 166 | { 167 | alphaX1 = (0.0 - SourceWorld[0]) / rayVector[0]; 168 | alphaXN = (sizeCT[0] * ctPixelSpacing[0] - SourceWorld[0]) / rayVector[0]; 169 | alphaXmin = std::min(alphaX1, alphaXN); 170 | alphaXmax = std::max(alphaX1, alphaXN); 171 | } 172 | else 173 | { 174 | alphaXmin = -2; 175 | alphaXmax = 2; 176 | } 177 | 178 | if (rayVector[1] != 0) 179 | { 180 | alphaY1 = (0.0 - SourceWorld[1]) / rayVector[1]; 181 | alphaYN = (sizeCT[1] * ctPixelSpacing[1] - SourceWorld[1]) / rayVector[1]; 182 | alphaYmin = std::min(alphaY1, alphaYN); 183 | alphaYmax = std::max(alphaY1, alphaYN); 184 | } 185 | else 186 | { 187 | alphaYmin = -2; 188 | alphaYmax = 2; 189 | } 190 | 191 | if (rayVector[2] != 0) 192 | { 193 | alphaZ1 = (0.0 - SourceWorld[2]) / rayVector[2]; 194 | alphaZN = (sizeCT[2] * ctPixelSpacing[2] - SourceWorld[2]) / rayVector[2]; 195 | alphaZmin = std::min(alphaZ1, alphaZN); 196 | alphaZmax = std::max(alphaZ1, alphaZN); 197 | } 198 | else 199 | { 200 | alphaZmin = -2; 201 | alphaZmax = 2; 202 | } 203 | 204 | /* Get the very first and the last alpha values when the ray 205 | intersects with the CT volume. */ 206 | alphaMin = std::max(std::max(alphaXmin, alphaYmin), alphaZmin); 207 | alphaMax = std::min(std::min(alphaXmax, alphaYmax), alphaZmax); 208 | 209 | /* Calculate the parametric values of the first intersection point 210 | of the ray with the X, Y, and Z-planes after the ray entered the 211 | CT volume. */ 212 | 213 | firstIntersection[0] = SourceWorld[0] + alphaMin * rayVector[0]; 214 | firstIntersection[1] = SourceWorld[1] + alphaMin * rayVector[1]; 215 | firstIntersection[2] = SourceWorld[2] + alphaMin * rayVector[2]; 216 | 217 | /* Transform world coordinate to the continuous index of the CT volume*/ 218 | firstIntersectionIndex[0] = firstIntersection[0] / ctPixelSpacing[0]; 219 | firstIntersectionIndex[1] = firstIntersection[1] / ctPixelSpacing[1]; 220 | firstIntersectionIndex[2] = firstIntersection[2] / ctPixelSpacing[2]; 221 | 222 | firstIntersectionIndexUp[0] = (int)ceil(firstIntersectionIndex[0]); 223 | firstIntersectionIndexUp[1] = (int)ceil(firstIntersectionIndex[1]); 224 | firstIntersectionIndexUp[2] = (int)ceil(firstIntersectionIndex[2]); 225 | 226 | firstIntersectionIndexDown[0] = (int)floor(firstIntersectionIndex[0]); 227 | firstIntersectionIndexDown[1] = (int)floor(firstIntersectionIndex[1]); 228 | firstIntersectionIndexDown[2] = (int)floor(firstIntersectionIndex[2]); 229 | 230 | 231 | if (rayVector[0] == 0) 232 | { 233 | alphaX = 2; 234 | } 235 | else 236 | { 237 | alphaIntersectionUp[0] = (firstIntersectionIndexUp[0] * ctPixelSpacing[0] - SourceWorld[0]) / rayVector[0]; 238 | alphaIntersectionDown[0] = (firstIntersectionIndexDown[0] * ctPixelSpacing[0] - SourceWorld[0]) / rayVector[0]; 239 | alphaX = std::max(alphaIntersectionUp[0], alphaIntersectionDown[0]); 240 | } 241 | 242 | if (rayVector[1] == 0) 243 | { 244 | alphaY = 2; 245 | } 246 | else 247 | { 248 | alphaIntersectionUp[1] = (firstIntersectionIndexUp[1] * ctPixelSpacing[1] - SourceWorld[1]) / rayVector[1]; 249 | alphaIntersectionDown[1] = (firstIntersectionIndexDown[1] * ctPixelSpacing[1] - SourceWorld[1]) / rayVector[1]; 250 | alphaY = std::max(alphaIntersectionUp[1], alphaIntersectionDown[1]); 251 | } 252 | 253 | if (rayVector[2] == 0) 254 | { 255 | alphaZ = 2; 256 | } 257 | else 258 | { 259 | alphaIntersectionUp[2] = (firstIntersectionIndexUp[2] * ctPixelSpacing[2] - SourceWorld[2]) / rayVector[2]; 260 | alphaIntersectionDown[2] = (firstIntersectionIndexDown[2] * ctPixelSpacing[2] - SourceWorld[2]) / rayVector[2]; 261 | alphaZ = std::max(alphaIntersectionUp[2], alphaIntersectionDown[2]); 262 | } 263 | 264 | /* Calculate alpha incremental values when the ray intercepts with x, y, and z-planes */ 265 | if (rayVector[0] != 0) 266 | { 267 | alphaUx = ctPixelSpacing[0] / itk::Math::abs(rayVector[0]); 268 | } 269 | else 270 | { 271 | alphaUx = 999; 272 | } 273 | if (rayVector[1] != 0) 274 | { 275 | alphaUy = ctPixelSpacing[1] / itk::Math::abs(rayVector[1]); 276 | } 277 | else 278 | { 279 | alphaUy = 999; 280 | } 281 | if (rayVector[2] != 0) 282 | { 283 | alphaUz = ctPixelSpacing[2] / itk::Math::abs(rayVector[2]); 284 | } 285 | else 286 | { 287 | alphaUz = 999; 288 | } 289 | /* Calculate voxel index incremental values along the ray path. */ 290 | if (SourceWorld[0] < drrPixelWorld[0]) 291 | { 292 | iU = 1; 293 | } 294 | else 295 | { 296 | iU = -1; 297 | } 298 | if (SourceWorld[1] < drrPixelWorld[1]) 299 | { 300 | jU = 1; 301 | } 302 | else 303 | { 304 | jU = -1; 305 | } 306 | 307 | if (SourceWorld[2] < drrPixelWorld[2]) 308 | { 309 | kU = 1; 310 | } 311 | else 312 | { 313 | kU = -1; 314 | } 315 | 316 | d12 = 0.0; /* Initialize the sum of the voxel intensities along the ray path to zero. */ 317 | 318 | 319 | /* Initialize the current ray position. */ 320 | alphaCmin = std::min(std::min(alphaX, alphaY), alphaZ); 321 | 322 | /* Initialize the current voxel index. */ 323 | cIndex[0] = firstIntersectionIndexDown[0]; 324 | cIndex[1] = firstIntersectionIndexDown[1]; 325 | cIndex[2] = firstIntersectionIndexDown[2]; 326 | 327 | while (alphaCmin < alphaMax) /* Check if the ray is still in the CT volume */ 328 | { 329 | /* Store the current ray position */ 330 | alphaCminPrev = alphaCmin; 331 | 332 | if ((alphaX <= alphaY) && (alphaX <= alphaZ)) 333 | { 334 | /* Current ray front intercepts with x-plane. Update alphaX. */ 335 | alphaCmin = alphaX; 336 | cIndex[0] = cIndex[0] + iU; 337 | alphaX = alphaX + alphaUx; 338 | } 339 | else if ((alphaY <= alphaX) && (alphaY <= alphaZ)) 340 | { 341 | /* Current ray front intercepts with y-plane. Update alphaY. */ 342 | alphaCmin = alphaY; 343 | cIndex[1] = cIndex[1] + jU; 344 | alphaY = alphaY + alphaUy; 345 | } 346 | else 347 | { 348 | /* Current ray front intercepts with z-plane. Update alphaZ. */ 349 | alphaCmin = alphaZ; 350 | cIndex[2] = cIndex[2] + kU; 351 | alphaZ = alphaZ + alphaUz; 352 | } 353 | 354 | if ((cIndex[0] >= 0) && (cIndex[0] < static_cast(sizeCT[0])) && (cIndex[1] >= 0) && 355 | (cIndex[1] < static_cast(sizeCT[1])) && (cIndex[2] >= 0) && 356 | (cIndex[2] < static_cast(sizeCT[2]))) 357 | { 358 | /* If it is a valid index, get the voxel intensity. */ 359 | value = static_cast(inputPtr->GetPixel(cIndex)); 360 | if (value > m_Threshold) /* Ignore voxels whose intensities are below the threshold. */ 361 | { 362 | d12 += (alphaCmin - alphaCminPrev) * (value - m_Threshold); 363 | } 364 | } 365 | } 366 | 367 | if (d12 < minOutputValue) 368 | { 369 | pixval = minOutputValue; 370 | } 371 | else if (d12 > maxOutputValue) 372 | { 373 | pixval = maxOutputValue; 374 | } 375 | else 376 | { 377 | pixval = static_cast(d12); 378 | } 379 | return (pixval); 380 | } 381 | 382 | 383 | template 384 | typename SiddonJacobsRayCastInterpolateImageFunction::OutputType 385 | SiddonJacobsRayCastInterpolateImageFunction::EvaluateAtContinuousIndex( 386 | const ContinuousIndexType & index) const 387 | { 388 | OutputPointType point; 389 | this->m_Image->TransformContinuousIndexToPhysicalPoint(index, point); 390 | 391 | return this->Evaluate(point); 392 | } 393 | 394 | 395 | template 396 | void 397 | SiddonJacobsRayCastInterpolateImageFunction::ComputeInverseTransform() const 398 | { 399 | m_ComposedTransform->SetIdentity(); 400 | m_ComposedTransform->Compose(m_Transform, 0); 401 | 402 | typename TransformType::InputPointType isocenter; 403 | isocenter = m_Transform->GetCenter(); 404 | // An Euler 3D transform is used to rotate the volume to simulate the roation of the linac gantry. 405 | // The rotation is about z-axis. After the transform, a AP projection geometry (projecting 406 | // towards positive y direction) is established. 407 | m_GantryRotTransform->SetRotation(0.0, 0.0, -m_ProjectionAngle); 408 | m_GantryRotTransform->SetCenter(isocenter); 409 | m_ComposedTransform->Compose(m_GantryRotTransform, 0); 410 | 411 | // An Euler 3D transfrom is used to shift the source to the origin. 412 | typename TransformType::OutputVectorType focalpointtranslation; 413 | focalpointtranslation[0] = -isocenter[0]; 414 | focalpointtranslation[1] = m_FocalPointToIsocenterDistance - isocenter[1]; 415 | focalpointtranslation[2] = -isocenter[2]; 416 | m_CamShiftTransform->SetTranslation(focalpointtranslation); 417 | m_ComposedTransform->Compose(m_CamShiftTransform, 0); 418 | 419 | // A Euler 3D transform is used to establish the standard negative z-axis projection geometry. (By 420 | // default, the camera is situated at the origin, points down the negative z-axis, and has an up- 421 | // vector of (0, 1, 0).) 422 | 423 | m_ComposedTransform->Compose(m_CamRotTransform, 0); 424 | 425 | // The overall inverse transform is computed. The inverse transform will be used by the interpolation 426 | // procedure. 427 | m_ComposedTransform->GetInverse(m_InverseTransform); 428 | this->Modified(); 429 | } 430 | 431 | 432 | template 433 | void 434 | SiddonJacobsRayCastInterpolateImageFunction::Initialize() 435 | { 436 | this->ComputeInverseTransform(); 437 | m_SourceWorld = m_InverseTransform->TransformPoint(m_SourcePoint); 438 | } 439 | 440 | } // namespace itk 441 | 442 | #endif 443 | -------------------------------------------------------------------------------- /include/itkTwoImageToOneImageMetric.h: -------------------------------------------------------------------------------- 1 | /*========================================================================= 2 | * 3 | * Copyright NumFOCUS 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * https://www.apache.org/licenses/LICENSE-2.0.txt 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *=========================================================================*/ 18 | #ifndef itkTwoImageToOneImageMetric_h 19 | #define itkTwoImageToOneImageMetric_h 20 | 21 | #include "itkImageBase.h" 22 | #include "itkTransform.h" 23 | #include "itkInterpolateImageFunction.h" 24 | #include "itkSingleValuedCostFunction.h" 25 | #include "itkGradientRecursiveGaussianImageFilter.h" 26 | #include "itkSpatialObject.h" 27 | 28 | namespace itk 29 | { 30 | 31 | /** \class TwoImageToOneImageMetric 32 | * \brief Computes similarity between two fixed images and one fixed image. 33 | * 34 | * This Class is templated over the type of the two input images. 35 | * It expects a Transform and two Interpolators to be plugged in. 36 | * This particular class is the base class for a hierarchy of 37 | * similarity metrics. 38 | * 39 | * This class computes a value that measures the similarity 40 | * between two Fixed image and the transformed Moving images. 41 | * The Interpolators are used to compute intensity values on 42 | * non-grid positions resulting from mapping points through 43 | * the Transform. 44 | * 45 | * 46 | * \ingroup RegistrationMetrics 47 | * \ingroup TwoProjectionRegistration 48 | * 49 | */ 50 | 51 | template 52 | class TwoImageToOneImageMetric : public SingleValuedCostFunction 53 | { 54 | public: 55 | ITK_DISALLOW_COPY_AND_MOVE(TwoImageToOneImageMetric); 56 | 57 | /** Standard class type alias. */ 58 | using Self = TwoImageToOneImageMetric; 59 | using Superclass = SingleValuedCostFunction; 60 | using Pointer = SmartPointer; 61 | using ConstPointer = SmartPointer; 62 | 63 | /** Type used for representing point components */ 64 | using CoordinateRepresentationType = Superclass::ParametersValueType; 65 | 66 | /** Run-time type information (and related methods). */ 67 | itkTypeMacro(TwoImageToOneImageMetric, SingleValuedCostFunction); 68 | 69 | /** Type of the moving Image. */ 70 | using MovingImageType = TMovingImage; 71 | using MovingImagePixelType = typename TMovingImage::PixelType; 72 | using MovingImageConstPointer = typename MovingImageType::ConstPointer; 73 | 74 | /** Type of the fixed Image. */ 75 | using FixedImageType = TFixedImage; 76 | using FixedImageConstPointer = typename FixedImageType::ConstPointer; 77 | using FixedImageRegionType = typename FixedImageType::RegionType; 78 | 79 | /** Constants for the image dimensions */ 80 | static constexpr unsigned int MovingImageDimension = TMovingImage::ImageDimension; 81 | static constexpr unsigned int FixedImageDimension = TFixedImage::ImageDimension; 82 | 83 | /** Type of the Transform Base class */ 84 | using TransformType = Transform; 87 | 88 | using TransformPointer = typename TransformType::Pointer; 89 | using InputPointType = typename TransformType::InputPointType; 90 | using OutputPointType = typename TransformType::OutputPointType; 91 | using TransformParametersType = typename TransformType::ParametersType; 92 | using TransformJacobianType = typename TransformType::JacobianType; 93 | 94 | /** Type of the Interpolator Base class */ 95 | using InterpolatorType = InterpolateImageFunction; 96 | 97 | 98 | /** Gaussian filter to compute the gradient of the Moving Image */ 99 | using RealType = typename NumericTraits::RealType; 100 | using GradientPixelType = CovariantVector; 101 | using GradientImageType = Image; 102 | using GradientImagePointer = SmartPointer; 103 | using GradientImageFilterType = GradientRecursiveGaussianImageFilter; 104 | using GradientImageFilterPointer = typename GradientImageFilterType::Pointer; 105 | using InterpolatorPointer = typename InterpolatorType::Pointer; 106 | 107 | /** Type for the mask of the fixed image. Only pixels that are "inside" 108 | this mask will be considered for the computation of the metric */ 109 | typedef SpatialObject FixedImageMaskType; 110 | using FixedImageMaskPointer = typename FixedImageMaskType::Pointer; 111 | 112 | /** Type for the mask of the moving image. Only pixels that are "inside" 113 | this mask will be considered for the computation of the metric */ 114 | typedef SpatialObject MovingImageMaskType; 115 | using MovingImageMaskPointer = typename MovingImageMaskType::Pointer; 116 | 117 | 118 | /** Type of the measure. */ 119 | using MeasureType = Superclass::MeasureType; 120 | 121 | /** Type of the derivative. */ 122 | using DerivativeType = Superclass::DerivativeType; 123 | 124 | /** Type of the parameters. */ 125 | using ParametersType = Superclass::ParametersType; 126 | 127 | /** Connect the Fixed Image. */ 128 | itkSetConstObjectMacro(FixedImage1, FixedImageType); 129 | 130 | /** Connect the Fixed Image. */ 131 | itkSetConstObjectMacro(FixedImage2, FixedImageType); 132 | 133 | /** Get the Fixed Image. */ 134 | itkGetConstObjectMacro(FixedImage1, FixedImageType); 135 | 136 | /** Get the Fixed Image. */ 137 | itkGetConstObjectMacro(FixedImage2, FixedImageType); 138 | 139 | /** Connect the Moving Image. */ 140 | itkSetConstObjectMacro(MovingImage, MovingImageType); 141 | 142 | /** Get the Moving Image. */ 143 | itkGetConstObjectMacro(MovingImage, MovingImageType); 144 | 145 | /** Connect the Transform. */ 146 | itkSetObjectMacro(Transform, TransformType); 147 | 148 | /** Get a pointer to the Transform. */ 149 | itkGetConstObjectMacro(Transform, TransformType); 150 | 151 | /** Connect the Interpolator. */ 152 | itkSetObjectMacro(Interpolator1, InterpolatorType); 153 | 154 | /** Connect the Interpolator. */ 155 | itkSetObjectMacro(Interpolator2, InterpolatorType); 156 | 157 | /** Get a pointer to the Interpolator. */ 158 | itkGetConstObjectMacro(Interpolator1, InterpolatorType); 159 | 160 | /** Get a pointer to the Interpolator. */ 161 | itkGetConstObjectMacro(Interpolator2, InterpolatorType); 162 | 163 | /** Get the number of pixels considered in the computation. */ 164 | itkGetConstReferenceMacro(NumberOfPixelsCounted, unsigned long); 165 | 166 | /** Set the region over which the metric will be computed */ 167 | itkSetMacro(FixedImageRegion1, FixedImageRegionType); 168 | 169 | /** Set the region over which the metric will be computed */ 170 | itkSetMacro(FixedImageRegion2, FixedImageRegionType); 171 | 172 | /** Get the region over which the metric will be computed */ 173 | itkGetConstReferenceMacro(FixedImageRegion1, FixedImageRegionType); 174 | 175 | /** Get the region over which the metric will be computed */ 176 | itkGetConstReferenceMacro(FixedImageRegion2, FixedImageRegionType); 177 | 178 | /** Set/Get the moving image mask. */ 179 | itkSetObjectMacro(MovingImageMask, MovingImageMaskType); 180 | itkGetConstObjectMacro(MovingImageMask, MovingImageMaskType); 181 | 182 | /** Set/Get the fixed image mask. */ 183 | itkSetObjectMacro(FixedImageMask1, FixedImageMaskType); 184 | itkSetObjectMacro(FixedImageMask2, FixedImageMaskType); 185 | itkGetConstObjectMacro(FixedImageMask1, FixedImageMaskType); 186 | itkGetConstObjectMacro(FixedImageMask2, FixedImageMaskType); 187 | 188 | /** Set/Get gradient computation. */ 189 | itkSetMacro(ComputeGradient, bool); 190 | itkGetConstReferenceMacro(ComputeGradient, bool); 191 | itkBooleanMacro(ComputeGradient); 192 | 193 | /** Get Gradient Image. */ 194 | itkGetConstObjectMacro(GradientImage, GradientImageType); 195 | 196 | /** Set the parameters defining the Transform. */ 197 | void 198 | SetTransformParameters(const ParametersType & parameters) const; 199 | 200 | /** Return the number of parameters required by the Transform */ 201 | unsigned int 202 | GetNumberOfParameters() const override 203 | { 204 | return m_Transform->GetNumberOfParameters(); 205 | } 206 | 207 | /** Initialize the Metric by making sure that all the components 208 | * are present and plugged together correctly */ 209 | virtual void 210 | Initialize(); 211 | 212 | protected: 213 | TwoImageToOneImageMetric(); 214 | ~TwoImageToOneImageMetric() override = default; 215 | void 216 | PrintSelf(std::ostream & os, Indent indent) const override; 217 | 218 | mutable unsigned long m_NumberOfPixelsCounted; 219 | 220 | FixedImageConstPointer m_FixedImage1; 221 | FixedImageConstPointer m_FixedImage2; 222 | MovingImageConstPointer m_MovingImage; 223 | 224 | mutable TransformPointer m_Transform; 225 | InterpolatorPointer m_Interpolator1; 226 | InterpolatorPointer m_Interpolator2; 227 | 228 | bool m_ComputeGradient; 229 | GradientImagePointer m_GradientImage; 230 | 231 | mutable FixedImageMaskPointer m_FixedImageMask1; 232 | mutable FixedImageMaskPointer m_FixedImageMask2; 233 | mutable MovingImageMaskPointer m_MovingImageMask; 234 | 235 | private: 236 | FixedImageRegionType m_FixedImageRegion1; 237 | FixedImageRegionType m_FixedImageRegion2; 238 | }; 239 | 240 | } // end namespace itk 241 | 242 | #ifndef ITK_MANUAL_INSTANTIATION 243 | # include "itkTwoImageToOneImageMetric.hxx" 244 | #endif 245 | 246 | #endif 247 | -------------------------------------------------------------------------------- /include/itkTwoImageToOneImageMetric.hxx: -------------------------------------------------------------------------------- 1 | /*========================================================================= 2 | * 3 | * Copyright NumFOCUS 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * https://www.apache.org/licenses/LICENSE-2.0.txt 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *=========================================================================*/ 18 | #ifndef itkTwoImageToOneImageMetric_hxx 19 | #define itkTwoImageToOneImageMetric_hxx 20 | 21 | namespace itk 22 | { 23 | 24 | template 25 | TwoImageToOneImageMetric::TwoImageToOneImageMetric() 26 | { 27 | m_FixedImage1 = nullptr; // has to be provided by the user. 28 | m_FixedImage2 = nullptr; // has to be provided by the user. 29 | m_MovingImage = nullptr; // has to be provided by the user. 30 | m_Transform = nullptr; // has to be provided by the user. 31 | m_Interpolator1 = nullptr; // has to be provided by the user. 32 | m_Interpolator2 = nullptr; // has to be provided by the user. 33 | m_GradientImage = nullptr; // will receive the output of the filter; 34 | m_ComputeGradient = true; // metric computes gradient by default 35 | m_NumberOfPixelsCounted = 0; // initialize to zero 36 | m_GradientImage = nullptr; // computed at initialization 37 | } 38 | 39 | 40 | /* 41 | * Set the parameters that define a unique transform 42 | */ 43 | template 44 | void 45 | TwoImageToOneImageMetric::SetTransformParameters(const ParametersType & parameters) const 46 | { 47 | if (!m_Transform) 48 | { 49 | itkExceptionMacro(<< "Transform has not been assigned"); 50 | } 51 | m_Transform->SetParameters(parameters); 52 | } 53 | 54 | 55 | template 56 | void 57 | TwoImageToOneImageMetric::Initialize() 58 | { 59 | 60 | if (!m_Transform) 61 | { 62 | itkExceptionMacro(<< "Transform is not present"); 63 | } 64 | 65 | if (!m_Interpolator1) 66 | { 67 | itkExceptionMacro(<< "Interpolator1 is not present"); 68 | } 69 | if (!m_Interpolator2) 70 | { 71 | itkExceptionMacro(<< "Interpolator2 is not present"); 72 | } 73 | 74 | if (!m_MovingImage) 75 | { 76 | itkExceptionMacro(<< "MovingImage is not present"); 77 | } 78 | 79 | if (!m_FixedImage1) 80 | { 81 | itkExceptionMacro(<< "FixedImage1 is not present"); 82 | } 83 | 84 | if (!m_FixedImage2) 85 | { 86 | itkExceptionMacro(<< "FixedImage2 is not present"); 87 | } 88 | 89 | if (m_FixedImageRegion1.GetNumberOfPixels() == 0) 90 | { 91 | itkExceptionMacro(<< "FixedImageRegion1 is empty"); 92 | } 93 | 94 | if (m_FixedImageRegion2.GetNumberOfPixels() == 0) 95 | { 96 | itkExceptionMacro(<< "FixedImageRegion2 is empty"); 97 | } 98 | 99 | // If the image is provided by a source, update the source. 100 | if (m_MovingImage->GetSource()) 101 | { 102 | m_MovingImage->GetSource()->Update(); 103 | } 104 | 105 | // If the image is provided by a source, update the source. 106 | if (m_FixedImage1->GetSource()) 107 | { 108 | m_FixedImage1->GetSource()->Update(); 109 | } 110 | 111 | if (m_FixedImage2->GetSource()) 112 | { 113 | m_FixedImage2->GetSource()->Update(); 114 | } 115 | 116 | // Make sure the FixedImageRegion is within the FixedImage buffered region 117 | if (!m_FixedImageRegion1.Crop(m_FixedImage1->GetBufferedRegion())) 118 | { 119 | itkExceptionMacro(<< "FixedImageRegion1 does not overlap the fixed image buffered region"); 120 | } 121 | 122 | if (!m_FixedImageRegion2.Crop(m_FixedImage2->GetBufferedRegion())) 123 | { 124 | itkExceptionMacro(<< "FixedImageRegion2 does not overlap the fixed image buffered region"); 125 | } 126 | 127 | m_Interpolator1->SetInputImage(m_MovingImage); 128 | m_Interpolator2->SetInputImage(m_MovingImage); 129 | 130 | if (m_ComputeGradient) 131 | { 132 | 133 | GradientImageFilterPointer gradientFilter = GradientImageFilterType::New(); 134 | 135 | gradientFilter->SetInput(m_MovingImage); 136 | 137 | const typename MovingImageType::SpacingType & spacing = m_MovingImage->GetSpacing(); 138 | double maximumSpacing = 0.0; 139 | for (unsigned int i = 0; i < MovingImageDimension; i++) 140 | { 141 | if (spacing[i] > maximumSpacing) 142 | { 143 | maximumSpacing = spacing[i]; 144 | } 145 | } 146 | gradientFilter->SetSigma(maximumSpacing); 147 | gradientFilter->SetNormalizeAcrossScale(true); 148 | 149 | gradientFilter->Update(); 150 | 151 | m_GradientImage = gradientFilter->GetOutput(); 152 | } 153 | 154 | // If there are any observers on the metric, call them to give the 155 | // user code a chance to set parameters on the metric 156 | this->InvokeEvent(InitializeEvent()); 157 | } 158 | 159 | 160 | template 161 | void 162 | TwoImageToOneImageMetric::PrintSelf(std::ostream & os, Indent indent) const 163 | { 164 | Superclass::PrintSelf(os, indent); 165 | os << indent << "ComputeGradient: " << static_cast::PrintType>(m_ComputeGradient) 166 | << std::endl; 167 | os << indent << "Moving Image: " << m_MovingImage.GetPointer() << std::endl; 168 | os << indent << "Fixed Image 1: " << m_FixedImage1.GetPointer() << std::endl; 169 | os << indent << "Fixed Image 2: " << m_FixedImage2.GetPointer() << std::endl; 170 | os << indent << "Gradient Image: " << m_GradientImage.GetPointer() << std::endl; 171 | os << indent << "Transform: " << m_Transform.GetPointer() << std::endl; 172 | os << indent << "Interpolator 1: " << m_Interpolator1.GetPointer() << std::endl; 173 | os << indent << "Interpolator 2: " << m_Interpolator2.GetPointer() << std::endl; 174 | os << indent << "FixedImageRegion 1: " << m_FixedImageRegion1 << std::endl; 175 | os << indent << "FixedImageRegion 2: " << m_FixedImageRegion2 << std::endl; 176 | os << indent << "Moving Image Mask: " << m_MovingImageMask.GetPointer() << std::endl; 177 | os << indent << "Fixed Image Mask 1: " << m_FixedImageMask1.GetPointer() << std::endl; 178 | os << indent << "Fixed Image Mask 2: " << m_FixedImageMask2.GetPointer() << std::endl; 179 | os << indent << "Number of Pixels Counted: " << m_NumberOfPixelsCounted << std::endl; 180 | } 181 | 182 | 183 | } // end namespace itk 184 | 185 | #endif 186 | -------------------------------------------------------------------------------- /include/itkTwoProjectionImageRegistrationMethod.h: -------------------------------------------------------------------------------- 1 | /*========================================================================= 2 | * 3 | * Copyright NumFOCUS 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * https://www.apache.org/licenses/LICENSE-2.0.txt 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *=========================================================================*/ 18 | #ifndef itkTwoProjectionImageRegistrationMethod_h 19 | #define itkTwoProjectionImageRegistrationMethod_h 20 | 21 | #include "itkProcessObject.h" 22 | #include "itkImage.h" 23 | #include "itkTwoImageToOneImageMetric.h" 24 | #include "itkSingleValuedNonLinearOptimizer.h" 25 | #include "itkDataObjectDecorator.h" 26 | 27 | namespace itk 28 | { 29 | 30 | /** \class TwoProjectionImageRegistrationMethod 31 | * \brief Base class for Image Registration Methods 32 | * 33 | * This Class define the generic interface for a registration method. 34 | * 35 | * This class is templated over the type of the two image to be 36 | * registered. A generic Transform is used by this class. That allows 37 | * to select at run time the particular type of transformation that 38 | * is to be applied for registering the images. 39 | * 40 | * This method use a generic Metric in order to compare the two images. 41 | * the final goal of the registration method is to find the set of 42 | * parameters of the Transformation that optimizes the metric. 43 | * 44 | * The registration method also support a generic optimizer that can 45 | * be selected at run-time. The only restriction for the optimizer is 46 | * that it should be able to operate in single-valued cost functions 47 | * given that the metrics used to compare images provide a single 48 | * value as output. 49 | * 50 | * The terms : Fixed image and Moving image are used in this class 51 | * to indicate what image is being mapped by the transform. 52 | * 53 | * This class uses the coordinate system of the Fixed image as a reference 54 | * and searchs for a Transform that will map points from the space of the 55 | * Fixed image to the space of the Moving image. 56 | * 57 | * For doing so, a Metric will be continously applied to compare the Fixed 58 | * image with the Transformed Moving image. This process also requires to 59 | * interpolate values from the Moving image. 60 | * 61 | * \ingroup RegistrationFilters 62 | * \ingroup TwoProjectionRegistration 63 | */ 64 | template 65 | class TwoProjectionImageRegistrationMethod : public ProcessObject 66 | { 67 | public: 68 | ITK_DISALLOW_COPY_AND_MOVE(TwoProjectionImageRegistrationMethod); 69 | 70 | /** Standard class type alias. */ 71 | using Self = TwoProjectionImageRegistrationMethod; 72 | using Superclass = ProcessObject; 73 | using Pointer = SmartPointer; 74 | using ConstPointer = SmartPointer; 75 | 76 | /** Method for creation through the object factory. */ 77 | itkNewMacro(Self); 78 | 79 | /** Run-time type information (and related methods). */ 80 | itkTypeMacro(TwoProjectionImageRegistrationMethod, ProcessObject); 81 | 82 | /** Type of the Fixed image. */ 83 | using FixedImageType = TFixedImage; 84 | using FixedImageConstPointer = typename FixedImageType::ConstPointer; 85 | 86 | /** Type of the Moving image. */ 87 | using MovingImageType = TMovingImage; 88 | using MovingImageConstPointer = typename MovingImageType::ConstPointer; 89 | 90 | /** Type of the metric. */ 91 | using MetricType = TwoImageToOneImageMetric; 92 | using MetricPointer = typename MetricType::Pointer; 93 | using FixedImageRegionType = typename MetricType::FixedImageRegionType; 94 | 95 | /** Type of the Transform . */ 96 | using TransformType = typename MetricType::TransformType; 97 | using TransformPointer = typename TransformType::Pointer; 98 | 99 | /** Type for the output: Using Decorator pattern for enabling 100 | * the Transform to be passed in the data pipeline */ 101 | using TransformOutputType = DataObjectDecorator; 102 | using TransformOutputPointer = typename TransformOutputType::Pointer; 103 | using TransformOutputConstPointer = typename TransformOutputType::ConstPointer; 104 | 105 | /** Type of the Interpolator. */ 106 | using InterpolatorType = typename MetricType::InterpolatorType; 107 | using InterpolatorPointer = typename InterpolatorType::Pointer; 108 | 109 | /** Type of the optimizer. */ 110 | using OptimizerType = SingleValuedNonLinearOptimizer; 111 | 112 | /** Type of the Transformation parameters This is the same type used to 113 | * represent the search space of the optimization algorithm */ 114 | using ParametersType = typename MetricType::TransformParametersType; 115 | 116 | /** Smart Pointer type to a DataObject. */ 117 | using DataObjectPointer = typename DataObject::Pointer; 118 | 119 | /** Method that initiates the registration. This will Initialize and ensure 120 | * that all inputs the registration needs are in place, via a call to 121 | * Initialize() will then start the optimization process via a call to 122 | * StartOptimization() */ 123 | void 124 | StartRegistration(); 125 | 126 | /** Method that initiates the optimization process. */ 127 | void 128 | StartOptimization(); 129 | 130 | /** Set/Get the Fixed images. */ 131 | void 132 | SetFixedImage1(const FixedImageType * fixedImage1); 133 | void 134 | SetFixedImage2(const FixedImageType * fixedImage2); 135 | itkGetConstObjectMacro(FixedImage1, FixedImageType); 136 | itkGetConstObjectMacro(FixedImage2, FixedImageType); 137 | 138 | /** Set/Get the Moving image. */ 139 | void 140 | SetMovingImage(const MovingImageType * movingImage); 141 | itkGetConstObjectMacro(MovingImage, MovingImageType); 142 | 143 | /** Set/Get the Optimizer. */ 144 | itkSetObjectMacro(Optimizer, OptimizerType); 145 | itkGetConstObjectMacro(Optimizer, OptimizerType); 146 | 147 | /** Set/Get the Metric. */ 148 | itkSetObjectMacro(Metric, MetricType); 149 | itkGetConstObjectMacro(Metric, MetricType); 150 | 151 | /** Set/Get the Transfrom. */ 152 | itkSetObjectMacro(Transform, TransformType); 153 | itkGetConstObjectMacro(Transform, TransformType); 154 | 155 | /** Set/Get the Interpolators. */ 156 | itkSetObjectMacro(Interpolator1, InterpolatorType); 157 | itkSetObjectMacro(Interpolator2, InterpolatorType); 158 | itkGetConstObjectMacro(Interpolator1, InterpolatorType); 159 | itkGetConstObjectMacro(Interpolator2, InterpolatorType); 160 | 161 | /** Set/Get the initial transformation parameters. */ 162 | virtual void 163 | SetInitialTransformParameters(const ParametersType & param); 164 | itkGetConstReferenceMacro(InitialTransformParameters, ParametersType); 165 | 166 | /** Get the last transformation parameters visited by 167 | * the optimizer. */ 168 | itkGetConstReferenceMacro(LastTransformParameters, ParametersType); 169 | 170 | /** Set the region of the fixed image to be considered as region of 171 | interest during the registration. This region will be passed to 172 | the ImageMetric in order to restrict the metric computation to 173 | consider only this region. 174 | \warning The same region can also be set directly into the metric. 175 | please avoid to set the region in both places since this can lead 176 | to inconsistent configurations. */ 177 | void 178 | SetFixedImageRegion1(const FixedImageRegionType & region1); 179 | void 180 | SetFixedImageRegion2(const FixedImageRegionType & region2); 181 | /** Get the region of the fixed image to be considered as region of 182 | interest during the registration. This region will be passed to 183 | the ImageMetric in order to restrict the metric computation to 184 | consider only this region. */ 185 | itkGetConstReferenceMacro(FixedImageRegion1, FixedImageRegionType); 186 | itkGetConstReferenceMacro(FixedImageRegion2, FixedImageRegionType); 187 | /** True if a region has been defined for the fixed image to which 188 | the ImageMetric will limit its computation */ 189 | itkGetMacro(FixedImageRegionDefined1, bool); 190 | itkGetMacro(FixedImageRegionDefined2, bool); 191 | /** Turn on/off the use of a fixed image region to which 192 | the ImageMetric will limit its computation. 193 | \warning The region must have been previously defined using the 194 | SetFixedImageRegion member function */ 195 | itkSetMacro(FixedImageRegionDefined1, bool); 196 | itkSetMacro(FixedImageRegionDefined2, bool); 197 | 198 | /** Initialize by setting the interconnects between the components. */ 199 | virtual void 200 | Initialize(); 201 | 202 | /** Returns the transform resulting from the registration process */ 203 | const TransformOutputType * 204 | GetOutput() const; 205 | 206 | /** Make a DataObject of the correct type to be used as the specified 207 | * output. */ 208 | using Superclass::MakeOutput; 209 | DataObjectPointer 210 | MakeOutput(DataObjectPointerArraySizeType idx) override; 211 | 212 | protected: 213 | TwoProjectionImageRegistrationMethod(); 214 | ~TwoProjectionImageRegistrationMethod() override = default; 215 | void 216 | PrintSelf(std::ostream & os, Indent indent) const override; 217 | 218 | /** Method invoked by the pipeline in order to trigger the computation of 219 | * the registration. */ 220 | void 221 | GenerateData() override; 222 | 223 | /** Provides derived classes with the ability to set this private var */ 224 | itkSetMacro(LastTransformParameters, ParametersType); 225 | 226 | 227 | private: 228 | MetricPointer m_Metric; 229 | OptimizerType::Pointer m_Optimizer; 230 | 231 | MovingImageConstPointer m_MovingImage; 232 | FixedImageConstPointer m_FixedImage1; 233 | FixedImageConstPointer m_FixedImage2; 234 | 235 | TransformPointer m_Transform; 236 | InterpolatorPointer m_Interpolator1; 237 | InterpolatorPointer m_Interpolator2; 238 | 239 | ParametersType m_InitialTransformParameters; 240 | ParametersType m_LastTransformParameters; 241 | 242 | bool m_FixedImageRegionDefined1; 243 | bool m_FixedImageRegionDefined2; 244 | FixedImageRegionType m_FixedImageRegion1; 245 | FixedImageRegionType m_FixedImageRegion2; 246 | }; 247 | 248 | } // end namespace itk 249 | 250 | 251 | #ifndef ITK_MANUAL_INSTANTIATION 252 | # include "itkTwoProjectionImageRegistrationMethod.hxx" 253 | #endif 254 | 255 | #endif 256 | -------------------------------------------------------------------------------- /include/itkTwoProjectionImageRegistrationMethod.hxx: -------------------------------------------------------------------------------- 1 | /*========================================================================= 2 | * 3 | * Copyright NumFOCUS 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * https://www.apache.org/licenses/LICENSE-2.0.txt 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *=========================================================================*/ 18 | #ifndef itkTwoProjectionImageRegistrationMethod_hxx 19 | #define itkTwoProjectionImageRegistrationMethod_hxx 20 | 21 | namespace itk 22 | { 23 | 24 | template 25 | TwoProjectionImageRegistrationMethod::TwoProjectionImageRegistrationMethod() 26 | { 27 | this->SetNumberOfRequiredOutputs(1); // for the Transform 28 | 29 | m_FixedImage1 = nullptr; // has to be provided by the user. 30 | m_FixedImage2 = nullptr; // has to be provided by the user. 31 | m_MovingImage = nullptr; // has to be provided by the user. 32 | m_Transform = nullptr; // has to be provided by the user. 33 | m_Interpolator1 = nullptr; // has to be provided by the user. 34 | m_Interpolator2 = nullptr; // has to be provided by the user. 35 | m_Metric = nullptr; // has to be provided by the user. 36 | m_Optimizer = nullptr; // has to be provided by the user. 37 | 38 | 39 | m_InitialTransformParameters = ParametersType(1); 40 | m_LastTransformParameters = ParametersType(1); 41 | 42 | m_InitialTransformParameters.Fill(0.0f); 43 | m_LastTransformParameters.Fill(0.0f); 44 | 45 | m_FixedImageRegionDefined1 = false; 46 | m_FixedImageRegionDefined2 = false; 47 | 48 | 49 | TransformOutputPointer transformDecorator = static_cast(this->MakeOutput(0).GetPointer()); 50 | 51 | this->ProcessObject::SetNthOutput(0, transformDecorator.GetPointer()); 52 | } 53 | 54 | 55 | template 56 | void 57 | TwoProjectionImageRegistrationMethod::SetInitialTransformParameters( 58 | const ParametersType & param) 59 | { 60 | m_InitialTransformParameters = param; 61 | this->Modified(); 62 | } 63 | 64 | 65 | /* 66 | * Set the region of the fixed image 1 to be considered for registration 67 | */ 68 | template 69 | void 70 | TwoProjectionImageRegistrationMethod::SetFixedImageRegion1( 71 | const FixedImageRegionType & region1) 72 | { 73 | m_FixedImageRegion1 = region1; 74 | m_FixedImageRegionDefined1 = true; 75 | } 76 | 77 | /* 78 | * Set the region of the fixed image 2 to be considered for registration 79 | */ 80 | template 81 | void 82 | TwoProjectionImageRegistrationMethod::SetFixedImageRegion2( 83 | const FixedImageRegionType & region2) 84 | { 85 | m_FixedImageRegion2 = region2; 86 | m_FixedImageRegionDefined2 = true; 87 | } 88 | 89 | 90 | /* 91 | * Initialize by setting the interconnects between components. 92 | */ 93 | template 94 | void 95 | TwoProjectionImageRegistrationMethod::Initialize() 96 | { 97 | 98 | if (!m_FixedImage1) 99 | { 100 | itkExceptionMacro(<< "FixedImage1 is not present"); 101 | } 102 | 103 | if (!m_FixedImage2) 104 | { 105 | itkExceptionMacro(<< "FixedImage2 is not present"); 106 | } 107 | 108 | if (!m_MovingImage) 109 | { 110 | itkExceptionMacro(<< "MovingImage is not present"); 111 | } 112 | 113 | if (!m_Metric) 114 | { 115 | itkExceptionMacro(<< "Metric is not present"); 116 | } 117 | 118 | if (!m_Optimizer) 119 | { 120 | itkExceptionMacro(<< "Optimizer is not present"); 121 | } 122 | 123 | if (!m_Transform) 124 | { 125 | itkExceptionMacro(<< "Transform is not present"); 126 | } 127 | 128 | // 129 | // Connect the transform to the Decorator. 130 | // 131 | auto * transformOutput = static_cast(this->ProcessObject::GetOutput(0)); 132 | 133 | transformOutput->Set(m_Transform.GetPointer()); 134 | 135 | 136 | if (!m_Interpolator1) 137 | { 138 | itkExceptionMacro(<< "Interpolator1 is not present"); 139 | } 140 | 141 | if (!m_Interpolator2) 142 | { 143 | itkExceptionMacro(<< "Interpolator2 is not present"); 144 | } 145 | 146 | // Store user-defined image origin 147 | // typename FixedImageType::PointType fixedOrigin1 = m_FixedImage1->GetOrigin(); 148 | // typename FixedImageType::PointType fixedOrigin2 = m_FixedImage2->GetOrigin(); 149 | 150 | // Setup the metric 151 | m_Metric->SetMovingImage(m_MovingImage); 152 | m_Metric->SetFixedImage1(m_FixedImage1); 153 | m_Metric->SetFixedImage2(m_FixedImage2); 154 | m_Metric->SetTransform(m_Transform); 155 | m_Metric->SetInterpolator1(m_Interpolator1); 156 | m_Metric->SetInterpolator2(m_Interpolator2); 157 | 158 | if (m_FixedImageRegionDefined1) 159 | { 160 | m_Metric->SetFixedImageRegion1(m_FixedImageRegion1); 161 | } 162 | else 163 | { 164 | m_Metric->SetFixedImageRegion1(m_FixedImage1->GetBufferedRegion()); 165 | } 166 | 167 | if (m_FixedImageRegionDefined2) 168 | { 169 | m_Metric->SetFixedImageRegion2(m_FixedImageRegion2); 170 | } 171 | else 172 | { 173 | m_Metric->SetFixedImageRegion2(m_FixedImage2->GetBufferedRegion()); 174 | } 175 | 176 | m_Metric->Initialize(); 177 | 178 | // Recover user-defined image origin 179 | /* const short Dimension = GetImageDimension::ImageDimension; 180 | double fixedOrg1[Dimension]; 181 | for(int i = 0; i < Dimension; i++) 182 | fixedOrg1[i] = fixedOrigin1[i]; 183 | m_FixedImage1->SetOrigin(fixedOrg1); 184 | m_Metric->GetFixedImage1()->SetOrigin(fixedOrg1); 185 | 186 | double fixedOrg2[Dimension]; 187 | for(int i = 0; i < Dimension; i++) 188 | fixedOrg2[i] = fixedOrigin2[i]; 189 | m_FixedImage2->SetOrigin(fixedOrg2); 190 | m_Metric->GetFixedImage2()->SetOrigin(fixedOrg2); 191 | */ 192 | // Setup the optimizer 193 | m_Optimizer->SetCostFunction(m_Metric); 194 | 195 | // Validate initial transform parameters 196 | if (m_InitialTransformParameters.Size() != m_Transform->GetNumberOfParameters()) 197 | { 198 | itkExceptionMacro(<< "Size mismatch between initial parameter and transform"); 199 | } 200 | 201 | m_Optimizer->SetInitialPosition(m_InitialTransformParameters); 202 | } 203 | 204 | 205 | /* 206 | * Starts the Registration Process 207 | */ 208 | template 209 | void 210 | TwoProjectionImageRegistrationMethod::StartRegistration() 211 | { 212 | 213 | ParametersType empty(1); 214 | empty.Fill(0.0); 215 | try 216 | { 217 | // initialize the interconnects between components 218 | this->Initialize(); 219 | } 220 | catch (ExceptionObject & err) 221 | { 222 | m_LastTransformParameters = empty; 223 | 224 | // pass exception to caller 225 | throw err; 226 | } 227 | 228 | this->StartOptimization(); 229 | } 230 | 231 | 232 | /* 233 | * Starts the Optimization process 234 | */ 235 | template 236 | void 237 | TwoProjectionImageRegistrationMethod::StartOptimization() 238 | { 239 | try 240 | { 241 | // do the optimization 242 | m_Optimizer->StartOptimization(); 243 | } 244 | catch (ExceptionObject & err) 245 | { 246 | // An error has occurred in the optimization. 247 | // Update the parameters 248 | m_LastTransformParameters = m_Optimizer->GetCurrentPosition(); 249 | 250 | // Pass exception to caller 251 | throw err; 252 | } 253 | 254 | // get the results 255 | m_LastTransformParameters = m_Optimizer->GetCurrentPosition(); 256 | m_Transform->SetParameters(m_LastTransformParameters); 257 | } 258 | 259 | 260 | template 261 | void 262 | TwoProjectionImageRegistrationMethod::PrintSelf(std::ostream & os, Indent indent) const 263 | { 264 | Superclass::PrintSelf(os, indent); 265 | os << indent << "Metric: " << m_Metric.GetPointer() << std::endl; 266 | os << indent << "Optimizer: " << m_Optimizer.GetPointer() << std::endl; 267 | os << indent << "Transform: " << m_Transform.GetPointer() << std::endl; 268 | os << indent << "Interpolator 1: " << m_Interpolator1.GetPointer() << std::endl; 269 | os << indent << "Interpolator 2: " << m_Interpolator2.GetPointer() << std::endl; 270 | os << indent << "Fixed Image 1: " << m_FixedImage1.GetPointer() << std::endl; 271 | os << indent << "Fixed Image 2: " << m_FixedImage2.GetPointer() << std::endl; 272 | os << indent << "Moving Image: " << m_MovingImage.GetPointer() << std::endl; 273 | os << indent << "Fixed Image 1 Region Defined: " << m_FixedImageRegionDefined1 << std::endl; 274 | os << indent << "Fixed Image 2 Region Defined: " << m_FixedImageRegionDefined2 << std::endl; 275 | os << indent << "Fixed Image 1 Region: " << m_FixedImageRegion1 << std::endl; 276 | os << indent << "Fixed Image 2 Region: " << m_FixedImageRegion2 << std::endl; 277 | os << indent << "Initial Transform Parameters: " << m_InitialTransformParameters << std::endl; 278 | os << indent << "Last Transform Parameters: " << m_LastTransformParameters << std::endl; 279 | } 280 | 281 | 282 | template 283 | void 284 | TwoProjectionImageRegistrationMethod::GenerateData() 285 | { 286 | this->StartRegistration(); 287 | } 288 | 289 | 290 | template 291 | const typename TwoProjectionImageRegistrationMethod::TransformOutputType * 292 | TwoProjectionImageRegistrationMethod::GetOutput() const 293 | { 294 | return static_cast(this->ProcessObject::GetOutput(0)); 295 | } 296 | 297 | 298 | template 299 | DataObject::Pointer 300 | TwoProjectionImageRegistrationMethod::MakeOutput(DataObjectPointerArraySizeType output) 301 | { 302 | switch (output) 303 | { 304 | case 0: 305 | return static_cast(TransformOutputType::New().GetPointer()); 306 | break; 307 | default: 308 | itkExceptionMacro("MakeOutput request for an output number larger than the expected number of outputs"); 309 | return nullptr; 310 | } 311 | } 312 | 313 | 314 | template 315 | void 316 | TwoProjectionImageRegistrationMethod::SetFixedImage1(const FixedImageType * fixedImage1) 317 | { 318 | itkDebugMacro("setting Fixed Image 1 to " << fixedImage1); 319 | 320 | if (this->m_FixedImage1.GetPointer() != fixedImage1) 321 | { 322 | this->m_FixedImage1 = fixedImage1; 323 | 324 | // Process object is not const-correct so the const_cast is required here 325 | this->ProcessObject::SetNthInput(0, const_cast(fixedImage1)); 326 | 327 | this->Modified(); 328 | } 329 | } 330 | 331 | 332 | template 333 | void 334 | TwoProjectionImageRegistrationMethod::SetFixedImage2(const FixedImageType * fixedImage2) 335 | { 336 | itkDebugMacro("setting Fixed Image 2 to " << fixedImage2); 337 | 338 | if (this->m_FixedImage2.GetPointer() != fixedImage2) 339 | { 340 | this->m_FixedImage2 = fixedImage2; 341 | 342 | // Process object is not const-correct so the const_cast is required here 343 | this->ProcessObject::SetNthInput(0, const_cast(fixedImage2)); 344 | 345 | this->Modified(); 346 | } 347 | } 348 | 349 | 350 | template 351 | void 352 | TwoProjectionImageRegistrationMethod::SetMovingImage(const MovingImageType * movingImage) 353 | { 354 | itkDebugMacro("setting Moving Image to " << movingImage); 355 | 356 | if (this->m_MovingImage.GetPointer() != movingImage) 357 | { 358 | this->m_MovingImage = movingImage; 359 | 360 | // Process object is not const-correct so the const_cast is required here 361 | this->ProcessObject::SetNthInput(1, const_cast(movingImage)); 362 | 363 | this->Modified(); 364 | } 365 | } 366 | 367 | } // end namespace itk 368 | 369 | #endif 370 | -------------------------------------------------------------------------------- /itk-module.cmake: -------------------------------------------------------------------------------- 1 | # the top-level README is used for describing this module, just 2 | # re-used it for documentation here 3 | get_filename_component(MY_CURRENT_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) 4 | file(READ "${MY_CURRENT_DIR}/README.rst" DOCUMENTATION) 5 | 6 | # itk_module() defines the module dependencies in TwoProjectionRegistration 7 | # TwoProjectionRegistration depends on ITKCommon 8 | # The testing module in TwoProjectionRegistration depends on ITKTestKernel 9 | # and ITKMetaIO(besides TwoProjectionRegistration and ITKCore) 10 | # By convention those modules outside of ITK are not prefixed with 11 | # ITK. 12 | 13 | # define the dependencies of the include module and the tests 14 | itk_module(TwoProjectionRegistration 15 | DEPENDS 16 | ITKImageFunction 17 | ITKImageGradient 18 | ITKOptimizers 19 | ITKRegistrationCommon 20 | ITKSpatialObjects 21 | ITKTransform 22 | TEST_DEPENDS 23 | ITKImageFunction 24 | ITKImageGradient 25 | ITKOptimizers 26 | ITKRegistrationCommon 27 | ITKSpatialObjects 28 | ITKTransform 29 | ITKTestKernel 30 | DESCRIPTION 31 | "${DOCUMENTATION}" 32 | EXCLUDE_FROM_DEFAULT 33 | # Header only libraries do not use ENABLE_SHARED 34 | ) 35 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["scikit-build-core"] 3 | build-backend = "scikit_build_core.build" 4 | 5 | [project] 6 | name = "itk-twoprojectionregistration" 7 | version = "2.0.0" 8 | description = "ITK classes for two-projection 2D/3D registration" 9 | readme = "README.rst" 10 | license = {file = "LICENSE"} 11 | authors = [ 12 | { name = "Jian Wu", email = "jwu2@ufl.edu" }, 13 | ] 14 | keywords = [ 15 | "itk", 16 | "InsightToolkit", 17 | "Projection", 18 | "Registration", 19 | ] 20 | classifiers = [ 21 | "Development Status :: 4 - Beta", 22 | "Intended Audience :: Developers", 23 | "Intended Audience :: Education", 24 | "Intended Audience :: Healthcare Industry", 25 | "Intended Audience :: Science/Research", 26 | "License :: OSI Approved :: Apache Software License", 27 | "Operating System :: Android", 28 | "Operating System :: MacOS", 29 | "Operating System :: Microsoft :: Windows", 30 | "Operating System :: POSIX", 31 | "Operating System :: Unix", 32 | "Programming Language :: C++", 33 | "Programming Language :: Python", 34 | "Topic :: Scientific/Engineering", 35 | "Topic :: Scientific/Engineering :: Information Analysis", 36 | "Topic :: Scientific/Engineering :: Medical Science Apps.", 37 | "Topic :: Software Development :: Libraries", 38 | ] 39 | requires-python = ">=3.8" 40 | dependencies = [ 41 | "itk == 5.4.*", 42 | ] 43 | 44 | [project.urls] 45 | Download = "https://github.com/InsightSoftwareConsortium/ITKTwoProjectionRegistration" 46 | Homepage = "https://github.com/InsightSoftwareConsortium/ITKTwoProjectionRegistration" 47 | 48 | [tool.scikit-build] 49 | # The versions of CMake to allow. If CMake is not present on the system or does 50 | # not pass this specifier, it will be downloaded via PyPI if possible. An empty 51 | # string will disable this check. 52 | cmake.version = ">=3.16.3" 53 | 54 | # A list of args to pass to CMake when configuring the project. Setting this in 55 | # config or envvar will override toml. See also ``cmake.define``. 56 | cmake.args = [] 57 | 58 | # A table of defines to pass to CMake when configuring the project. Additive. 59 | cmake.define = {} 60 | 61 | # Verbose printout when building. 62 | cmake.verbose = true 63 | 64 | # The build type to use when building the project. Valid options are: "Debug", 65 | # "Release", "RelWithDebInfo", "MinSizeRel", "", etc. 66 | cmake.build-type = "Release" 67 | 68 | # The source directory to use when building the project. Currently only affects 69 | # the native builder (not the setuptools plugin). 70 | cmake.source-dir = "." 71 | 72 | # The versions of Ninja to allow. If Ninja is not present on the system or does 73 | # not pass this specifier, it will be downloaded via PyPI if possible. An empty 74 | # string will disable this check. 75 | ninja.version = ">=1.11" 76 | 77 | # The logging level to display, "DEBUG", "INFO", "WARNING", and "ERROR" are 78 | # possible options. 79 | logging.level = "INFO" 80 | 81 | # Files to include in the SDist even if they are skipped by default. Supports 82 | # gitignore syntax. 83 | sdist.include = [] 84 | 85 | # Files to exclude from the SDist even if they are included by default. Supports 86 | # gitignore syntax. 87 | sdist.exclude = [] 88 | 89 | # A list of license files to include in the wheel. Supports glob patterns. 90 | wheel.license-files = ["LICEN[CS]E*",] 91 | 92 | # Target the platlib or the purelib. If not set, the default is to target the 93 | # platlib if wheel.cmake is true, and the purelib otherwise. 94 | wheel.platlib = "false" 95 | 96 | # If CMake is less than this value, backport a copy of FindPython. Set to 0 97 | # disable this, or the empty string. 98 | backport.find-python = "3.26.1" 99 | 100 | # Select the editable mode to use. Can be "redirect" (default) or "inplace". 101 | editable.mode = "redirect" 102 | 103 | # Rebuild the project when the package is imported. The build-directory must be 104 | # set. 105 | editable.rebuild = false 106 | 107 | # If set, this will provide a method for scikit-build-core backward compatibility. 108 | minimum-version = "0.8.2" 109 | 110 | # The build directory. Defaults to a temporary directory, but can be set. 111 | build-dir = "build/{wheel_tag}" 112 | -------------------------------------------------------------------------------- /test/Baseline/BoxheadDRRFullDev1_G0_Reg-Author.tif.md5: -------------------------------------------------------------------------------- 1 | 595272c3fa0752bd21949c8b6147a8ae 2 | -------------------------------------------------------------------------------- /test/Baseline/BoxheadDRRFullDev1_G90_Reg-Author.tif.md5: -------------------------------------------------------------------------------- 1 | 5f08d61e43c73c3f506af25d8b18f9b9 2 | -------------------------------------------------------------------------------- /test/Baseline/BoxheadDRRFullDev1_G90_Reg.tif.md5: -------------------------------------------------------------------------------- 1 | 5f08d61e43c73c3f506af25d8b18f9b9 -------------------------------------------------------------------------------- /test/Baseline/boxheadDRRDev1_G0_Reg-Author.tif.md5: -------------------------------------------------------------------------------- 1 | 367a10e5c06588b28e50b8d403aebce8 2 | -------------------------------------------------------------------------------- /test/Baseline/boxheadDRRDev1_G90_Reg-Author.tif.md5: -------------------------------------------------------------------------------- 1 | 526d1871124b36daf4d7558a13dd8743 2 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | itk_module_test() 2 | 3 | set(TwoProjectionRegistrationTests 4 | TwoProjection2D3DRegistration.cxx 5 | GetDRRSiddonJacobsRayTracing.cxx 6 | ) 7 | 8 | CreateTestDriver(TwoProjectionRegistration "${TwoProjectionRegistration-Test_LIBRARIES}" "${TwoProjectionRegistrationTests}") 9 | 10 | itk_add_test(NAME TwoProjection2D3DRegistrationDownSizedCTTest 11 | COMMAND TwoProjectionRegistrationTestDriver TwoProjection2D3DRegistration 12 | -res 1 1 1 1 13 | -iso 99.62 101.18 65 14 | -o ${ITK_TEST_OUTPUT_DIR}/boxheadDRRDev1_G0_Reg.tif ${ITK_TEST_OUTPUT_DIR}/boxheadDRRDev1_G90_Reg.tif 15 | DATA{Input/boxheadDRRDev1_G0.tif} 0 16 | DATA{Input/boxheadDRRDev1_G90.tif} 90 17 | DATA{Input/BoxheadCT.img,BoxheadCT.hdr} 18 | ) 19 | 20 | itk_add_test(NAME TwoProjection2D3DRegistrationFullSizeCTTest 21 | COMMAND TwoProjectionRegistrationTestDriver TwoProjection2D3DRegistration 22 | -res 0.5 0.5 0.5 0.5 23 | -iso 255 259 130 24 | -o ${ITK_TEST_OUTPUT_DIR}/BoxheadDRRFullDev1_G0_Reg.tif ${ITK_TEST_OUTPUT_DIR}/BoxheadDRRFullDev1_G90_Reg.tif 25 | DATA{Input/BoxheadDRRFullDev1_G0.tif} 0 26 | DATA{Input/BoxheadDRRFullDev1_G90.tif} 90 27 | DATA{Input/BoxheadCTFull.img,BoxheadCTFull.hdr} 28 | ) 29 | set_property(TEST TwoProjection2D3DRegistrationFullSizeCTTest APPEND PROPERTY LABELS RUNS_LONG) 30 | 31 | itk_add_test(NAME GetDRRSiddonJacobsRayTracingDownSizedCTTest1 32 | COMMAND TwoProjectionRegistrationTestDriver GetDRRSiddonJacobsRayTracing 33 | -rp 0 -rx -3 -ry 4 -rz 2 -t 5 5 5 34 | -iso 99.62 101.18 65 -res 1 1 35 | -size 256 256 36 | -o ${ITK_TEST_OUTPUT_DIR}/boxheadDRRDev1_G0.tif 37 | DATA{Input/BoxheadCT.img,BoxheadCT.hdr} 38 | ) 39 | 40 | itk_add_test(NAME GetDRRSiddonJacobsRayTracingDownSizedCTTest2 41 | COMMAND TwoProjectionRegistrationTestDriver GetDRRSiddonJacobsRayTracing 42 | -rp 90 -rx -3 -ry 4 -rz 2 -t 5 5 5 43 | -iso 99.62 101.18 65 -res 1 1 44 | -size 256 256 45 | -o ${ITK_TEST_OUTPUT_DIR}/boxheadDRRDev1_G90.tif 46 | DATA{Input/BoxheadCT.img,BoxheadCT.hdr} 47 | ) 48 | 49 | itk_add_test(NAME GetDRRSiddonJacobsRayTracingFullSizedCTTest1 50 | COMMAND TwoProjectionRegistrationTestDriver GetDRRSiddonJacobsRayTracing 51 | -rp 0 -rx -3 -ry 4 -rz 2 -t 5 5 5 52 | -iso 255 259 130 -res 0.5 0.5 53 | -size 512 512 54 | -o ${ITK_TEST_OUTPUT_DIR}/BoxheadDRRFullDev1_G0.tif 55 | DATA{Input/BoxheadCTFull.img,BoxheadCTFull.hdr} 56 | ) 57 | 58 | itk_add_test(NAME GetDRRSiddonJacobsRayTracingFullSizedCTTest2 59 | COMMAND TwoProjectionRegistrationTestDriver GetDRRSiddonJacobsRayTracing 60 | -rp 90 -rx -3 -ry 4 -rz 2 -t 5 5 5 61 | -iso 255 259 130 -res 0.5 0.5 62 | -size 512 512 63 | -o ${ITK_TEST_OUTPUT_DIR}/BoxheadDRRFullDev1_G90.tif 64 | DATA{Input/BoxheadCTFull.img,BoxheadCTFull.hdr} 65 | ) 66 | -------------------------------------------------------------------------------- /test/DicomSeriesReadNiftiImageWrite.cxx: -------------------------------------------------------------------------------- 1 | /*========================================================================= 2 | 3 | Program: Insight Segmentation & Registration Toolkit 4 | Module: DicomSeriesReadNiftiImageWrite.cxx 5 | Language: C++ 6 | Date: Date: 2010/12/13 7 | Version: 1.0 8 | Author: Jian Wu (eewujian@hotmail.com) 9 | Univerisity of Florida 10 | Virginia Commonwealth University 11 | 12 | This program read a DICOM series into a volume and then save this volume in NIFTI 13 | file format. 14 | 15 | This program was modified from the ITK example--DicomSeriesReadSeriesWrite.cxx 16 | 17 | =========================================================================*/ 18 | // Software Guide : BeginLatex 19 | // 20 | // Probably the most common representation of datasets in clinical 21 | // applications is the one that uses sets of DICOM slices in order to compose 22 | // tridimensional images. This is the case for CT, MRI and PET scanners. It is 23 | // very common therefore for image analysts to have to process volumetric 24 | // images that are stored in the form of a set of DICOM files belonging to a 25 | // common DICOM series. 26 | // 27 | // The following example illustrates how to use ITK functionalities in order 28 | // to read a DICOM series into a volume and then save this volume in NIFTI 29 | // file format. 30 | // 31 | // The example begins by including the appropriate headers. In particular we 32 | // will need the \doxygen{GDCMImageIO} object in order to have access to the 33 | // capabilities of the GDCM library for reading DICOM files, and the 34 | // \doxygen{GDCMSeriesFileNames} object for generating the lists of filenames 35 | // identifying the slices of a common volumetric dataset. 36 | // 37 | // \index{itk::ImageSeriesReader!header} 38 | // \index{itk::GDCMImageIO!header} 39 | // \index{itk::GDCMSeriesFileNames!header} 40 | // \index{itk::ImageFileWriter!header} 41 | // 42 | // Software Guide : EndLatex 43 | 44 | // Software Guide : BeginCodeSnippet 45 | #include "itkGDCMImageIO.h" 46 | #include "itkGDCMSeriesFileNames.h" 47 | #include "itkImageSeriesReader.h" 48 | #include "itkImageFileWriter.h" 49 | #include "itkNiftiImageIO.h" 50 | // Software Guide : EndCodeSnippet 51 | 52 | 53 | int 54 | main(int argc, char * argv[]) 55 | { 56 | 57 | if (argc < 3) 58 | { 59 | std::cerr << "Usage: " << std::endl; 60 | std::cerr << argv[0] << " DicomDirectory outputFileName [seriesName]" << std::endl; 61 | return EXIT_FAILURE; 62 | } 63 | 64 | 65 | // Software Guide : BeginLatex 66 | // 67 | // We define the pixel type and dimension of the image to be read. In this 68 | // particular case, the dimensionality of the image is 3, and we assume a 69 | // \code{signed short} pixel type that is commonly used for X-Rays CT scanners. 70 | // 71 | // Software Guide : EndLatex 72 | 73 | // Software Guide : BeginCodeSnippet 74 | using PixelType = signed short; 75 | constexpr unsigned int Dimension = 3; 76 | 77 | using ImageType = itk::Image; 78 | // Software Guide : EndCodeSnippet 79 | 80 | 81 | // Software Guide : BeginLatex 82 | // 83 | // We use the image type for instantiating the type of the series reader and 84 | // for constructing one object of its type. 85 | // 86 | // Software Guide : EndLatex 87 | 88 | // Software Guide : BeginCodeSnippet 89 | using ReaderType = itk::ImageSeriesReader; 90 | ReaderType::Pointer reader = ReaderType::New(); 91 | // Software Guide : EndCodeSnippet 92 | 93 | 94 | // Software Guide : BeginLatex 95 | // 96 | // A GDCMImageIO object is created and connected to the reader. This object is 97 | // the one that is aware of the internal intricacies of the DICOM format. 98 | // 99 | // Software Guide : EndLatex 100 | 101 | // Software Guide : BeginCodeSnippet 102 | using ImageIOType = itk::GDCMImageIO; 103 | ImageIOType::Pointer dicomIO = ImageIOType::New(); 104 | 105 | reader->SetImageIO(dicomIO); 106 | // Software Guide : EndCodeSnippet 107 | 108 | 109 | // Software Guide : BeginLatex 110 | // 111 | // Now we face one of the main challenges of the process of reading a DICOM 112 | // series. That is, to identify from a given directory the set of filenames 113 | // that belong together to the same volumetric image. Fortunately for us, GDCM 114 | // offers functionalities for solving this problem and we just need to invoke 115 | // those functionalities through an ITK class that encapsulates a communication 116 | // with GDCM classes. This ITK object is the GDCMSeriesFileNames. Conveniently 117 | // for us, we only need to pass to this class the name of the directory where 118 | // the DICOM slices are stored. This is done with the \code{SetDirectory()} 119 | // method. The GDCMSeriesFileNames object will explore the directory and will 120 | // generate a sequence of filenames for DICOM files for one study/series. 121 | // In this example, we also call the \code{SetUseSeriesDetails(true)} function 122 | // that tells the GDCMSereiesFileNames object to use additional DICOM 123 | // information to distinguish unique volumes within the directory. This is 124 | // useful, for example, if a DICOM device assigns the same SeriesID to 125 | // a scout scan and its 3D volume; by using additional DICOM information 126 | // the scout scan will not be included as part of the 3D volume. Note that 127 | // \code{SetUseSeriesDetails(true)} must be called prior to calling 128 | // \code{SetDirectory()}. By default \code{SetUseSeriesDetails(true)} will use 129 | // the following DICOM tags to sub-refine a set of files into multiple series: 130 | // * 0020 0011 Series Number 131 | // * 0018 0024 Sequence Name 132 | // * 0018 0050 Slice Thickness 133 | // * 0028 0010 Rows 134 | // * 0028 0011 Columns 135 | // If this is not enough for your specific case you can always add some more 136 | // restrictions using the \code{AddSeriesRestriction()} method. In this example we will use 137 | // the DICOM Tag: 0008 0021 DA 1 Series Date, to sub-refine each series. The format 138 | // for passing the argument is a string containing first the group then the element 139 | // of the DICOM tag, separed by a pipe (|) sign. 140 | // 141 | // 142 | // \index{itk::GDCMSeriesFileNames!SetDirectory()} 143 | // 144 | // Software Guide : EndLatex 145 | 146 | // Software Guide : BeginCodeSnippet 147 | using NamesGeneratorType = itk::GDCMSeriesFileNames; 148 | NamesGeneratorType::Pointer nameGenerator = NamesGeneratorType::New(); 149 | 150 | nameGenerator->SetUseSeriesDetails(true); 151 | nameGenerator->AddSeriesRestriction("0008|0021"); 152 | 153 | nameGenerator->SetDirectory(argv[1]); 154 | // Software Guide : EndCodeSnippet 155 | 156 | 157 | try 158 | { 159 | std::cout << std::endl << "The directory: " << std::endl; 160 | std::cout << std::endl << argv[1] << std::endl << std::endl; 161 | std::cout << "Contains the following DICOM Series: "; 162 | std::cout << std::endl << std::endl; 163 | 164 | 165 | // Software Guide : BeginLatex 166 | // 167 | // The GDCMSeriesFileNames object first identifies the list of DICOM series 168 | // that are present in the given directory. We receive that list in a reference 169 | // to a container of strings and then we can do things like printing out all 170 | // the series identifiers that the generator had found. Since the process of 171 | // finding the series identifiers can potentially throw exceptions, it is 172 | // wise to put this code inside a try/catch block. 173 | // 174 | // Software Guide : EndLatex 175 | 176 | // Software Guide : BeginCodeSnippet 177 | using SeriesIdContainer = std::vector; 178 | 179 | const SeriesIdContainer & seriesUID = nameGenerator->GetSeriesUIDs(); 180 | 181 | SeriesIdContainer::const_iterator seriesItr = seriesUID.begin(); 182 | SeriesIdContainer::const_iterator seriesEnd = seriesUID.end(); 183 | while (seriesItr != seriesEnd) 184 | { 185 | std::cout << seriesItr->c_str() << std::endl; 186 | seriesItr++; 187 | } 188 | // Software Guide : EndCodeSnippet 189 | 190 | 191 | // Software Guide : BeginLatex 192 | // 193 | // Given that it is common to find multiple DICOM series in the same directory, 194 | // we must tell the GDCM classes what specific series do we want to read. In 195 | // this example we do this by checking first if the user has provided a series 196 | // identifier in the command line arguments. If no series identifier has been 197 | // passed, then we simply use the first series found during the exploration of 198 | // the directory. 199 | // 200 | // Software Guide : EndLatex 201 | 202 | // Software Guide : BeginCodeSnippet 203 | std::string seriesIdentifier; 204 | 205 | if (argc > 3) // If no optional series identifier 206 | { 207 | seriesIdentifier = argv[3]; 208 | } 209 | else 210 | { 211 | seriesIdentifier = seriesUID.begin()->c_str(); 212 | } 213 | // Software Guide : EndCodeSnippet 214 | 215 | 216 | std::cout << std::endl << std::endl; 217 | std::cout << "Now reading series: " << std::endl << std::endl; 218 | std::cout << seriesIdentifier << std::endl; 219 | std::cout << std::endl << std::endl; 220 | 221 | 222 | // Software Guide : BeginLatex 223 | // 224 | // We pass the series identifier to the name generator and ask for all the 225 | // filenames associated to that series. This list is returned in a container of 226 | // strings by the \code{GetFileNames()} method. 227 | // 228 | // \index{itk::GDCMSeriesFileNames!GetFileNames()} 229 | // 230 | // Software Guide : EndLatex 231 | 232 | // Software Guide : BeginCodeSnippet 233 | using FileNamesContainer = std::vector; 234 | FileNamesContainer fileNames; 235 | 236 | fileNames = nameGenerator->GetFileNames(seriesIdentifier); 237 | // Software Guide : EndCodeSnippet 238 | 239 | 240 | // Software Guide : BeginLatex 241 | // 242 | // 243 | // The list of filenames can now be passed to the \doxygen{ImageSeriesReader} 244 | // using the \code{SetFileNames()} method. 245 | // 246 | // \index{itk::ImageSeriesReader!SetFileNames()} 247 | // 248 | // Software Guide : EndLatex 249 | 250 | // Software Guide : BeginCodeSnippet 251 | reader->SetFileNames(fileNames); 252 | // Software Guide : EndCodeSnippet 253 | 254 | 255 | // Software Guide : BeginLatex 256 | // 257 | // Finally we can trigger the reading process by invoking the \code{Update()} 258 | // method in the reader. This call as usual is placed inside a \code{try/catch} 259 | // block. 260 | // 261 | // Software Guide : EndLatex 262 | 263 | // Software Guide : BeginCodeSnippet 264 | try 265 | { 266 | reader->Update(); 267 | } 268 | catch (itk::ExceptionObject & ex) 269 | { 270 | std::cout << ex << std::endl; 271 | return EXIT_FAILURE; 272 | } 273 | // Software Guide : EndCodeSnippet 274 | 275 | 276 | // Software Guide : BeginLatex 277 | // 278 | // At this point, we have a volumetric image in memory that we can access by 279 | // invoking the \code{GetOutput()} method of the reader. 280 | // 281 | // Software Guide : EndLatex 282 | 283 | 284 | // Software Guide : BeginLatex 285 | // 286 | // We proceed now to save the volumetric image in another file, as specified by 287 | // the user in the command line arguments of this program. Thanks to the 288 | // ImageIO factory mechanism, only the filename extension is needed to identify 289 | // the file format in this case. 290 | // 291 | // Software Guide : EndLatex 292 | 293 | // Software Guide : BeginCodeSnippet 294 | using WriterType = itk::ImageFileWriter; 295 | WriterType::Pointer writer = WriterType::New(); 296 | 297 | using ImageIOType = itk::NiftiImageIO; 298 | ImageIOType::Pointer niftiIO = ImageIOType::New(); 299 | 300 | // The NiftiImageIO object is then connected to the 301 | // ImageFileWriter. This will short-circuit the action of the 302 | // ImageIOFactory mechanism. The ImageFileWriter will 303 | // not attempt to look for other ImageIO objects capable of 304 | // performing the writing tasks. It will simply invoke the one provided by 305 | // the user. 306 | writer->SetImageIO(niftiIO); 307 | 308 | writer->SetFileName(argv[2]); 309 | 310 | writer->SetInput(reader->GetOutput()); 311 | // Software Guide : EndCodeSnippet 312 | 313 | std::cout << "Writing the image as " << std::endl << std::endl; 314 | std::cout << argv[2] << std::endl << std::endl; 315 | 316 | 317 | // Software Guide : BeginLatex 318 | // 319 | // The process of writing the image is initiated by invoking the 320 | // \code{Update()} method of the writer. 321 | // 322 | // Software Guide : EndLatex 323 | 324 | try 325 | { 326 | // Software Guide : BeginCodeSnippet 327 | writer->Update(); 328 | // Software Guide : EndCodeSnippet 329 | } 330 | catch (itk::ExceptionObject & ex) 331 | { 332 | std::cout << ex << std::endl; 333 | return EXIT_FAILURE; 334 | } 335 | } 336 | catch (itk::ExceptionObject & ex) 337 | { 338 | std::cout << ex << std::endl; 339 | return EXIT_FAILURE; 340 | } 341 | 342 | 343 | // Software Guide : BeginLatex 344 | // 345 | // Note that in addition to writing the volumetric image to a file we could 346 | // have used it as the input for any 3D processing pipeline. Keep in mind that 347 | // DICOM is simply a file format and a network protocol. Once the image data 348 | // has been loaded into memory, it behaves as any other volumetric dataset that 349 | // you could have loaded from any other file format. 350 | // 351 | // Software Guide : EndLatex 352 | 353 | 354 | return EXIT_SUCCESS; 355 | } 356 | -------------------------------------------------------------------------------- /test/Docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:8 2 | MAINTAINER Insight Software Consortium 3 | 4 | RUN apt-get update && apt-get install -y \ 5 | build-essential \ 6 | curl \ 7 | cmake \ 8 | git \ 9 | libexpat1-dev \ 10 | libhdf5-dev \ 11 | libjpeg-dev \ 12 | libpng12-dev \ 13 | libpython3-dev \ 14 | libtiff5-dev \ 15 | python \ 16 | ninja-build \ 17 | wget \ 18 | vim \ 19 | zlib1g-dev 20 | 21 | RUN mkdir -p /usr/src/ITKTwoProjectionRegistration-build 22 | WORKDIR /usr/src 23 | 24 | # 2015-12-16 25 | ENV ITK_GIT_TAG 53ad7f621df9772539aa3f943c368edc0c54dfe0 26 | RUN git clone git://itk.org/ITK.git && \ 27 | cd ITK && \ 28 | git checkout ${ITK_GIT_TAG} && \ 29 | cd ../ && \ 30 | mkdir ITK-build && \ 31 | cd ITK-build && \ 32 | cmake \ 33 | -G Ninja \ 34 | -DCMAKE_INSTALL_PREFIX:PATH=/usr \ 35 | -DBUILD_EXAMPLES:BOOL=OFF \ 36 | -DBUILD_TESTING:BOOL=OFF \ 37 | -DBUILD_SHARED_LIBS:BOOL=ON \ 38 | -DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON \ 39 | -DITK_LEGACY_REMOVE:BOOL=ON \ 40 | -DITK_BUILD_DEFAULT_MODULES:BOOL=OFF \ 41 | -DITK_USE_SYSTEM_LIBRARIES:BOOL=ON \ 42 | -DModule_ITKImageFunction:BOOL=ON \ 43 | -DModule_ITKImageGradient:BOOL=ON \ 44 | -DModule_ITKOptimizers:BOOL=ON \ 45 | -DModule_ITKRegistrationCommon:BOOL=ON \ 46 | -DModule_ITKSpatialObjects:BOOL=ON \ 47 | -DModule_ITKTransform:BOOL=ON \ 48 | -DModule_ITKTestKernel:BOOL=ON \ 49 | ../ITK && \ 50 | ninja && \ 51 | find . -name '*.o' -delete 52 | -------------------------------------------------------------------------------- /test/Docker/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | script_dir="`cd $(dirname $0); pwd`" 4 | 5 | docker build -t insighttoolkit/twoprojectionregistration-test $script_dir 6 | -------------------------------------------------------------------------------- /test/Docker/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | script_dir="`cd $(dirname $0); pwd`" 4 | 5 | docker run \ 6 | --rm \ 7 | -v $script_dir/../..:/usr/src/ITKTwoProjectionRegistration \ 8 | insighttoolkit/twoprojectionregistration-test \ 9 | /usr/src/ITKTwoProjectionRegistration/test/Docker/test.sh 10 | -------------------------------------------------------------------------------- /test/Docker/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This is a script to build the modules and run the test suite in the base 4 | # Docker container. 5 | 6 | die() { 7 | echo "Error: $@" 1>&2 8 | exit 1; 9 | } 10 | 11 | cd /usr/src/ITKTwoProjectionRegistration-build || die "Could not cd into the build directory" 12 | 13 | cmake \ 14 | -G Ninja \ 15 | -DITK_DIR:PATH=/usr/src/ITK-build \ 16 | -DCMAKE_BUILD_TYPE:STRING=Release \ 17 | /usr/src/ITKTwoProjectionRegistration || die "CMake configuration failed" 18 | ctest -VV -D Experimental || die "ctest failed" 19 | -------------------------------------------------------------------------------- /test/GetDRRSiddonJacobsRayTracing.cxx: -------------------------------------------------------------------------------- 1 | /*========================================================================= 2 | 3 | Program: Insight Segmentation & Registration Toolkit 4 | Module: getDRRSiddonJacobsRayTracing.cxx 5 | Language: C++ 6 | Date: 2010/12/15 7 | Version: 1.0 8 | Author: Jian Wu (eewujian@hotmail.com) 9 | Univerisity of Florida 10 | Virginia Commonwealth University 11 | 12 | This program compute digitally reconstructed radiograph (DRR) by projecting 13 | a 3D CT image into a 2D image plane using Siddon-Jacob ray-tracing algorithm. 14 | 15 | This program was modified from the ITK application--GenerateProjection.cxx 16 | 17 | ------------------------------------------------------------------------- 18 | References: 19 | 20 | R. L. Siddon, "Fast calculation of the exact radiological path for a 21 | threedimensional CT array," Medical Physics 12, 252-55 (1985). 22 | 23 | F. Jacobs, E. Sundermann, B. De Sutter, M. Christiaens, and I. Lemahieu, 24 | "A fast algorithm to calculate the exact radiological path through a pixel 25 | or voxel space," Journal of Computing and Information Technology ? 26 | CIT 6, 89-94 (1998). 27 | 28 | =========================================================================*/ 29 | 30 | 31 | // This example illustrates the use of the ResampleImageFilter and 32 | // SiddonJacobsRayCastInterpolateImageFunction to generate digitally 33 | // reconstructed radiographs (DRRs) from a 3D CT image volume. 34 | 35 | // The program attempts to generate the simulated x-ray images that can 36 | // be acquired when an imager is attached to a linear accelerator. 37 | 38 | #include "itkTimeProbesCollectorBase.h" 39 | #include "itkImage.h" 40 | #include "itkImageFileReader.h" 41 | #include "itkImageFileWriter.h" 42 | #include "itkResampleImageFilter.h" 43 | #include "itkImageRegionIteratorWithIndex.h" 44 | #include "itkRescaleIntensityImageFilter.h" 45 | #include "itkFlipImageFilter.h" 46 | 47 | #include "itkEuler3DTransform.h" 48 | #include "itkSiddonJacobsRayCastInterpolateImageFunction.h" 49 | 50 | 51 | void 52 | raytracing_exe_usage() 53 | { 54 | std::cerr << "\n"; 55 | std::cerr << "Usage: getDRRSiddonJacobsRayTracing [input]\n"; 56 | std::cerr << " calculates the Digitally Reconstructed Radiograph from \n"; 57 | std::cerr << " a CT image using Siddon-Jacob ray-tracing algorithm. \n\n"; 58 | std::cerr << " where is one or more of the following:\n\n"; 59 | std::cerr << " <-h> Display (this) usage information\n"; 60 | std::cerr << " <-v> Verbose output [default: no]\n"; 61 | std::cerr 62 | << " <-res float float> DRR Pixel spacing in isocenter plane in mm [default: 0.51mm 0.51mm] \n"; 63 | std::cerr << " <-size int int> Size of DRR in number of pixels [default: 512x512] \n"; 64 | std::cerr 65 | << " <-scd float> Source to isocenter (i.e., 3D image center) distance in mm [default: 1000mm]\n"; 66 | std::cerr << " <-t float float float> CT volume translation in x, y, and z direction in mm \n"; 67 | std::cerr << " <-rx float> CT volume rotation about x axis in degrees \n"; 68 | std::cerr << " <-ry float> CT volume rotation about y axis in degrees \n"; 69 | std::cerr << " <-rz float> CT volume rotation about z axis in degrees \n"; 70 | std::cerr << " <-2dcx float float> Central axis position of DRR in continuous indices \n"; 71 | std::cerr << " <-iso float float float> Continous voxel indices of CT isocenter (center of rotation and " 72 | "projection center)\n"; 73 | std::cerr << " <-rp float> Projection angle in degrees"; 74 | std::cerr << " <-threshold float> CT intensity threshold, below which are ignored [default: 0]\n"; 75 | std::cerr << " <-o file> Output image filename\n\n"; 76 | std::cerr << " by Jian Wu (eewujian@hotmail.com)\n\n"; 77 | exit(EXIT_FAILURE); 78 | } 79 | 80 | 81 | int 82 | GetDRRSiddonJacobsRayTracing(int argc, char * argv[]) 83 | { 84 | char * input_name = nullptr; 85 | char * output_name = nullptr; 86 | 87 | bool ok; 88 | bool verbose = false; 89 | bool customized_iso = false; // Flag for customized 3D image isocenter positions 90 | bool customized_2DCX = false; // Flag for customized 2D image central axis positions 91 | 92 | float rprojection = 0.; // Projection angle in degrees 93 | 94 | // CT volume rotation around isocenter along x,y,z axis in degrees 95 | float rx = 0.; 96 | float ry = 0.; 97 | float rz = 0.; 98 | 99 | // Translation parameter of the isocenter in mm 100 | float tx = 0.; 101 | float ty = 0.; 102 | float tz = 0.; 103 | 104 | // The pixel indices of the isocenter 105 | float cx = 0.; 106 | float cy = 0.; 107 | float cz = 0.; 108 | 109 | float scd = 1000.0; // Source to isocenter distance in mm 110 | 111 | // Default pixel spacing in the iso-center plane in mm 112 | float im_sx = 0.51; 113 | float im_sy = 0.51; 114 | 115 | // Size of the output image in number of pixels 116 | int dx = 512; 117 | int dy = 512; 118 | 119 | // The central axis positions of the 2D images in continuous indices 120 | float o2Dx = 0.0f; 121 | float o2Dy = 0.0f; 122 | 123 | float threshold = 0.; 124 | 125 | // Create a timer to record calculation time. 126 | itk::TimeProbesCollectorBase timer; 127 | 128 | // Parse command line parameters 129 | 130 | while (argc > 1) 131 | { 132 | ok = false; 133 | 134 | if ((ok == false) && (strcmp(argv[1], "-h") == 0)) 135 | { 136 | argc--; 137 | argv++; 138 | ok = true; 139 | raytracing_exe_usage(); 140 | } 141 | 142 | if ((ok == false) && (strcmp(argv[1], "-v") == 0)) 143 | { 144 | argc--; 145 | argv++; 146 | ok = true; 147 | verbose = true; 148 | } 149 | 150 | if ((ok == false) && (strcmp(argv[1], "-rx") == 0)) 151 | { 152 | argc--; 153 | argv++; 154 | ok = true; 155 | rx = atof(argv[1]); 156 | argc--; 157 | argv++; 158 | } 159 | 160 | if ((ok == false) && (strcmp(argv[1], "-ry") == 0)) 161 | { 162 | argc--; 163 | argv++; 164 | ok = true; 165 | ry = atof(argv[1]); 166 | argc--; 167 | argv++; 168 | } 169 | 170 | if ((ok == false) && (strcmp(argv[1], "-rz") == 0)) 171 | { 172 | argc--; 173 | argv++; 174 | ok = true; 175 | rz = atof(argv[1]); 176 | argc--; 177 | argv++; 178 | } 179 | 180 | if ((ok == false) && (strcmp(argv[1], "-threshold") == 0)) 181 | { 182 | argc--; 183 | argv++; 184 | ok = true; 185 | threshold = atof(argv[1]); 186 | argc--; 187 | argv++; 188 | } 189 | 190 | if ((ok == false) && (strcmp(argv[1], "-t") == 0)) 191 | { 192 | argc--; 193 | argv++; 194 | ok = true; 195 | tx = atof(argv[1]); 196 | argc--; 197 | argv++; 198 | ty = atof(argv[1]); 199 | argc--; 200 | argv++; 201 | tz = atof(argv[1]); 202 | argc--; 203 | argv++; 204 | } 205 | 206 | if ((ok == false) && (strcmp(argv[1], "-iso") == 0)) 207 | { 208 | argc--; 209 | argv++; 210 | ok = true; 211 | cx = atof(argv[1]); 212 | argc--; 213 | argv++; 214 | cy = atof(argv[1]); 215 | argc--; 216 | argv++; 217 | cz = atof(argv[1]); 218 | argc--; 219 | argv++; 220 | customized_iso = true; 221 | } 222 | 223 | if ((ok == false) && (strcmp(argv[1], "-rp") == 0)) 224 | { 225 | argc--; 226 | argv++; 227 | ok = true; 228 | rprojection = atof(argv[1]); 229 | argc--; 230 | argv++; 231 | } 232 | 233 | if ((ok == false) && (strcmp(argv[1], "-res") == 0)) 234 | { 235 | argc--; 236 | argv++; 237 | ok = true; 238 | im_sx = atof(argv[1]); 239 | argc--; 240 | argv++; 241 | im_sy = atof(argv[1]); 242 | argc--; 243 | argv++; 244 | } 245 | 246 | if ((ok == false) && (strcmp(argv[1], "-size") == 0)) 247 | { 248 | argc--; 249 | argv++; 250 | ok = true; 251 | dx = atoi(argv[1]); 252 | argc--; 253 | argv++; 254 | dy = atoi(argv[1]); 255 | argc--; 256 | argv++; 257 | } 258 | 259 | if ((ok == false) && (strcmp(argv[1], "-scd") == 0)) 260 | { 261 | argc--; 262 | argv++; 263 | ok = true; 264 | scd = atof(argv[1]); 265 | argc--; 266 | argv++; 267 | } 268 | 269 | if ((ok == false) && (strcmp(argv[1], "-2dcx") == 0)) 270 | { 271 | argc--; 272 | argv++; 273 | ok = true; 274 | o2Dx = atof(argv[1]); 275 | argc--; 276 | argv++; 277 | o2Dy = atof(argv[1]); 278 | argc--; 279 | argv++; 280 | customized_2DCX = true; 281 | } 282 | 283 | if ((ok == false) && (strcmp(argv[1], "-o") == 0)) 284 | { 285 | argc--; 286 | argv++; 287 | ok = true; 288 | output_name = argv[1]; 289 | argc--; 290 | argv++; 291 | } 292 | 293 | if (ok == false) 294 | { 295 | if (input_name == nullptr) 296 | { 297 | input_name = argv[1]; 298 | argc--; 299 | argv++; 300 | } 301 | else 302 | { 303 | std::cerr << "ERROR: Can not parse argument " << argv[1] << std::endl; 304 | raytracing_exe_usage(); 305 | } 306 | } 307 | } 308 | 309 | if (verbose) 310 | { 311 | if (input_name) 312 | std::cout << "Input image: " << input_name << std::endl; 313 | if (output_name) 314 | std::cout << "Output image: " << output_name << std::endl; 315 | } 316 | 317 | // Although we generate a 2D projection of the 3D volume for the 318 | // purposes of the interpolator both images must be three dimensional. 319 | 320 | constexpr unsigned int Dimension = 3; 321 | using InputPixelType = short; 322 | using OutputPixelType = unsigned char; 323 | 324 | using InputImageType = itk::Image; 325 | using OutputImageType = itk::Image; 326 | 327 | InputImageType::Pointer image; 328 | 329 | // For the purposes of this example we assume the input volume has 330 | // been loaded into an {itk::Image image}. 331 | 332 | if (input_name) 333 | { 334 | timer.Start("Loading Input Image"); 335 | using ReaderType = itk::ImageFileReader; 336 | ReaderType::Pointer reader = ReaderType::New(); 337 | reader->SetFileName(input_name); 338 | 339 | try 340 | { 341 | reader->Update(); 342 | } 343 | catch (itk::ExceptionObject & err) 344 | { 345 | std::cerr << "ERROR: ExceptionObject caught !" << std::endl; 346 | std::cerr << err << std::endl; 347 | return EXIT_FAILURE; 348 | } 349 | 350 | image = reader->GetOutput(); 351 | timer.Stop("Loading Input Image"); 352 | } 353 | else 354 | { 355 | std::cerr << "Input image file missing !" << std::endl; 356 | return EXIT_FAILURE; 357 | } 358 | 359 | // To simply Siddon-Jacob's fast ray-tracing algorithm, we force the origin of the CT image 360 | // to be (0,0,0). Because we align the CT isocenter with the central axis, the projection 361 | // geometry is fully defined. The origin of the CT image becomes irrelavent. 362 | InputImageType::PointType ctOrigin; 363 | ctOrigin[0] = 0.0; 364 | ctOrigin[1] = 0.0; 365 | ctOrigin[2] = 0.0; 366 | image->SetOrigin(ctOrigin); 367 | 368 | // Print out the details of the input volume 369 | 370 | if (verbose) 371 | { 372 | unsigned int i; 373 | const itk::Vector spacing = image->GetSpacing(); 374 | std::cout << std::endl << "Input "; 375 | 376 | InputImageType::RegionType region = image->GetBufferedRegion(); 377 | region.Print(std::cout); 378 | 379 | std::cout << " Resolution: ["; 380 | for (i = 0; i < Dimension; i++) 381 | { 382 | std::cout << spacing[i]; 383 | if (i < Dimension - 1) 384 | std::cout << ", "; 385 | } 386 | std::cout << "]" << std::endl; 387 | 388 | const itk::Point origin = image->GetOrigin(); 389 | std::cout << " Origin: ["; 390 | for (i = 0; i < Dimension; i++) 391 | { 392 | std::cout << origin[i]; 393 | if (i < Dimension - 1) 394 | std::cout << ", "; 395 | } 396 | std::cout << "]" << std::endl << std::endl; 397 | } 398 | 399 | // Creation of a {ResampleImageFilter} enables coordinates for 400 | // each of the pixels in the DRR image to be generated. These 401 | // coordinates are used by the {RayCastInterpolateImageFunction} 402 | // to determine the equation of each corresponding ray which is cast 403 | // through the input volume. 404 | 405 | using FilterType = itk::ResampleImageFilter; 406 | 407 | FilterType::Pointer filter = FilterType::New(); 408 | 409 | filter->SetInput(image); 410 | filter->SetDefaultPixelValue(0); 411 | 412 | // An Euler transformation is defined to position the input volume. 413 | 414 | using TransformType = itk::Euler3DTransform; 415 | 416 | TransformType::Pointer transform = TransformType::New(); 417 | 418 | transform->SetComputeZYX(true); 419 | 420 | TransformType::OutputVectorType translation; 421 | 422 | translation[0] = tx; 423 | translation[1] = ty; 424 | translation[2] = tz; 425 | 426 | // constant for converting degrees into radians 427 | const double dtr = (atan(1.0) * 4.0) / 180.0; 428 | 429 | transform->SetTranslation(translation); 430 | transform->SetRotation(dtr * rx, dtr * ry, dtr * rz); 431 | 432 | InputImageType::PointType imOrigin = image->GetOrigin(); 433 | InputImageType::SpacingType imRes = image->GetSpacing(); 434 | 435 | using InputImageRegionType = InputImageType::RegionType; 436 | using InputImageSizeType = InputImageRegionType::SizeType; 437 | 438 | InputImageRegionType imRegion = image->GetBufferedRegion(); 439 | InputImageSizeType imSize = imRegion.GetSize(); 440 | 441 | TransformType::InputPointType isocenter; 442 | if (customized_iso) 443 | { 444 | // Isocenter location given by the user. 445 | isocenter[0] = imOrigin[0] + imRes[0] * cx; 446 | isocenter[1] = imOrigin[1] + imRes[1] * cy; 447 | isocenter[2] = imOrigin[2] + imRes[2] * cz; 448 | } 449 | else 450 | { 451 | // Set the center of the image as the isocenter. 452 | isocenter[0] = imOrigin[0] + imRes[0] * static_cast(imSize[0]) / 2.0; 453 | isocenter[1] = imOrigin[1] + imRes[1] * static_cast(imSize[1]) / 2.0; 454 | isocenter[2] = imOrigin[2] + imRes[2] * static_cast(imSize[2]) / 2.0; 455 | } 456 | transform->SetCenter(isocenter); 457 | 458 | if (verbose) 459 | { 460 | std::cout << "Image size: " << imSize[0] << ", " << imSize[1] << ", " << imSize[2] << std::endl 461 | << " resolution: " << imRes[0] << ", " << imRes[1] << ", " << imRes[2] << std::endl 462 | << " origin: " << imOrigin[0] << ", " << imOrigin[1] << ", " << imOrigin[2] << std::endl 463 | << " isocenter: " << isocenter[0] << ", " << isocenter[1] << ", " << isocenter[2] << std::endl 464 | << "Transform: " << transform << std::endl; 465 | } 466 | 467 | using InterpolatorType = itk::SiddonJacobsRayCastInterpolateImageFunction; 468 | InterpolatorType::Pointer interpolator = InterpolatorType::New(); 469 | 470 | interpolator->SetProjectionAngle(dtr * rprojection); // Set angle between projection central axis and -z axis 471 | interpolator->SetFocalPointToIsocenterDistance(scd); // Set source to isocenter distance 472 | interpolator->SetThreshold(threshold); // Set intensity threshold, below which are ignored. 473 | interpolator->SetTransform(transform); 474 | 475 | interpolator->Initialize(); 476 | 477 | filter->SetInterpolator(interpolator); 478 | 479 | 480 | // The size and resolution of the output DRR image is specified via the filter. 481 | 482 | // setup the scene 483 | InputImageType::SizeType size; 484 | double spacing[Dimension]; 485 | 486 | size[0] = dx; // number of pixels along X of the 2D DRR image 487 | size[1] = dy; // number of pixels along X of the 2D DRR image 488 | size[2] = 1; // only one slice 489 | spacing[0] = im_sx; // pixel spacing along X of the 2D DRR image [mm] 490 | spacing[1] = im_sy; // pixel spacing along Y of the 2D DRR image [mm] 491 | spacing[2] = 1.0; // slice thickness of the 2D DRR image [mm] 492 | 493 | filter->SetSize(size); 494 | 495 | filter->SetOutputSpacing(spacing); 496 | 497 | if (verbose) 498 | { 499 | std::cout << "Output image size: " << size[0] << ", " << size[1] << ", " << size[2] << std::endl; 500 | 501 | std::cout << "Output image spacing: " << spacing[0] << ", " << spacing[1] << ", " << spacing[2] << std::endl; 502 | } 503 | 504 | double origin[Dimension]; 505 | 506 | if (!customized_2DCX) 507 | { // Central axis positions are not given by the user. Use the image centers 508 | // as the central axis position. 509 | o2Dx = ((double)dx - 1.) / 2.; 510 | o2Dy = ((double)dy - 1.) / 2.; 511 | } 512 | 513 | // Compute the origin (in mm) of the 2D Image 514 | origin[0] = -im_sx * o2Dx; 515 | origin[1] = -im_sy * o2Dy; 516 | origin[2] = -scd; 517 | 518 | filter->SetOutputOrigin(origin); 519 | 520 | timer.Start("DRR generation"); 521 | filter->Update(); 522 | timer.Stop("DRR generation"); 523 | 524 | if (verbose) 525 | { 526 | std::cout << "Output image origin: " << origin[0] << ", " << origin[1] << ", " << origin[2] << std::endl; 527 | } 528 | 529 | // create writer 530 | 531 | if (output_name) 532 | { 533 | 534 | // The output of the filter can then be passed to a writer to 535 | // save the DRR image to a file. 536 | 537 | using RescaleFilterType = itk::RescaleIntensityImageFilter; 538 | RescaleFilterType::Pointer rescaler = RescaleFilterType::New(); 539 | rescaler->SetOutputMinimum(0); 540 | rescaler->SetOutputMaximum(255); 541 | rescaler->SetInput(filter->GetOutput()); 542 | 543 | timer.Start("DRR post-processing"); 544 | rescaler->Update(); 545 | 546 | // Out of some reason, the computed projection is upsided-down. 547 | // Here we use a FilpImageFilter to flip the images in y direction. 548 | using FlipFilterType = itk::FlipImageFilter; 549 | FlipFilterType::Pointer flipFilter = FlipFilterType::New(); 550 | 551 | using FlipAxesArrayType = FlipFilterType::FlipAxesArrayType; 552 | FlipAxesArrayType flipArray; 553 | flipArray[0] = false; 554 | flipArray[1] = true; 555 | 556 | flipFilter->SetFlipAxes(flipArray); 557 | flipFilter->SetInput(rescaler->GetOutput()); 558 | flipFilter->Update(); 559 | 560 | timer.Stop("DRR post-processing"); 561 | 562 | using WriterType = itk::ImageFileWriter; 563 | WriterType::Pointer writer = WriterType::New(); 564 | 565 | // Now we are ready to write the projection image. 566 | writer->SetFileName(output_name); 567 | writer->SetInput(flipFilter->GetOutput()); 568 | 569 | try 570 | { 571 | std::cout << "Writing image: " << output_name << std::endl; 572 | writer->Update(); 573 | } 574 | catch (itk::ExceptionObject & err) 575 | { 576 | std::cerr << "ERROR: ExceptionObject caught !" << std::endl; 577 | std::cerr << err << std::endl; 578 | } 579 | } 580 | else 581 | { 582 | filter->Update(); 583 | } 584 | 585 | timer.Report(); 586 | 587 | return EXIT_SUCCESS; 588 | } 589 | -------------------------------------------------------------------------------- /test/Input/BoxheadCT.hdr.md5: -------------------------------------------------------------------------------- 1 | bf888d8fb9d2bf4eabb9b7f2beb35208 2 | -------------------------------------------------------------------------------- /test/Input/BoxheadCT.img.md5: -------------------------------------------------------------------------------- 1 | 7fdaa6b03b27d795b9cdc097a47046ee 2 | -------------------------------------------------------------------------------- /test/Input/BoxheadCTFull.hdr.md5: -------------------------------------------------------------------------------- 1 | 02cf4333dea39d6c7fcd0b6bbec6515f 2 | -------------------------------------------------------------------------------- /test/Input/BoxheadCTFull.img.md5: -------------------------------------------------------------------------------- 1 | b4ed0e70fb4f381c7a3967c650b0a824 2 | -------------------------------------------------------------------------------- /test/Input/BoxheadDRRFullDev1_G0.tif.md5: -------------------------------------------------------------------------------- 1 | ebd2b58a0e7a58259939da4248fa108f 2 | -------------------------------------------------------------------------------- /test/Input/BoxheadDRRFullDev1_G90.tif.md5: -------------------------------------------------------------------------------- 1 | f89d9eb4b8f50cc92659eda0968110d0 -------------------------------------------------------------------------------- /test/Input/boxheadDRRDev1_G0.tif.md5: -------------------------------------------------------------------------------- 1 | e0391392a8e463e63c17b4bda0211cd0 2 | -------------------------------------------------------------------------------- /test/Input/boxheadDRRDev1_G90.tif.md5: -------------------------------------------------------------------------------- 1 | 034660fd002da511d8232692b85d6c3b 2 | -------------------------------------------------------------------------------- /test/ReadResampleWriteNifti.cxx: -------------------------------------------------------------------------------- 1 | /*========================================================================= 2 | 3 | Program: Insight Segmentation & Registration Toolkit 4 | Module: ReadResampleWriteNifti.cxx 5 | Language: C++ 6 | Date: Date: 2010/12/13 7 | Version: 1.0 8 | Author: Jian Wu (eewujian@hotmail.com) 9 | Univerisity of Florida 10 | Virginia Commonwealth University 11 | 12 | This Program read a 3D image volume, downsample it, and save it in NIFTI 13 | image format. 14 | 15 | This program was modified from the ITK example--ResampleImageFilter2.cxx 16 | 17 | =========================================================================*/ 18 | #include "itkImage.h" 19 | #include "itkImageFileReader.h" 20 | #include "itkImageFileWriter.h" 21 | #include "itkNiftiImageIO.h" 22 | #include "itkResampleImageFilter.h" 23 | #include "itkAffineTransform.h" 24 | #include "itkLinearInterpolateImageFunction.h" 25 | 26 | 27 | int 28 | main(int argc, char * argv[]) 29 | { 30 | if (argc < 3) 31 | { 32 | std::cerr << "Usage: " << std::endl; 33 | std::cerr << argv[0] << " inputImageFile outputImageFile" << std::endl; 34 | return EXIT_FAILURE; 35 | } 36 | 37 | if (argc > 3) 38 | { 39 | std::cerr << "Too many arguments" << std::endl; 40 | } 41 | 42 | constexpr unsigned int Dimension = 3; 43 | 44 | double outputSpacing[Dimension]; 45 | outputSpacing[0] = 2.0; // pixel spacing in millimeters along X 46 | outputSpacing[1] = 2.0; // pixel spacing in millimeters along Y 47 | outputSpacing[2] = 2.0; // pixel spacing in millimeters along Z 48 | 49 | using InputPixelType = short; 50 | using OutputPixelType = short; 51 | 52 | using InputImageType = itk::Image; 53 | using OutputImageType = itk::Image; 54 | 55 | 56 | using ReaderType = itk::ImageFileReader; 57 | using WriterType = itk::ImageFileWriter; 58 | 59 | ReaderType::Pointer reader = ReaderType::New(); 60 | reader->SetFileName(argv[1]); 61 | reader->Update(); 62 | 63 | using FilterType = itk::ResampleImageFilter; 64 | 65 | FilterType::Pointer filter = FilterType::New(); 66 | using TransformType = itk::AffineTransform; 67 | TransformType::Pointer transform = TransformType::New(); 68 | 69 | using InterpolatorType = itk::LinearInterpolateImageFunction; 70 | InterpolatorType::Pointer interpolator = InterpolatorType::New(); 71 | filter->SetInterpolator(interpolator); 72 | 73 | filter->SetDefaultPixelValue(0); 74 | 75 | InputImageType::SpacingType inputSpacing = reader->GetOutput()->GetSpacing(); 76 | InputImageType::RegionType inputRegion = reader->GetOutput()->GetLargestPossibleRegion(); 77 | InputImageType::SizeType inputSize = inputRegion.GetSize(); 78 | 79 | double resampleRatio[Dimension]; 80 | resampleRatio[0] = outputSpacing[0] / inputSpacing[0]; // resample ratio along X 81 | resampleRatio[1] = outputSpacing[1] / inputSpacing[1]; // resample ratio along Y 82 | resampleRatio[2] = outputSpacing[2] / inputSpacing[2]; // resample ratio along Z 83 | 84 | 85 | filter->SetOutputSpacing(outputSpacing); 86 | 87 | filter->SetOutputOrigin(reader->GetOutput()->GetOrigin()); 88 | 89 | 90 | InputImageType::SizeType outputSize; 91 | 92 | outputSize[0] = floor(inputSize[0] / resampleRatio[0] + 0.5); // number of pixels along X 93 | outputSize[1] = floor(inputSize[1] / resampleRatio[1] + 0.5); // number of pixels along Y 94 | outputSize[2] = floor(inputSize[2] / resampleRatio[2] + 0.5); // number of pixels along Z 95 | 96 | filter->SetSize(outputSize); 97 | 98 | filter->SetInput(reader->GetOutput()); 99 | 100 | transform->SetIdentity(); 101 | filter->SetTransform(transform); 102 | 103 | WriterType::Pointer writer = WriterType::New(); 104 | 105 | using ImageIOType = itk::NiftiImageIO; 106 | ImageIOType::Pointer niftiIO = ImageIOType::New(); 107 | 108 | // The NiftiImageIO object is then connected to the 109 | // ImageFileWriter. This will short-circuit the action of the 110 | // ImageIOFactory mechanism. The ImageFileWriter will 111 | // not attempt to look for other ImageIO objects capable of 112 | // performing the writing tasks. It will simply invoke the one provided by 113 | // the user. 114 | writer->SetImageIO(niftiIO); 115 | writer->SetFileName(argv[2]); 116 | writer->SetInput(filter->GetOutput()); 117 | writer->Update(); 118 | 119 | return EXIT_SUCCESS; 120 | } 121 | -------------------------------------------------------------------------------- /test/TwoProjection2D3DRegistration.cxx: -------------------------------------------------------------------------------- 1 | /*========================================================================= 2 | * 3 | * Copyright NumFOCUS 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * https://www.apache.org/licenses/LICENSE-2.0.txt 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | *=========================================================================*/ 18 | 19 | /*========================================================================= 20 | 21 | This program implements an intensity based 2D-3D registration algorithm using the 22 | SiddonJacobsRayCastInterpolateImageFunction and NormalizedCorrelationTwoImageToOneImageMetric 23 | similarity measure 24 | 25 | PowellOptimizer is used as the optimization method to avoid gradient calculation. 26 | Euler3DTransform instead of CenteredEuler3DTransform is used to avoid the shift of the center. 27 | 28 | When generating DRRs, the program attempts to simulate a 2D imaging system attached to a linac 29 | for radiation therapy. The program registers two 2D portal images with their corresponding DRR 30 | images generated from the 3D dataset. The portal images may be acquired at any arbitrary projection 31 | angles. The similarity measure is the summation of the measures calculated each projection. 32 | Multiresolution strategy was not implemented. 33 | 34 | This program was modified from the ITK application--IntensityBased2D3DRegistration.cxx 35 | 36 | =========================================================================*/ 37 | #include "itkTwoProjectionImageRegistrationMethod.h" 38 | 39 | // The transformation used is a rigid 3D Euler transform with the 40 | // provision of a center of rotation which defaults to the center of 41 | // the 3D volume. In practice the center of the particular feature of 42 | // interest in the 3D volume should be used. 43 | 44 | #include "itkEuler3DTransform.h" 45 | 46 | // We have chosen to implement the registration using the normalized coorelation 47 | // similarity measure. 48 | 49 | //#include "itkGradientDifferenceTwoImageToOneImageMetric.h" 50 | #include "itkNormalizedCorrelationTwoImageToOneImageMetric.h" 51 | 52 | // This is an intensity based registration algorithm so ray casting is 53 | // used to project the 3D volume onto pixels in the target 2D image. 54 | #include "itkSiddonJacobsRayCastInterpolateImageFunction.h" 55 | 56 | // Finally the Powell optimizer is used to avoid requiring gradient information. 57 | #include "itkPowellOptimizer.h" 58 | 59 | #include "itkImageFileReader.h" 60 | #include "itkImageFileWriter.h" 61 | 62 | #include "itkResampleImageFilter.h" 63 | #include "itkCastImageFilter.h" 64 | #include "itkRescaleIntensityImageFilter.h" 65 | #include "itkFlipImageFilter.h" 66 | 67 | #include "itkCommand.h" 68 | #include "itkTimeProbesCollectorBase.h" 69 | 70 | 71 | // First we define the command class to allow us to monitor the registration. 72 | 73 | class CommandIterationUpdate : public itk::Command 74 | { 75 | public: 76 | using Self = CommandIterationUpdate; 77 | using Superclass = itk::Command; 78 | using Pointer = itk::SmartPointer; 79 | itkNewMacro(Self); 80 | 81 | protected: 82 | CommandIterationUpdate() = default; 83 | 84 | public: 85 | using OptimizerType = itk::PowellOptimizer; 86 | using OptimizerPointer = const OptimizerType *; 87 | 88 | void 89 | Execute(itk::Object * caller, const itk::EventObject & event) override 90 | { 91 | Execute((const itk::Object *)caller, event); 92 | } 93 | 94 | void 95 | Execute(const itk::Object * object, const itk::EventObject & event) override 96 | { 97 | auto optimizer = dynamic_cast(object); 98 | if (typeid(event) != typeid(itk::IterationEvent)) 99 | { 100 | return; 101 | } 102 | // std::cout << "Iteration: " << optimizer->GetCurrentIteration() << std::endl; 103 | std::cout << "Similarity: " << optimizer->GetValue() << std::endl; 104 | std::cout << "Position: " << optimizer->GetCurrentPosition() << std::endl; 105 | } 106 | }; 107 | 108 | 109 | void 110 | exe_usage() 111 | { 112 | std::cerr << "\n"; 113 | std::cerr << "Usage: TwoProjection2D3DRegistration Image2D1 ProjAngle1 Image2D2 ProjAngle2 Volume3D\n"; 114 | std::cerr << " Registers two 2D images to a 3D volume. \n\n"; 115 | std::cerr << " where is one or more of the following:\n\n"; 116 | std::cerr << " <-h> Display (this) usage information\n"; 117 | std::cerr << " <-v> Verbose output [default: no]\n"; 118 | std::cerr << " <-dbg> Debugging output [default: no]\n"; 119 | std::cerr << " <-scd float> Source to isocenter distance [default: 1000mm]\n"; 120 | std::cerr << " <-t float float float> CT volume translation in x, y, and z direction in mm \n"; 121 | std::cerr << " <-rx float> CT volume rotation about x axis in degrees \n"; 122 | std::cerr << " <-ry float> CT volume rotation about y axis in degrees \n"; 123 | std::cerr << " <-rz float> CT volume rotation about z axis in degrees \n"; 124 | std::cerr 125 | << " <-2dcx float float float float> Central axis positions of the 2D images in continuous indices \n"; 126 | std::cerr << " <-res float float float float> Pixel spacing of projection images in the isocenter plane " 127 | "[default: 1x1 mm] \n"; 128 | std::cerr << " <-iso float float float> Isocenter location in voxel in indices (center of rotation and " 129 | "projection center)\n"; 130 | std::cerr << " <-threshold float> Intensity threshold below which are ignore [default: 0]\n"; 131 | std::cerr << " <-o file> Output image filename\n\n"; 132 | std::cerr << " by Jian Wu\n"; 133 | std::cerr << " eewujian@hotmail.com\n"; 134 | std::cerr << " (Univeristy of Florida)\n\n"; 135 | exit(EXIT_FAILURE); 136 | } 137 | 138 | 139 | int 140 | TwoProjection2D3DRegistration(int argc, char * argv[]) 141 | { 142 | char * fileImage2D1 = nullptr; 143 | double projAngle1 = -999; 144 | char * fileImage2D2 = nullptr; 145 | double projAngle2 = -999; 146 | char * fileVolume3D = nullptr; 147 | // Default output file names 148 | const char * fileOutput1 = "Image2D1_Registered.tif"; 149 | const char * fileOutput2 = "Image2D2_Registered.tif"; 150 | 151 | bool ok; 152 | bool verbose = false; 153 | bool debug = false; 154 | bool customized_iso = false; 155 | bool customized_2DCX = false; // Flag for customized 2D image central axis positions 156 | bool customized_2DRES = false; // Flag for customized 2D image pixel spacing 157 | 158 | double rx = 0.; 159 | double ry = 0.; 160 | double rz = 0.; 161 | 162 | double tx = 0.; 163 | double ty = 0.; 164 | double tz = 0.; 165 | 166 | double cx = 0.; 167 | double cy = 0.; 168 | double cz = 0.; 169 | 170 | double scd = 1000.; // Source to isocenter distance 171 | 172 | double image1centerX = 0.0; 173 | double image1centerY = 0.0; 174 | double image2centerX = 0.0; 175 | double image2centerY = 0.0; 176 | 177 | double image1resX = 1.0; 178 | double image1resY = 1.0; 179 | double image2resX = 1.0; 180 | double image2resY = 1.0; 181 | 182 | double threshold = 0.0; 183 | 184 | // Parse command line parameters 185 | 186 | if (argc <= 5) 187 | exe_usage(); 188 | 189 | while (argc > 1) 190 | { 191 | ok = false; 192 | 193 | if ((ok == false) && (strcmp(argv[1], "-h") == 0)) 194 | { 195 | argc--; 196 | argv++; 197 | ok = true; 198 | exe_usage(); 199 | } 200 | 201 | if ((ok == false) && (strcmp(argv[1], "-v") == 0)) 202 | { 203 | argc--; 204 | argv++; 205 | ok = true; 206 | verbose = true; 207 | } 208 | 209 | if ((ok == false) && (strcmp(argv[1], "-dbg") == 0)) 210 | { 211 | argc--; 212 | argv++; 213 | ok = true; 214 | debug = true; 215 | } 216 | 217 | if ((ok == false) && (strcmp(argv[1], "-scd") == 0)) 218 | { 219 | argc--; 220 | argv++; 221 | ok = true; 222 | scd = atof(argv[1]); 223 | argc--; 224 | argv++; 225 | } 226 | 227 | if ((ok == false) && (strcmp(argv[1], "-t") == 0)) 228 | { 229 | argc--; 230 | argv++; 231 | ok = true; 232 | tx = atof(argv[1]); 233 | argc--; 234 | argv++; 235 | ty = atof(argv[1]); 236 | argc--; 237 | argv++; 238 | tz = atof(argv[1]); 239 | argc--; 240 | argv++; 241 | } 242 | 243 | if ((ok == false) && (strcmp(argv[1], "-rx") == 0)) 244 | { 245 | argc--; 246 | argv++; 247 | ok = true; 248 | rx = atof(argv[1]); 249 | argc--; 250 | argv++; 251 | } 252 | 253 | if ((ok == false) && (strcmp(argv[1], "-ry") == 0)) 254 | { 255 | argc--; 256 | argv++; 257 | ok = true; 258 | ry = atof(argv[1]); 259 | argc--; 260 | argv++; 261 | } 262 | 263 | if ((ok == false) && (strcmp(argv[1], "-rz") == 0)) 264 | { 265 | argc--; 266 | argv++; 267 | ok = true; 268 | rz = atof(argv[1]); 269 | argc--; 270 | argv++; 271 | } 272 | 273 | if ((ok == false) && (strcmp(argv[1], "-2dcx") == 0)) 274 | { 275 | argc--; 276 | argv++; 277 | ok = true; 278 | image1centerX = atof(argv[1]); 279 | argc--; 280 | argv++; 281 | image1centerY = atof(argv[1]); 282 | argc--; 283 | argv++; 284 | image2centerX = atof(argv[1]); 285 | argc--; 286 | argv++; 287 | image2centerY = atof(argv[1]); 288 | argc--; 289 | argv++; 290 | customized_2DCX = true; 291 | } 292 | 293 | if ((ok == false) && (strcmp(argv[1], "-res") == 0)) 294 | { 295 | argc--; 296 | argv++; 297 | ok = true; 298 | image1resX = atof(argv[1]); 299 | argc--; 300 | argv++; 301 | image1resY = atof(argv[1]); 302 | argc--; 303 | argv++; 304 | image2resX = atof(argv[1]); 305 | argc--; 306 | argv++; 307 | image2resY = atof(argv[1]); 308 | argc--; 309 | argv++; 310 | customized_2DRES = true; 311 | } 312 | 313 | if ((ok == false) && (strcmp(argv[1], "-iso") == 0)) 314 | { 315 | argc--; 316 | argv++; 317 | ok = true; 318 | cx = atof(argv[1]); 319 | argc--; 320 | argv++; 321 | cy = atof(argv[1]); 322 | argc--; 323 | argv++; 324 | cz = atof(argv[1]); 325 | argc--; 326 | argv++; 327 | customized_iso = true; 328 | } 329 | 330 | if ((ok == false) && (strcmp(argv[1], "-threshold") == 0)) 331 | { 332 | argc--; 333 | argv++; 334 | ok = true; 335 | threshold = atof(argv[1]); 336 | argc--; 337 | argv++; 338 | } 339 | 340 | if ((ok == false) && (strcmp(argv[1], "-o") == 0)) 341 | { 342 | argc--; 343 | argv++; 344 | ok = true; 345 | fileOutput1 = argv[1]; 346 | argc--; 347 | argv++; 348 | fileOutput2 = argv[1]; 349 | argc--; 350 | argv++; 351 | } 352 | 353 | 354 | if (ok == false) 355 | { 356 | 357 | if (fileImage2D1 == nullptr) 358 | { 359 | fileImage2D1 = argv[1]; 360 | argc--; 361 | argv++; 362 | } 363 | 364 | if (projAngle1 == -999) 365 | { 366 | projAngle1 = atof(argv[1]); 367 | argc--; 368 | argv++; 369 | } 370 | 371 | if (fileImage2D2 == nullptr) 372 | { 373 | fileImage2D2 = argv[1]; 374 | argc--; 375 | argv++; 376 | } 377 | 378 | if (projAngle2 == -999) 379 | { 380 | projAngle2 = atof(argv[1]); 381 | argc--; 382 | argv++; 383 | } 384 | 385 | else if (fileVolume3D == nullptr) 386 | { 387 | fileVolume3D = argv[1]; 388 | argc--; 389 | argv++; 390 | } 391 | 392 | else 393 | { 394 | std::cerr << "ERROR: Cannot parse argument " << argv[1] << std::endl; 395 | exe_usage(); 396 | } 397 | } 398 | } 399 | 400 | if (verbose) 401 | { 402 | if (fileImage2D1) 403 | std::cout << "Input 2D image 1: " << fileImage2D1 << std::endl; 404 | if (fileImage2D1) 405 | std::cout << "Projection Angle 1: " << projAngle1 << std::endl; 406 | if (fileImage2D2) 407 | std::cout << "Input 2D image 2: " << fileImage2D2 << std::endl; 408 | if (fileImage2D2) 409 | std::cout << "Projection Angle 2: " << projAngle2 << std::endl; 410 | if (fileVolume3D) 411 | std::cout << "Input 3D image: " << fileVolume3D << std::endl; 412 | if (fileOutput1) 413 | std::cout << "Output image 1: " << fileOutput1 << std::endl; 414 | if (fileOutput2) 415 | std::cout << "Output image 2: " << fileOutput2 << std::endl; 416 | } 417 | 418 | 419 | // We begin the program proper by defining the 2D and 3D images. The 420 | // {TwoProjectionImageRegistrationMethod} requires that both 421 | // images have the same dimension so the 2D image is given 422 | // dimension 3 and the size of the {z} dimension is set to unity. 423 | 424 | constexpr unsigned int Dimension = 3; 425 | using InternalPixelType = float; 426 | using PixelType3D = short; 427 | 428 | using ImageType3D = itk::Image; 429 | 430 | using OutputPixelType = unsigned char; 431 | using OutputImageType = itk::Image; 432 | 433 | // The following lines define each of the components used in the 434 | // registration: The transform, optimizer, metric, interpolator and 435 | // the registration method itself. 436 | 437 | using InternalImageType = itk::Image; 438 | 439 | using TransformType = itk::Euler3DTransform; 440 | 441 | using OptimizerType = itk::PowellOptimizer; 442 | 443 | // using MetricType = itk::GradientDifferenceTwoImageToOneImageMetric< 444 | using MetricType = itk::NormalizedCorrelationTwoImageToOneImageMetric; 445 | 446 | using InterpolatorType = itk::SiddonJacobsRayCastInterpolateImageFunction; 447 | 448 | 449 | using RegistrationType = itk::TwoProjectionImageRegistrationMethod; 450 | 451 | 452 | // Each of the registration components are instantiated in the 453 | // usual way... 454 | 455 | MetricType::Pointer metric = MetricType::New(); 456 | TransformType::Pointer transform = TransformType::New(); 457 | OptimizerType::Pointer optimizer = OptimizerType::New(); 458 | InterpolatorType::Pointer interpolator1 = InterpolatorType::New(); 459 | InterpolatorType::Pointer interpolator2 = InterpolatorType::New(); 460 | RegistrationType::Pointer registration = RegistrationType::New(); 461 | 462 | metric->ComputeGradientOff(); 463 | metric->SetSubtractMean(true); 464 | 465 | // and passed to the registration method: 466 | 467 | registration->SetMetric(metric); 468 | registration->SetOptimizer(optimizer); 469 | registration->SetTransform(transform); 470 | registration->SetInterpolator1(interpolator1); 471 | registration->SetInterpolator2(interpolator2); 472 | 473 | if (debug) 474 | { 475 | metric->DebugOn(); 476 | // transform->DebugOn(); 477 | // optimizer->DebugOn(); 478 | interpolator1->DebugOn(); 479 | interpolator2->DebugOn(); 480 | // registration->DebugOn(); 481 | } 482 | 483 | 484 | // The 2- and 3-D images are read from files, 485 | 486 | using ImageReaderType2D = itk::ImageFileReader; 487 | using ImageReaderType3D = itk::ImageFileReader; 488 | 489 | ImageReaderType2D::Pointer imageReader2D1 = ImageReaderType2D::New(); 490 | ImageReaderType2D::Pointer imageReader2D2 = ImageReaderType2D::New(); 491 | ImageReaderType3D::Pointer imageReader3D = ImageReaderType3D::New(); 492 | 493 | imageReader2D1->SetFileName(fileImage2D1); 494 | imageReader2D2->SetFileName(fileImage2D2); 495 | imageReader3D->SetFileName(fileVolume3D); 496 | imageReader3D->Update(); 497 | 498 | ImageType3D::Pointer image3DIn = imageReader3D->GetOutput(); 499 | 500 | // To simply Siddon-Jacob's fast ray-tracing algorithm, we force the origin of the CT image 501 | // to be (0,0,0). Because we align the CT isocenter with the central axis, the projection 502 | // geometry is fully defined. The origin of the CT image becomes irrelavent. 503 | ImageType3D::PointType image3DOrigin; 504 | image3DOrigin[0] = 0.0; 505 | image3DOrigin[1] = 0.0; 506 | image3DOrigin[2] = 0.0; 507 | image3DIn->SetOrigin(image3DOrigin); 508 | 509 | InternalImageType::Pointer image_tmp1 = imageReader2D1->GetOutput(); 510 | InternalImageType::Pointer image_tmp2 = imageReader2D2->GetOutput(); 511 | 512 | imageReader2D1->Update(); 513 | imageReader2D2->Update(); 514 | 515 | if (customized_2DRES) 516 | { 517 | InternalImageType::SpacingType spacing; 518 | spacing[0] = image1resX; 519 | spacing[1] = image1resY; 520 | spacing[2] = 1.0; 521 | image_tmp1->SetSpacing(spacing); 522 | 523 | spacing[0] = image2resX; 524 | spacing[1] = image2resY; 525 | image_tmp2->SetSpacing(spacing); 526 | } 527 | // The input 2D images were loaded as 3D images. They were considered 528 | // as a single slice from a 3D volume. By default, images stored on the 529 | // disk are treated as if they have RAI orientation. After view point 530 | // transformation, the order of 2D image pixel reading is equivalent to 531 | // from inferior to superior. This is contradictory to the traditional 532 | // 2D x-ray image storage, in which a typical 2D image reader reads and 533 | // writes images from superior to inferior. Thus the loaded 2D DICOM 534 | // images should be flipped in y-direction. This was done by using a. 535 | // FilpImageFilter. 536 | using FlipFilterType = itk::FlipImageFilter; 537 | FlipFilterType::Pointer flipFilter1 = FlipFilterType::New(); 538 | FlipFilterType::Pointer flipFilter2 = FlipFilterType::New(); 539 | 540 | using FlipAxesArrayType = FlipFilterType::FlipAxesArrayType; 541 | FlipAxesArrayType flipArray; 542 | flipArray[0] = false; 543 | flipArray[1] = true; 544 | flipArray[2] = false; 545 | 546 | flipFilter1->SetFlipAxes(flipArray); 547 | flipFilter2->SetFlipAxes(flipArray); 548 | 549 | flipFilter1->SetInput(imageReader2D1->GetOutput()); 550 | flipFilter2->SetInput(imageReader2D2->GetOutput()); 551 | 552 | // The input 2D images may have 16 bits. We rescale the pixel value to between 0-255. 553 | using Input2DRescaleFilterType = itk::RescaleIntensityImageFilter; 554 | 555 | Input2DRescaleFilterType::Pointer rescaler2D1 = Input2DRescaleFilterType::New(); 556 | rescaler2D1->SetOutputMinimum(0); 557 | rescaler2D1->SetOutputMaximum(255); 558 | rescaler2D1->SetInput(flipFilter1->GetOutput()); 559 | 560 | Input2DRescaleFilterType::Pointer rescaler2D2 = Input2DRescaleFilterType::New(); 561 | rescaler2D2->SetOutputMinimum(0); 562 | rescaler2D2->SetOutputMaximum(255); 563 | rescaler2D2->SetInput(flipFilter2->GetOutput()); 564 | 565 | 566 | // The 3D CT dataset is casted to the internal image type using 567 | // {CastImageFilters}. 568 | 569 | using CastFilterType3D = itk::CastImageFilter; 570 | 571 | CastFilterType3D::Pointer caster3D = CastFilterType3D::New(); 572 | caster3D->SetInput(image3DIn); 573 | 574 | rescaler2D1->Update(); 575 | rescaler2D2->Update(); 576 | caster3D->Update(); 577 | 578 | 579 | registration->SetFixedImage1(rescaler2D1->GetOutput()); 580 | registration->SetFixedImage2(rescaler2D2->GetOutput()); 581 | registration->SetMovingImage(caster3D->GetOutput()); 582 | 583 | // Initialise the transform 584 | // ~~~~~~~~~~~~~~~~~~~~~~~~ 585 | 586 | // Set the order of the computation. Default ZXY 587 | transform->SetComputeZYX(true); 588 | 589 | 590 | // The transform is initialised with the translation [tx,ty,tz] and 591 | // rotation [rx,ry,rz] specified on the command line 592 | 593 | TransformType::OutputVectorType translation; 594 | 595 | translation[0] = tx; 596 | translation[1] = ty; 597 | translation[2] = tz; 598 | 599 | transform->SetTranslation(translation); 600 | 601 | // constant for converting degrees to radians 602 | const double dtr = (atan(1.0) * 4.0) / 180.0; 603 | transform->SetRotation(dtr * rx, dtr * ry, dtr * rz); 604 | 605 | // The centre of rotation is set by default to the centre of the 3D 606 | // volume but can be offset from this position using a command 607 | // line specified translation [cx,cy,cz] 608 | 609 | ImageType3D::PointType origin3D = image3DIn->GetOrigin(); 610 | const itk::Vector resolution3D = image3DIn->GetSpacing(); 611 | 612 | using ImageRegionType3D = ImageType3D::RegionType; 613 | using SizeType3D = ImageRegionType3D::SizeType; 614 | 615 | ImageRegionType3D region3D = caster3D->GetOutput()->GetBufferedRegion(); 616 | SizeType3D size3D = region3D.GetSize(); 617 | 618 | TransformType::InputPointType isocenter; 619 | if (customized_iso) 620 | { 621 | // Isocenter location given by the user. 622 | isocenter[0] = origin3D[0] + resolution3D[0] * cx; 623 | isocenter[1] = origin3D[1] + resolution3D[1] * cy; 624 | isocenter[2] = origin3D[2] + resolution3D[2] * cz; 625 | } 626 | else 627 | { 628 | // Set the center of the image as the isocenter. 629 | isocenter[0] = origin3D[0] + resolution3D[0] * static_cast(size3D[0]) / 2.0; 630 | isocenter[1] = origin3D[1] + resolution3D[1] * static_cast(size3D[1]) / 2.0; 631 | isocenter[2] = origin3D[2] + resolution3D[2] * static_cast(size3D[2]) / 2.0; 632 | } 633 | 634 | transform->SetCenter(isocenter); 635 | 636 | 637 | if (verbose) 638 | { 639 | std::cout << "3D image size: " << size3D[0] << ", " << size3D[1] << ", " << size3D[2] << std::endl 640 | << " resolution: " << resolution3D[0] << ", " << resolution3D[1] << ", " << resolution3D[2] << std::endl 641 | << "Transform: " << transform << std::endl; 642 | } 643 | 644 | 645 | // Set the origin of the 2D image 646 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 647 | 648 | // For correct (perspective) projection of the 3D volume, the 2D 649 | // image needs to be placed at a certain distance (the source-to- 650 | // isocenter distance {scd} ) from the focal point, and the normal 651 | // from the imaging plane to the focal point needs to be specified. 652 | // 653 | // By default, the imaging plane normal is set by default to the 654 | // center of the 2D image but may be modified from this using the 655 | // command line parameters [image1centerX, image1centerY, 656 | // image2centerX, image2centerY]. 657 | 658 | double origin2D1[Dimension]; 659 | double origin2D2[Dimension]; 660 | 661 | // Note: Two 2D images may have different image sizes and pixel dimensions, although 662 | // scd are the same. 663 | 664 | const itk::Vector resolution2D1 = imageReader2D1->GetOutput()->GetSpacing(); 665 | const itk::Vector resolution2D2 = imageReader2D2->GetOutput()->GetSpacing(); 666 | 667 | using ImageRegionType2D = InternalImageType::RegionType; 668 | using SizeType2D = ImageRegionType2D::SizeType; 669 | 670 | ImageRegionType2D region2D1 = rescaler2D1->GetOutput()->GetBufferedRegion(); 671 | ImageRegionType2D region2D2 = rescaler2D2->GetOutput()->GetBufferedRegion(); 672 | SizeType2D size2D1 = region2D1.GetSize(); 673 | SizeType2D size2D2 = region2D2.GetSize(); 674 | 675 | if (!customized_2DCX) 676 | { // Central axis positions are not given by the user. Use the image centers 677 | // as the central axis position. 678 | image1centerX = ((double)size2D1[0] - 1.) / 2.; 679 | image1centerY = ((double)size2D1[1] - 1.) / 2.; 680 | image2centerX = ((double)size2D2[0] - 1.) / 2.; 681 | image2centerY = ((double)size2D2[1] - 1.) / 2.; 682 | } 683 | 684 | // 2D Image 1 685 | origin2D1[0] = -resolution2D1[0] * image1centerX; 686 | origin2D1[1] = -resolution2D1[1] * image1centerY; 687 | origin2D1[2] = -scd; 688 | 689 | imageReader2D1->GetOutput()->SetOrigin(origin2D1); 690 | rescaler2D1->GetOutput()->SetOrigin(origin2D1); 691 | 692 | // 2D Image 2 693 | origin2D2[0] = -resolution2D2[0] * image2centerX; 694 | origin2D2[1] = -resolution2D2[1] * image2centerY; 695 | origin2D2[2] = -scd; 696 | 697 | imageReader2D2->GetOutput()->SetOrigin(origin2D2); 698 | rescaler2D2->GetOutput()->SetOrigin(origin2D2); 699 | 700 | registration->SetFixedImageRegion1(rescaler2D1->GetOutput()->GetBufferedRegion()); 701 | registration->SetFixedImageRegion2(rescaler2D2->GetOutput()->GetBufferedRegion()); 702 | 703 | if (verbose) 704 | { 705 | std::cout << "2D image 1 size: " << size2D1[0] << ", " << size2D1[1] << ", " << size2D1[2] << std::endl 706 | << " resolution: " << resolution2D1[0] << ", " << resolution2D1[1] << ", " << resolution2D1[2] 707 | << std::endl 708 | << " and position: " << origin2D1[0] << ", " << origin2D1[1] << ", " << origin2D1[2] << std::endl 709 | << "2D image 2 size: " << size2D2[0] << ", " << size2D2[1] << ", " << size2D2[2] << std::endl 710 | << " resolution: " << resolution2D2[0] << ", " << resolution2D2[1] << ", " << resolution2D2[2] 711 | << std::endl 712 | << " and position: " << origin2D2[0] << ", " << origin2D2[1] << ", " << origin2D2[2] << std::endl; 713 | } 714 | 715 | // Initialize the ray cast interpolator 716 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 717 | 718 | // The ray cast interpolator is used to project the 3D volume. It 719 | // does this by casting rays from the (transformed) focal point to 720 | // each (transformed) pixel coordinate in the 2D image. 721 | // 722 | // In addition a threshold may be specified to ensure that only 723 | // intensities greater than a given value contribute to the 724 | // projected volume. This can be used, for instance, to remove soft 725 | // tissue from projections of CT data and force the registration 726 | // to find a match which aligns bony structures in the images. 727 | 728 | // 2D Image 1 729 | interpolator1->SetProjectionAngle(dtr * projAngle1); 730 | interpolator1->SetFocalPointToIsocenterDistance(scd); 731 | interpolator1->SetThreshold(threshold); 732 | interpolator1->SetTransform(transform); 733 | 734 | interpolator1->Initialize(); 735 | 736 | // 2D Image 2 737 | interpolator2->SetProjectionAngle(dtr * projAngle2); 738 | interpolator2->SetFocalPointToIsocenterDistance(scd); 739 | interpolator2->SetThreshold(threshold); 740 | interpolator2->SetTransform(transform); 741 | 742 | interpolator2->Initialize(); 743 | 744 | 745 | // Set up the transform and start position 746 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 747 | 748 | // The registration start position is intialised using the 749 | // transformation parameters. 750 | 751 | registration->SetInitialTransformParameters(transform->GetParameters()); 752 | 753 | // We wish to minimize the negative normalized correlation similarity measure. 754 | 755 | // optimizer->SetMaximize( true ); // for GradientDifferenceTwoImageToOneImageMetric 756 | optimizer->SetMaximize(false); // for NCC 757 | 758 | optimizer->SetMaximumIteration(10); 759 | optimizer->SetMaximumLineIteration(4); // for Powell's method 760 | optimizer->SetStepLength(4.0); 761 | optimizer->SetStepTolerance(0.02); 762 | optimizer->SetValueTolerance(0.001); 763 | 764 | // The optimizer weightings are set such that one degree equates to 765 | // one millimeter. 766 | 767 | itk::Optimizer::ScalesType weightings(transform->GetNumberOfParameters()); 768 | 769 | weightings[0] = 1. / dtr; 770 | weightings[1] = 1. / dtr; 771 | weightings[2] = 1. / dtr; 772 | weightings[3] = 1.; 773 | weightings[4] = 1.; 774 | weightings[5] = 1.; 775 | 776 | optimizer->SetScales(weightings); 777 | 778 | if (verbose) 779 | { 780 | optimizer->Print(std::cout); 781 | } 782 | 783 | 784 | // Create the observers 785 | // ~~~~~~~~~~~~~~~~~~~~ 786 | 787 | CommandIterationUpdate::Pointer observer = CommandIterationUpdate::New(); 788 | 789 | optimizer->AddObserver(itk::IterationEvent(), observer); 790 | 791 | 792 | // Start the registration 793 | // ~~~~~~~~~~~~~~~~~~~~~~ 794 | 795 | // Create a timer to record calculation time. 796 | itk::TimeProbesCollectorBase timer; 797 | 798 | if (verbose) 799 | { 800 | std::cout << "Starting the registration now..." << std::endl; 801 | } 802 | 803 | try 804 | { 805 | timer.Start("Registration"); 806 | // Start the registration. 807 | registration->StartRegistration(); 808 | timer.Stop("Registration"); 809 | } 810 | catch (itk::ExceptionObject & err) 811 | { 812 | std::cout << "ExceptionObject caught !" << std::endl; 813 | std::cout << err << std::endl; 814 | return -1; 815 | } 816 | 817 | using ParametersType = RegistrationType::ParametersType; 818 | ParametersType finalParameters = registration->GetLastTransformParameters(); 819 | 820 | const double RotationAlongX = finalParameters[0] / dtr; // Convert radian to degree 821 | const double RotationAlongY = finalParameters[1] / dtr; 822 | const double RotationAlongZ = finalParameters[2] / dtr; 823 | const double TranslationAlongX = finalParameters[3]; 824 | const double TranslationAlongY = finalParameters[4]; 825 | const double TranslationAlongZ = finalParameters[5]; 826 | 827 | const int numberOfIterations = optimizer->GetCurrentIteration(); 828 | 829 | const double bestValue = optimizer->GetValue(); 830 | 831 | std::cout << "Result = " << std::endl; 832 | std::cout << " Rotation Along X = " << RotationAlongX << " deg" << std::endl; 833 | std::cout << " Rotation Along Y = " << RotationAlongY << " deg" << std::endl; 834 | std::cout << " Rotation Along Z = " << RotationAlongZ << " deg" << std::endl; 835 | std::cout << " Translation X = " << TranslationAlongX << " mm" << std::endl; 836 | std::cout << " Translation Y = " << TranslationAlongY << " mm" << std::endl; 837 | std::cout << " Translation Z = " << TranslationAlongZ << " mm" << std::endl; 838 | std::cout << " Number Of Iterations = " << numberOfIterations << std::endl; 839 | std::cout << " Metric value = " << bestValue << std::endl; 840 | 841 | 842 | // Write out the projection images at the registration position 843 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 844 | 845 | TransformType::Pointer finalTransform = TransformType::New(); 846 | // The transform is determined by the parameters and the rotation center. 847 | finalTransform->SetParameters(finalParameters); 848 | finalTransform->SetCenter(isocenter); 849 | 850 | using ResampleFilterType = itk::ResampleImageFilter; 851 | 852 | // The ResampleImageFilter is the driving force for the projection image generation. 853 | ResampleFilterType::Pointer resampleFilter1 = ResampleFilterType::New(); 854 | 855 | resampleFilter1->SetInput(caster3D->GetOutput()); // Link the 3D volume. 856 | resampleFilter1->SetDefaultPixelValue(0); 857 | 858 | // The parameters of interpolator1, such as ProjectionAngle and FocalPointToIsocenterDistance 859 | // have been set before registration. Here we only need to replace the initial 860 | // transform with the final transform. 861 | interpolator1->SetTransform(finalTransform); 862 | interpolator1->Initialize(); 863 | resampleFilter1->SetInterpolator(interpolator1); 864 | 865 | // The output 2D projection image has the same image size, origin, and the pixel spacing as 866 | // those of the input 2D image. 867 | resampleFilter1->SetSize(rescaler2D1->GetOutput()->GetLargestPossibleRegion().GetSize()); 868 | resampleFilter1->SetOutputOrigin(rescaler2D1->GetOutput()->GetOrigin()); 869 | resampleFilter1->SetOutputSpacing(rescaler2D1->GetOutput()->GetSpacing()); 870 | 871 | // Do the same thing for the output image 2. 872 | ResampleFilterType::Pointer resampleFilter2 = ResampleFilterType::New(); 873 | resampleFilter2->SetInput(caster3D->GetOutput()); 874 | resampleFilter2->SetDefaultPixelValue(0); 875 | 876 | // The parameters of interpolator2, such as ProjectionAngle and FocalPointToIsocenterDistance 877 | // have been set before registration. Here we only need to replace the initial 878 | // transform with the final transform. 879 | interpolator2->SetTransform(finalTransform); 880 | interpolator2->Initialize(); 881 | resampleFilter2->SetInterpolator(interpolator2); 882 | 883 | resampleFilter2->SetSize(rescaler2D2->GetOutput()->GetLargestPossibleRegion().GetSize()); 884 | resampleFilter2->SetOutputOrigin(rescaler2D2->GetOutput()->GetOrigin()); 885 | resampleFilter2->SetOutputSpacing(rescaler2D2->GetOutput()->GetSpacing()); 886 | 887 | /////////////////////////////---DEGUG--START----//////////////////////////////////// 888 | if (debug) 889 | { 890 | InternalImageType::PointType outputorigin2D1 = rescaler2D1->GetOutput()->GetOrigin(); 891 | std::cout << "Output image 1 origin: " << outputorigin2D1[0] << ", " << outputorigin2D1[1] << ", " 892 | << outputorigin2D1[2] << std::endl; 893 | InternalImageType::PointType outputorigin2D2 = rescaler2D2->GetOutput()->GetOrigin(); 894 | std::cout << "Output image 2 origin: " << outputorigin2D2[0] << ", " << outputorigin2D2[1] << ", " 895 | << outputorigin2D2[2] << std::endl; 896 | } 897 | /////////////////////////////---DEGUG--END----////////////////////////////////////// 898 | 899 | 900 | // As explained before, the computed projection is upsided-down. 901 | // Here we use a FilpImageFilter to flip the images in y-direction. 902 | flipFilter1->SetInput(resampleFilter1->GetOutput()); 903 | flipFilter2->SetInput(resampleFilter2->GetOutput()); 904 | 905 | // Rescale the intensity of the projection images to 0-255 for output. 906 | using RescaleFilterType = itk::RescaleIntensityImageFilter; 907 | 908 | RescaleFilterType::Pointer rescaler1 = RescaleFilterType::New(); 909 | rescaler1->SetOutputMinimum(0); 910 | rescaler1->SetOutputMaximum(255); 911 | rescaler1->SetInput(flipFilter1->GetOutput()); 912 | 913 | RescaleFilterType::Pointer rescaler2 = RescaleFilterType::New(); 914 | rescaler2->SetOutputMinimum(0); 915 | rescaler2->SetOutputMaximum(255); 916 | rescaler2->SetInput(flipFilter2->GetOutput()); 917 | 918 | 919 | using WriterType = itk::ImageFileWriter; 920 | WriterType::Pointer writer1 = WriterType::New(); 921 | WriterType::Pointer writer2 = WriterType::New(); 922 | 923 | writer1->SetFileName(fileOutput1); 924 | writer1->SetInput(rescaler1->GetOutput()); 925 | 926 | try 927 | { 928 | std::cout << "Writing image: " << fileOutput1 << std::endl; 929 | writer1->Update(); 930 | } 931 | catch (itk::ExceptionObject & err) 932 | { 933 | std::cerr << "ERROR: ExceptionObject caught !" << std::endl; 934 | std::cerr << err << std::endl; 935 | } 936 | 937 | writer2->SetFileName(fileOutput2); 938 | writer2->SetInput(rescaler2->GetOutput()); 939 | 940 | try 941 | { 942 | std::cout << "Writing image: " << fileOutput2 << std::endl; 943 | writer2->Update(); 944 | } 945 | catch (itk::ExceptionObject & err) 946 | { 947 | std::cerr << "ERROR: ExceptionObject caught !" << std::endl; 948 | std::cerr << err << std::endl; 949 | } 950 | timer.Report(); 951 | 952 | return EXIT_SUCCESS; 953 | } 954 | -------------------------------------------------------------------------------- /wrapping/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | itk_wrap_module(TwoProjectionRegistration) 2 | 3 | set(WRAPPER_SUBMODULE_ORDER 4 | itkNormalizedCorrelationTwoImageToOneImageMetric 5 | itkSiddonJacobsRayCastInterpolateImageFunction 6 | itkTwoImageToOneImageMetric 7 | itkTwoProjectionImageRegistrationMethod) 8 | 9 | itk_auto_load_submodules() 10 | itk_end_wrap_module() 11 | -------------------------------------------------------------------------------- /wrapping/itkNormalizedCorrelationTwoImageToOneImageMetric.wrap: -------------------------------------------------------------------------------- 1 | itk_wrap_class("itk::NormalizedCorrelationTwoImageToOneImageMetric" POINTER) 2 | itk_wrap_image_filter("${WRAP_ITK_SCALAR}" 2 2+) 3 | itk_end_wrap_class() 4 | -------------------------------------------------------------------------------- /wrapping/itkSiddonJacobsRayCastInterpolateImageFunction.wrap: -------------------------------------------------------------------------------- 1 | itk_wrap_filter_dims(has_d_3 3) 2 | 3 | if(has_d_3) 4 | itk_wrap_class("itk::SiddonJacobsRayCastInterpolateImageFunction" POINTER) 5 | foreach(t ${WRAP_ITK_SCALAR}) 6 | # This interpolator works for 3-dimensional images only 7 | itk_wrap_template("${ITKM_I${t}3}${ITKM_D}" "${ITKT_I${t}3},${ITKT_D}") 8 | endforeach() 9 | itk_end_wrap_class() 10 | endif() 11 | -------------------------------------------------------------------------------- /wrapping/itkTwoImageToOneImageMetric.wrap: -------------------------------------------------------------------------------- 1 | itk_wrap_class("itk::TwoImageToOneImageMetric" POINTER) 2 | itk_wrap_image_filter("${WRAP_ITK_SCALAR}" 2 2+) 3 | itk_end_wrap_class() 4 | -------------------------------------------------------------------------------- /wrapping/itkTwoProjectionImageRegistrationMethod.wrap: -------------------------------------------------------------------------------- 1 | itk_wrap_class("itk::TwoProjectionImageRegistrationMethod" POINTER) 2 | itk_wrap_image_filter("${WRAP_ITK_SCALAR}" 2 2+) 3 | itk_end_wrap_class() 4 | --------------------------------------------------------------------------------