diff --git a/.github/workflows/build-test-package.yml b/.github/workflows/build-test-package.yml index 3f76e85..13651bf 100644 --- a/.github/workflows/build-test-package.yml +++ b/.github/workflows/build-test-package.yml @@ -1,12 +1,20 @@ name: Build, test, package +on: + push: + branches: + - main + pull_request: + branches: + - main + on: [push,pull_request] jobs: cxx-build-workflow: - uses: thewtex/ITKRemoteModuleBuildTestPackageAction/.github/workflows/build-test-cxx.yml@1f6e25cd9a591707611af5bbb94b7d2fbfa42994 + uses: InsightSoftwareConsortium/ITKRemoteModuleBuildTestPackageAction/.github/workflows/build-test-cxx.yml@v5.4.0 python-build-workflow: - uses: thewtex/ITKRemoteModuleBuildTestPackageAction/.github/workflows/build-test-package-python.yml@1f6e25cd9a591707611af5bbb94b7d2fbfa42994 + uses: InsightSoftwareConsortium/ITKRemoteModuleBuildTestPackageAction/.github/workflows/build-test-package-python.yml@v5.4.0 secrets: pypi_password: ${{ secrets.pypi_password }} diff --git a/.gitignore b/.gitignore index 43325fb..c580348 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +CMakePresets.json emscripten-build/ wasi-build/ node_modules/ diff --git a/CMakeLists.txt b/CMakeLists.txt index f338199..226e572 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,3 +15,9 @@ else() set(ITK_DIR ${CMAKE_BINARY_DIR}) itk_module_impl() endif() + +# These are not expected to pass. +# Mark the tests NornirKWStyleTest and NornirInDoxygenGroup as expected to fail +if(BUILD_TESTING) + set_tests_properties(NornirKWStyleTest NornirInDoxygenGroup PROPERTIES WILL_FAIL TRUE) +endif() \ No newline at end of file diff --git a/LICENSE b/LICENSE index 62589ed..d159169 100644 --- a/LICENSE +++ b/LICENSE @@ -1,202 +1,339 @@ - - Apache License - Version 2.0, January 2004 - https://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/README.rst b/README.rst index 82474b3..74badf9 100644 --- a/README.rst +++ b/README.rst @@ -9,10 +9,6 @@ ITKNornir :target: https://pypi.python.org/pypi/itk-nornir :alt: PyPI Version -.. image:: https://img.shields.io/badge/License-Apache%202.0-blue.svg - :target: https://github.com/thewtex/ITKNornir/blob/main/LICENSE - :alt: License - Overview -------- diff --git a/include/IRAABBox.h b/include/IRAABBox.h new file mode 100644 index 0000000..e851d4e --- /dev/null +++ b/include/IRAABBox.h @@ -0,0 +1,256 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_aa_bbox.hxx +// Author : Pavel A. Koshevoy +// Created : Mon Jun 7 22:14:00 MDT 2004 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : Axis aligned 3D bounding box. + +#ifndef THE_AA_BBOX_HXX_ +#define THE_AA_BBOX_HXX_ + +// system includes: +#include + +// local includes: +#include "IRV3X1P3X1.h" + +// namespace access: +using std::ostream; + + +// axis aligned bounding box layout: +// +// C1-------E9------C5 +// /| /| +// / | / | Z = [0 0 1]T +// E0 | F0 E4 | | +// / e1 f1 / | | +// / | / E5 | reference coordinate system +// C0-------E8------C4 | | +// | | | F5 | + - - - - Y = [0 1 0]T +// | f4 c2----e10-|-----C6 / +// | / | / / +// E3 / F3 E7 / X = [1 0 0]T +// | e2 f2 | E6 +// | / | / +// |/ |/ +// C3------E11------C7 +// +// Fi - face id, fi - hidden face id. +// Ei - edge id, ei - hidden edge id. +// Ci - corner id, ci - hidden corner id. +// +// The bounding box faces correspond to the fiew point orientation as follows: +// F0 = top face +// f1 = back face +// f2 = bottom face +// F3 = front face +// f4 = right face +// F5 = left face + +//---------------------------------------------------------------- +// the_aa_bbox_t +// +class the_aa_bbox_t +{ +public: + the_aa_bbox_t() + { clear(); } + + // reset the bounding box to be empty: + inline void clear() + { + min_.assign(FLT_MAX, FLT_MAX, FLT_MAX); + max_.assign(-FLT_MAX, -FLT_MAX, -FLT_MAX); + } + + // addition/expansion operators: + the_aa_bbox_t & operator << (const p3x1_t & pt); + the_aa_bbox_t & operator += (const the_aa_bbox_t & bbox); + + inline the_aa_bbox_t operator + (const the_aa_bbox_t & bbox) const + { + the_aa_bbox_t ret_val(*this); + return ret_val += bbox; + } + + // scale operators: + the_aa_bbox_t & operator *= (const float & s); + + the_aa_bbox_t operator * (const float & s) const + { + the_aa_bbox_t result(*this); + result *= s; + return result; + } + + // uniformly advance/retreat every face of the bounding box by value r: + the_aa_bbox_t & operator += (const float & r); + + inline the_aa_bbox_t operator + (const float & r) const + { + the_aa_bbox_t result(*this); + result += r; + return result; + } + + inline the_aa_bbox_t operator - (const float & r) const + { return (*this + (-r)); } + + inline the_aa_bbox_t & operator -= (const float & r) + { return (*this += (-r)); } + + // equality test operator: + inline bool operator == (const the_aa_bbox_t & bbox) const + { return ((min_ == bbox.min_) && (max_ == bbox.max_)); } + + inline bool operator != (const the_aa_bbox_t & bbox) const + { return !((*this) == bbox); } + + // return true if the volume of this bounding box is smaller than + // the volume of the given bounding box: + inline bool operator < (const the_aa_bbox_t & bbox) const + { return (volume() < bbox.volume()); } + + // calculate the volume of this bounding box: + inline float volume() const + { + if (is_empty()) return 0.0; + return (max_[0] - min_[0]) * (max_[1] - min_[1]) * (max_[2] - min_[2]); + } + + // convert min/max into 8 bounding box corners, + // the caller has to make sure that corner_array is of size 8: + void corners(p3x1_t * corner_array) const; + + // bounding box validity tests: + bool is_empty() const; + + inline bool is_singular() const + { return (min_ == max_); } + + bool is_linear() const; + bool is_planar() const; + bool is_spacial() const; + + // calculate the edge length of the bounding box: + inline float length(const unsigned int & axis_id) const + { return (max_[axis_id] - min_[axis_id]); } + + // calculate the center of the bounding box: + inline p3x1_t center() const + { return 0.5f * (max_ + min_); } + + // calculate the radius of the bounding box (sphere): + float radius(const p3x1_t & center) const; + + inline float radius() const + { return 0.5f * diameter(); } + + inline float diameter() const + { + if (is_empty()) return 0; + return (min_ - max_).norm(); + } + + // calculate the radius of the bounding box (cylinder): + float radius(const p3x1_t & center, const unsigned int & axis_w_id) const; + + inline float radius(const unsigned int & axis_w_id) const + { return radius(center(), axis_w_id); } + + // check whether a given point is contained between the faces of the + // bounding box normal to the x, y, and z axis - if the point is + // contained within all three, the point is inside the bounding box: + void contains(const p3x1_t & pt, + bool & contained_in_x, + bool & contained_in_y, + bool & contained_in_z) const; + + // check whether the bounding box contains a given point: + inline bool contains(const p3x1_t & pt) const + { + bool contained_in_x = false; + bool contained_in_y = false; + bool contained_in_z = false; + contains(pt, contained_in_x, contained_in_y, contained_in_z); + return (contained_in_x && contained_in_y && contained_in_z); + } + + // check whether the bounding box contains another bounding box: + inline bool contains(const the_aa_bbox_t & bbox) const + { return contains(bbox.min_) && contains(bbox.max_); } + + // check whether the bounding boxes intersect: + bool intersects(const the_aa_bbox_t & bbox) const; + + // clamp this bounding box to lay within the confines of + // a given bounding box: + void clamp(const the_aa_bbox_t & confines); + + // return a copy of this bounding box clamped within the given confines: + inline the_aa_bbox_t clamped(const the_aa_bbox_t & confines) const + { + the_aa_bbox_t tmp(*this); + tmp.clamp(confines); + return tmp; + } + + // find the intersection of this bounding box with a given ray: + bool intersects_ray(const p3x1_t & o, + const v3x1_t & d, + float & t_min, + float & t_max) const; + + // find the axis id of the largest/smallest dimension of the bounding box: + unsigned int largest_dimension() const; + unsigned int smallest_dimension() const; + + // For debugging, dumps this bounding box into a stream: + void dump(ostream & strm) const; + + // the minimum and maximum points of the bounding box: + p3x1_t min_; + p3x1_t max_; +}; + +//---------------------------------------------------------------- +// operator * +// +inline the_aa_bbox_t +operator * (float s, const the_aa_bbox_t & bbox) +{ return bbox * s; } + +//---------------------------------------------------------------- +// operator << +// +inline ostream & +operator << (ostream & strm, const the_aa_bbox_t & bbox) +{ + bbox.dump(strm); + return strm; +} + + +#endif // THE_AA_BBOX_HXX_ diff --git a/include/IRException.h b/include/IRException.h new file mode 100644 index 0000000..814b6a9 --- /dev/null +++ b/include/IRException.h @@ -0,0 +1,75 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_exception.hxx +// Author : Pavel A. Koshevoy +// Created : Sun Sep 24 18:06:00 MDT 2006 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : an exception convenience class + +#ifndef THE_EXCEPTION_HXX_ +#define THE_EXCEPTION_HXX_ + +// system includes: +#include +#include +#include + + +//---------------------------------------------------------------- +// the_exception_t +// +class the_exception_t : public std::exception +{ +public: + the_exception_t(const char * description = NULL, + const char * file = NULL, + const unsigned int & line = 0) + { + std::ostringstream os; + + if (file != NULL) + { + os << file << ':' << line << " -- "; + } + + if (description != NULL) + { + os << description; + } + + what_ = os.str(); + } + + virtual ~the_exception_t() throw () + {} + + // virtual: + const char * what() const throw() + { return what_.c_str(); } + + // data: + std::string what_; +}; + + +#endif // THE_EXCEPTION_HXX_ diff --git a/include/IRFFT.h b/include/IRFFT.h new file mode 100644 index 0000000..1df1933 --- /dev/null +++ b/include/IRFFT.h @@ -0,0 +1,419 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : fft.hxx +// Author : Pavel A. Koshevoy +// Created : 2005/06/03 10:16 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : Wrapper class and helper functions for working with +// FFTW3 and ITK images. + +#ifndef FFT_HXX_ +#define FFT_HXX_ + +// FFTW includes: +// #include + +// ITK includes: +#include +#include + +// system includes: +#include +#include +#include + + +namespace itk_fft +{ + using TransformDirectionEnum = itk::ComplexToComplexFFTImageFilterEnums::TransformDirection; + //---------------------------------------------------------------- + // set_num_fftw_threads + // + // set's number of threads used by fftw, returns previous value: + // extern std::size_t set_num_fftw_threads(std::size_t num_threads); + + //---------------------------------------------------------------- + // itk_image_t + // + typedef itk::Image itk_image_t; + + //---------------------------------------------------------------- + // itk_imageptr_t + // + typedef itk_image_t::Pointer itk_imageptr_t; + + //---------------------------------------------------------------- + // fft_complex_t + // + typedef std::complex fft_complex_t; + + //---------------------------------------------------------------- + // itk_complex_image_t + // + typedef itk::Image itk_complex_image_t; + + //---------------------------------------------------------------- + // itk_complex_imageptr_t + // + typedef itk_complex_image_t::Pointer itk_complex_imageptr_t; + + //---------------------------------------------------------------- + // fft_data_t + // + class fft_data_t + { + public: + fft_data_t(): image_(), nx_(0), ny_(0) {} + fft_data_t(const unsigned int w, const unsigned int h); + explicit fft_data_t(const itk_imageptr_t & real); + fft_data_t(const itk_imageptr_t & real, + const itk_imageptr_t & imag); + fft_data_t(const fft_data_t & data); + ~fft_data_t() { cleanup(); } + + fft_data_t & operator = (const fft_data_t & data); + + void cleanup(); + + void resize(const unsigned int w, const unsigned int h); + + void fill(const float real, const float imag = 0.0); + + void setup(const itk_imageptr_t & real, + const itk_imageptr_t & imag = itk_imageptr_t()); + + // ITK helpers: + itk_imageptr_t component(const bool imag = 0) const; + inline itk_imageptr_t real() const { return component(0); } + inline itk_imageptr_t imag() const { return component(1); } + + // Apply a low-pass filter to this image. This function will + // zero-out high-frequency components, where the cutoff frequency + // is specified by radius r in [0, 1]. The sharpness of the + // cutoff may be controlled by parameter s, where s == 0 results in + // an ideal low-pass filter, and s == 1 is a low pass filter defined + // by a scaled and shifted cosine function, 1 at the origin, + // 0.5 at the cutoff frequency and 0 at twice the cutoff frequency. + void apply_lp_filter(const double r, const double s = 0); + + // accessors: + inline const itk_complex_image_t * data() const { return image_.get(); } + inline itk_complex_image_t * data() { return image_.get(); } + + inline unsigned int nx() const { return nx_; } + inline unsigned int ny() const { return ny_; } + + inline const fft_complex_t & operator() (const unsigned int & x, + const unsigned int & y) const + { return at(x, y); } + + inline fft_complex_t & operator() (const unsigned int & x, + const unsigned int & y) + { return at(x, y); } + + inline const fft_complex_t & at(const unsigned int & x, + const unsigned int & y) const + { return image_->GetPixel({ x, y }); } + + inline fft_complex_t & at(const unsigned int & x, + const unsigned int & y) + { return image_->GetPixel({ x, y }); } + + protected: + itk_complex_imageptr_t image_; + unsigned int nx_; + unsigned int ny_; + }; + + + //---------------------------------------------------------------- + // fft + // + extern bool + fft(itk_image_t::ConstPointer & in, fft_data_t & out); + + //---------------------------------------------------------------- + // fft + // + extern bool + fft(const fft_data_t & in, fft_data_t & out, TransformDirectionEnum sign = TransformDirectionEnum::FORWARD); + + //---------------------------------------------------------------- + // ifft + // + extern bool + ifft(const fft_data_t & in, fft_data_t & out); + + //---------------------------------------------------------------- + // ifft + // + inline fft_data_t + ifft(const fft_data_t & in) + { + fft_data_t out; +#ifndef NDEBUG // get around an annoying compiler warning: + bool ok = +#endif + ifft(in, out); + assert(ok); + return out; + } + + + // //---------------------------------------------------------------- + // // fn_fft_c_t + // // + // typedef fft_complex_t(*fn_fft_c_t)(const fft_complex_t & in); + + // //---------------------------------------------------------------- + // // fn_fft_cc_t + // // + // typedef fft_complex_t(*fn_fft_cc_t)(const fft_complex_t & a, + // const fft_complex_t & b); + + // //---------------------------------------------------------------- + // // elem_by_elem + // // + // extern void + // elem_by_elem(fn_fft_c_t f, + // const fft_data_t & in, + // fft_data_t & out); + + // //---------------------------------------------------------------- + // // elem_by_elem + // // + // extern void + // elem_by_elem(fft_complex_t(*f)(const float & a, + // const fft_complex_t & b), + // const float & a, + // const fft_data_t & b, + // fft_data_t & c); + + // //---------------------------------------------------------------- + // // elem_by_elem + // // + // extern void + // elem_by_elem(fft_complex_t(*f)(const fft_complex_t & a, + // const float & b), + // const fft_data_t & a, + // const float & b, + // fft_data_t & c); + + // //---------------------------------------------------------------- + // // elem_by_elem + // // + // extern void + // elem_by_elem(fft_complex_t(*f)(const fft_complex_t & a, + // const fft_complex_t & b), + // const fft_complex_t & a, + // const fft_data_t & b, + // fft_data_t & c); + + // //---------------------------------------------------------------- + // // elem_by_elem + // // + // extern void + // elem_by_elem(fft_complex_t(*f)(const fft_complex_t & a, + // const fft_complex_t & b), + // const fft_data_t & a, + // const fft_complex_t & b, + // fft_data_t & c); + + // //---------------------------------------------------------------- + // // elem_by_elem + // // + // extern void + // elem_by_elem(fn_fft_cc_t f, + // const fft_data_t & a, + // const fft_data_t & b, + // fft_data_t & c); + + + // //---------------------------------------------------------------- + // // conj + // // + // // element-by-element complex conjugate: + // // + // inline void + // conj(const fft_data_t & in, fft_data_t & out) + // { elem_by_elem(std::conj, in, out); } + + // //---------------------------------------------------------------- + // // conj + // // + // inline fft_data_t + // conj(const fft_data_t & in) + // { + // fft_data_t out; + // conj(in, out); + // return out; + // } + + + // //---------------------------------------------------------------- + // // sqrt + // // + // // element-by-element square root: + // // + // inline void + // sqrt(const fft_data_t & in, fft_data_t & out) + // { elem_by_elem(std::sqrt, in, out); } + + // //---------------------------------------------------------------- + // // sqrt + // // + // inline fft_data_t + // sqrt(const fft_data_t & in) + // { + // fft_data_t out; + // sqrt(in, out); + // return out; + // } + + + // //---------------------------------------------------------------- + // // _mul + // // + // template + // inline fft_complex_t + // _mul(const a_t & a, const b_t & b) + // { return a * b; } + + // //---------------------------------------------------------------- + // // mul + // // + // // element-by-element multiplication, c = a * b: + // // + // template + // inline void + // mul(const a_t & a, const b_t & b, fft_data_t & c) + // { elem_by_elem(_mul, a, b, c); } + + // //---------------------------------------------------------------- + // // mul + // // + // template + // inline fft_data_t + // mul(const a_t & a, const b_t & b) + // { + // fft_data_t c; + // mul(a, b, c); + // return c; + // } + + + //---------------------------------------------------------------- + // _div + // + template + inline fft_complex_t + _div(const a_t & a, const b_t & b) + { return a / b; } + + // //---------------------------------------------------------------- + // // div + // // + // // element-by-element division, c = a / b: + // // + // template + // inline void + // div(const a_t & a, const b_t & b, fft_data_t & c) + // { elem_by_elem(_div, a, b, c); } + + // //---------------------------------------------------------------- + // // div + // // + // template + // inline fft_data_t + // div(const a_t & a, const b_t & b) + // { + // fft_data_t c; + // div(a, b, c); + // return c; + // } + + + //---------------------------------------------------------------- + // _add + // + template + inline fft_complex_t + _add(const a_t & a, const b_t & b) + { return a + b; } + + // //---------------------------------------------------------------- + // // add + // // + // // element-by-element addition, c = a + b: + // // + // template + // inline void + // add(const a_t & a, const b_t & b, fft_data_t & c) + // { elem_by_elem(_add, a, b, c); } + + // //---------------------------------------------------------------- + // // add + // // + // template + // inline fft_data_t + // add(const a_t & a, const b_t & b) + // { + // fft_data_t c; + // add(a, b, c); + // return c; + // } + + + // //---------------------------------------------------------------- + // // _sub + // // + // template + // inline fft_complex_t + // _sub(const a_t & a, const b_t & b) + // { return a - b; } + + // //---------------------------------------------------------------- + // // sub + // // + // // element-by-element subtraction, c = a - b: + // // + // template + // inline void + // sub(const a_t & a, const b_t & b, fft_data_t & c) + // { elem_by_elem(_sub, a, b, c); } + + // //---------------------------------------------------------------- + // // sub + // // + // template + // inline fft_data_t + // sub(const a_t & a, const b_t & b) + // { + // fft_data_t c; + // sub(a, b, c); + // return c; + // } +} + + +#endif // FFT_HXX_ diff --git a/include/IRFFTCommon.h b/include/IRFFTCommon.h new file mode 100644 index 0000000..6c4db1c --- /dev/null +++ b/include/IRFFTCommon.h @@ -0,0 +1,594 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : fft_common.hxx +// Author : Pavel A. Koshevoy +// Created : 2005/11/10 14:05 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : Helper functions for image alignment (registration) +// using phase correlation to find the translation vector. + +#ifndef FFT_COMMON_HXX_ +#define FFT_COMMON_HXX_ + +// local includes: +#include "itkIRCommon.h" +#include "IRFFT.h" +#include "itkIRDynamicArray.h" +#include "IRLog.h" + +// system includes: +#include +#include +#include + +#ifndef WIN32 +# include +#endif + +// namespace access: +using namespace itk_fft; + +//---------------------------------------------------------------- +// DEBUG_PDF +// +// #define DEBUG_PDF + +#ifdef DEBUG_PDF +// FIXME: this is not thread safe: +# define DEBUG_PDF_PFX the_text_t("PDF-") +# define DEBUG_CLUSTERS +# define DEBUG_MARKERS +extern unsigned int DEBUG_COUNTER1; +extern unsigned int DEBUG_COUNTER2; +#else +# define DEBUG_PDF_PFX the_text_t("") +#endif + + +//---------------------------------------------------------------- +// local_max_t +// +class local_max_t +{ +public: + local_max_t(const double value, const double x, const double y, const unsigned int area) + : value_(value) + , x_(x) + , y_(y) + , area_(area) + {} + + inline bool + operator==(const local_max_t & lm) const + { + return (value_ == lm.value_) && (x_ == lm.x_) && (y_ == lm.y_); + } + + inline bool + operator<(const local_max_t & lm) const + { + return value_ < lm.value_; + } + + inline bool + operator>(const local_max_t & lm) const + { + return value_ > lm.value_; + } + + // cluster value: + double value_; + + // center of mass of the cluster: + double x_; + double y_; + + // area of the cluster: + unsigned int area_; +}; + +//---------------------------------------------------------------- +// operator << +// +inline std::ostream & +operator<<(std::ostream & sout, const local_max_t & lm) +{ + return sout << lm.value_ << '\t' << lm.x_ << '\t' << lm.y_ << '\t' << lm.area_ << endl; +} + +//---------------------------------------------------------------- +// cluster_bbox_t +// +class cluster_bbox_t +{ +public: + cluster_bbox_t() { reset(); } + + void + reset() + { + min_[0] = std::numeric_limits::max(); + min_[1] = std::numeric_limits::max(); + max_[0] = std::numeric_limits::min(); + max_[1] = std::numeric_limits::min(); + } + + void + update(int x, int y) + { + min_[0] = std::min(min_[0], x); + min_[1] = std::min(min_[1], y); + max_[0] = std::max(max_[0], x); + max_[1] = std::max(max_[1], y); + } + + int min_[2]; + int max_[2]; +}; + +//---------------------------------------------------------------- +// find_maxima_cm +// +// The percentage below refers to the number of pixels that fall +// below the maxima. Thus, the number of pixels above the maxima +// is 1 - percentage. This way it is possible to specify a +// thresholding value without knowing anything about the image. +// +// The given image is thresholded, the resulting clusters/blobs +// are identified/classified, the center of mass of each cluster +// is treated as a maxima in the image. +// +// Returns the number of maxima found. +// +extern unsigned int +find_maxima_cm(std::list & max_list, + const itk_image_t::Pointer & image, + const double percentage = 0.9995, + const the_text_t & prefix = DEBUG_PDF_PFX, + const the_text_t & suffix = the_text_t(".png")); + + +//---------------------------------------------------------------- +// find_correlation +// +template +unsigned int +find_correlation(std::list & max_list, + const TImage * fi, + const TImage * mi, + double lp_filter_r, + double lp_filter_s, + const double min_overlap, + const double max_overlap) +{ + itk_image_t::Pointer z0 = cast(fi); + itk_image_t::Pointer z1 = cast(mi); + return find_correlation(max_list, z0, z1, lp_filter_r, lp_filter_s, min_overlap, max_overlap); +} + + +//---------------------------------------------------------------- +// find_correlation +// +template <> +unsigned int +find_correlation(std::list & max_list, + const itk_image_t * fi, + const itk_image_t * mi, + + + // low pass filter parameters + // (resampled data requires less smoothing): + double lp_filter_r, + double lp_filter_s, + const double overlap_min, + const double overlap_max); + + +//---------------------------------------------------------------- +// find_correlation +// +// This is a backwards compatibility API +// +template +unsigned int +find_correlation(const TImage * fi, + const TImage * mi, + std::list & max_list, + bool resampled_data, + const double overlap_min, + const double overlap_max) +{ + double lp_filter_r = resampled_data ? 0.9 : 0.5; + return find_correlation(max_list, fi, mi, lp_filter_r, 0.1, overlap_min, overlap_max); +} + + +//---------------------------------------------------------------- +// threshold_maxima +// +// Discard maxima whose mass is below a given threshold ratio +// of the total mass of all maxima: +// +void +threshold_maxima(std::list & max_list, const double threshold); + +//---------------------------------------------------------------- +// reject_negligible_maxima +// +// Discard maxima that are worse than the best maxima by a factor +// greater than the given threshold ratio: +// +// Returns the size of the new list. +// +unsigned int +reject_negligible_maxima(std::list & max_list, const double threshold); + +//---------------------------------------------------------------- +// overlap_t +// +class overlap_t +{ +public: + overlap_t() + : id_(~0) + , overlap_(0.0) + {} + + overlap_t(const unsigned int id, const double overlap) + : id_(id) + , overlap_(overlap) + {} + + inline bool + operator==(const overlap_t & d) const + { + return id_ == d.id_; + } + + inline bool + operator<(const overlap_t & d) const + { + return overlap_ < d.overlap_; + } + + inline bool + operator>(const overlap_t & d) const + { + return overlap_ > d.overlap_; + } + + unsigned int id_; + double overlap_; +}; + +//---------------------------------------------------------------- +// reject_negligible_overlap +// +void +reject_negligible_overlap(std::list & ol, const double threshold); + + +//---------------------------------------------------------------- +// estimate_displacement +// +template +double +estimate_displacement(the_log_t & log, + const TImage * a, + const TImage * b, + const local_max_t & lm, + translate_transform_t::Pointer & transform, + image_t::PointType offset_min, + image_t::PointType offset_max, + const double overlap_min = 0.0, + const double overlap_max = 1.0, + const mask_t * mask_a = NULL, + const mask_t * mask_b = NULL) +{ + // FIXME: +#ifdef DEBUG_PDF + the_text_t fn_debug = the_text_t::number(DEBUG_COUNTER1, 3, '0') + the_text_t("-") + + the_text_t::number(DEBUG_COUNTER2, 1, '0') + the_text_t("-"); +#endif + + itk_image_t::SizeType max_sz = calc_padding(a, b); + const unsigned int & w = max_sz[0]; + const unsigned int & h = max_sz[1]; + + // evaluate 4 permutations of the maxima: + // typedef itk::NormalizedCorrelationImageToImageMetric + typedef itk::MeanSquaresImageToImageMetric metric_t; + typedef itk::LinearInterpolateImageFunction interpolator_t; + + typedef typename TImage::SpacingType spacing_t; + spacing_t spacing = a->GetSpacing(); + double sx = spacing[0]; + double sy = spacing[1]; + + // evaluate 4 permutation of the maxima: + // TODO: James A: Is this for the fft? We can run the FFT in such a way that we just know... + const double x = lm.x_; + const double y = lm.y_; + + const vec2d_t t[] = { + vec2d(sx * x, sy * y), vec2d(sx * (x - w), sy * y), vec2d(sx * x, sy * (y - h)), vec2d(sx * (x - w), sy * (y - h)) + }; + + double best_metric = std::numeric_limits::max(); + itk_image_t::SizeType max_sz_with_spacing = max_sz; + max_sz_with_spacing[0] = max_sz[0] * sx; + max_sz_with_spacing[1] = max_sz[1] * sy; + + + for (unsigned int i = 0; i < 4; i++) + { + double overlap = OverlapPercent(max_sz_with_spacing, t[i]); + + if (overlap < overlap_min || overlap > overlap_max) + continue; + + translate_transform_t::Pointer ti = translate_transform_t::New(); + ti->SetOffset(t[i]); + + // FIXME: +#ifdef DEBUG_PDF + the_text_t fn = fn_debug + the_text_t::number(i) + ".png"; + save_rgb(fn, a, b, ti, mask_a, mask_b); +#endif + + const double area_ratio = overlap_ratio(a, b, ti); + int old_precision = log.precision(); + log.precision(2); + log << i << ": " << setw(3) << std::fixed << area_ratio * 100.0 << "% of overlap, "; + log.precision(old_precision); + + if (area_ratio < overlap_min || area_ratio > overlap_max) + { + log << "skipping..." << endl; + continue; + } + + // double metric = eval_metric(ti, a, b); + double metric = my_metric(a, b, ti, mask_a, mask_b, overlap_min, overlap_max); + log << std::scientific << metric; + if (metric < best_metric) + { + transform = ti; + best_metric = metric; + log << " - better..." << endl; + +#ifdef DEBUG_PDF + save_rgb(fn_debug + the_text_t::number(i) + "-better.png", a, b, ti, mask_a, mask_b); +#endif + } + else + { + log << " - worse..." << endl; + } + } + + return best_metric; +} + + +//---------------------------------------------------------------- +// match_one_pair +// +// match two images, find the best matching transform and +// return the corresponding match metric value +// +template +double +match_one_pair(the_log_t & log, + const bool images_were_resampled, + const bool use_std_mask, + const TImage * fi, + const TImage * mi, + const TMask * fi_mask, + const TMask * mi_mask, + + const double overlap_min, + const double overlap_max, + + image_t::PointType offset_min, + image_t::PointType offset_max, + + translate_transform_t::Pointer & ti, + std::list & peaks, + unsigned int & num_peaks, + + // ideally this should be one, but radial distortion may + // generate several valid peaks (up to 4), so it may be + // necessary to consider more peaks for the unmatched images: + const unsigned int max_peaks) +{ +#ifdef DEBUG_PDF + DEBUG_COUNTER1++; +#endif + + ti = NULL; + + unsigned int total_peaks = 0; + if (use_std_mask) + { + // ugh, blank out 5 percent of the image at the bottom + // to cover up the image id: + typename TImage::SizeType fi_sz = fi->GetLargestPossibleRegion().GetSize(); + typename TImage::SizeType mi_sz = mi->GetLargestPossibleRegion().GetSize(); + + unsigned int fi_y = (unsigned int)(0.95 * fi_sz[1]); + unsigned int mi_y = (unsigned int)(0.95 * mi_sz[1]); + + typename TImage::Pointer fi_filled = cast(fi); + fill(fi_filled, 0, fi_y, fi_sz[0], fi_sz[1] - fi_y, 0); + + typename TImage::Pointer mi_filled = cast(mi); + fill(mi_filled, 0, mi_y, mi_sz[0], mi_sz[1] - mi_y, 0); + + total_peaks = + find_correlation(fi_filled, mi_filled, peaks, images_were_resampled, overlap_min, overlap_max); + } + else + { + total_peaks = find_correlation(fi, mi, peaks, images_were_resampled, overlap_min, overlap_max); + } + + num_peaks = reject_negligible_maxima(peaks, 3.0); + log << num_peaks << '/' << total_peaks << " eligible peaks, "; + + if (num_peaks > max_peaks) + { + log << "skipping..." << endl; + return std::numeric_limits::max(); + } + + // choose the best peak: + double best_metric = std::numeric_limits::max(); + +#ifdef DEBUG_PDF + DEBUG_COUNTER2 = 0; +#endif + for (std::list::iterator j = peaks.begin(); j != peaks.end(); ++j) + { + log << "evaluating permutations..." << endl; + const local_max_t & lm = *j; + + translate_transform_t::Pointer tmp = translate_transform_t::New(); + double metric = estimate_displacement( + log, fi, mi, lm, tmp, offset_min, offset_max, overlap_min, overlap_max, fi_mask, mi_mask); + if (metric < best_metric) + { + best_metric = metric; + ti = tmp; + } + +#ifdef DEBUG_PDF + DEBUG_COUNTER2++; +#endif + } + + return best_metric; +} + + +//---------------------------------------------------------------- +// match_one_pair +// +// match two images, find the best matching transform and +// return the corresponding match metric value +// +template +double +match_one_pair(the_log_t & log, + const bool images_were_resampled, + const bool use_std_mask, + const TImage * fi, + const TImage * mi, + const TMask * fi_mask, + const TMask * mi_mask, + + const double overlap_min, + const double overlap_max, + + image_t::PointType offset_min, + image_t::PointType offset_max, + + const unsigned int node_id, + translate_transform_t::Pointer & ti, + std::pair> & peaks, + + // ideally this should be one, but radial distortion may + // generate several valid peaks (up to 4), so it may be + // necessary to consider more peaks for the unmatched images: + const unsigned int max_peaks) +{ + unsigned int peak_list_size = 0; + std::list peak_list; + double m = match_one_pair(log, + images_were_resampled, + use_std_mask, + fi, + mi, + fi_mask, + mi_mask, + overlap_min, + overlap_max, + offset_min, + offset_max, + ti, + peak_list, + peak_list_size, + max_peaks); + + // this info will be used when trying to match the unmatched images: + if (peak_list_size != 0 && peak_list_size <= max_peaks && + (peaks.second.empty() || peaks.second.size() > peak_list_size)) + { + peaks.first = node_id; + peaks.second.clear(); + peaks.second.splice(peaks.second.end(), peak_list); + } + + return m; +} + +//---------------------------------------------------------------- +// match_one_pair +// +template +double +match_one_pair(the_log_t & log, + const bool images_were_resampled, + const bool use_std_mask, + const TImage * fi, + const TImage * mi, + const TMask * fi_mask, + const TMask * mi_mask, + const double overlap_min, + const double overlap_max, + image_t::PointType offset_min, + image_t::PointType offset_max, + translate_transform_t::Pointer & ti) +{ + std::list peaks; + unsigned int num_peaks = 0; + return match_one_pair(log, + images_were_resampled, + use_std_mask, + fi, + mi, + fi_mask, + mi_mask, + overlap_min, + overlap_max, + offset_min, + offset_max, + ti, + peaks, + num_peaks, + UINT_MAX); +} + + +#endif // FFT_COMMON_HXX_ diff --git a/include/IRGridCommon.h b/include/IRGridCommon.h new file mode 100644 index 0000000..490eb37 --- /dev/null +++ b/include/IRGridCommon.h @@ -0,0 +1,1612 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : grid_common.hxx +// Author : Pavel A. Koshevoy +// Created : Wed Jan 10 09:31:00 MDT 2007 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : code used to refine mesh transform control points + +#ifndef GRID_COMMON_HXX_ +#define GRID_COMMON_HXX_ + +// the includes: +#include "itkIRCommon.h" +#include "IRFFTCommon.h" +#include "IRGridTransform.h" +#include "itkGridTransform.h" +#include "itkRegularStepGradientDescentOptimizer2.h" +#include "IROptimizerObserver.h" +#include "IRLog.h" + +// system includes: +#include +#include +#include +#include +#include + +// ITK includes: +#include +#include +#include +#include +#include + +//---------------------------------------------------------------- +// DEBUG_REFINE_ONE_POINT +// +// #define DEBUG_REFINE_ONE_POINT + +//---------------------------------------------------------------- +// DEBUG_ESTIMATE_DISPLACEMENT +// +// #define DEBUG_ESTIMATE_DISPLACEMENT + +#if defined(DEBUG_REFINE_ONE_POINT) || defined(DEBUG_ESTIMATE_DISPLACEMENT) +static int COUNTER = 0; +#endif + + +//---------------------------------------------------------------- +// optimizer_t +// +typedef itk::RegularStepGradientDescentOptimizer2 optimizer_t; + + +//---------------------------------------------------------------- +// setup_grid_transform +// +extern bool +setup_grid_transform(the_grid_transform_t & transform, + unsigned int rows, + unsigned int cols, + const pnt2d_t & tile_min, + const pnt2d_t & tile_max, + const mask_t * tile_mask, + base_transform_t::ConstPointer mosaic_to_tile, + unsigned int max_iterations = 100, + double min_step_scale = 1e-12, + double min_error_sqrd = 1e-16, + unsigned int pick_up_pace_steps = 5); + +//---------------------------------------------------------------- +// setup_mesh_transform +// +extern bool +setup_mesh_transform(the_mesh_transform_t & transform, + unsigned int rows, + unsigned int cols, + const pnt2d_t & tile_min, + const pnt2d_t & tile_max, + const mask_t * tile_mask, + base_transform_t::ConstPointer mosaic_to_tile, + unsigned int max_iterations, + double min_step_scale, + double min_error_sqrd, + unsigned int pick_up_pace_steps); + +//---------------------------------------------------------------- +// estimate_displacement +// +template +void +estimate_displacement(the_log_t & log, + + // current best metric: + double & best_metric, + translate_transform_t::Pointer & best_transform, + + // origin offset of the small neighborhood of image A + // within the large image A: + const vec2d_t & offset, + + const TImage * a, // large image A + const TImage * b, // small image B + + // this could be the size of B (ususally when matching + // small neighborhoods in A and B), or the + // maximum dimesions of A and B (plain image matching): + const typename TImage::SizeType & period_sz, + + const local_max_t & lm, + const double overlap_min = 0.05, + const double overlap_max = 1.0, + const mask_t * mask_a = NULL, + const mask_t * mask_b = NULL, + + const unsigned int num_perms = 4) +{ + vec2d_t best_offset = best_transform->GetOffset(); + + const unsigned int & w = period_sz[0]; + const unsigned int & h = period_sz[1]; + + typedef typename TImage::SpacingType spacing_t; + spacing_t spacing = b->GetSpacing(); + double sx = spacing[0]; + double sy = spacing[1]; + + // evaluate 4 permutation of the maxima: + const double x = lm.x_; + const double y = lm.y_; + + const vec2d_t t[] = { + vec2d(sx * x, sy * y), vec2d(sx * (x - w), sy * y), vec2d(sx * x, sy * (y - h)), vec2d(sx * (x - w), sy * (y - h)) + }; + +#ifdef DEBUG_ESTIMATE_DISPLACEMENT + static const the_text_t fn_save("/tmp/estimate_displacement-"); + static int COUNTER2 = 0; + the_text_t suffix = the_text_t::number(COUNTER, 3, '0') + "-" + the_text_t::number(COUNTER2, 3, '0') + "-"; + COUNTER2++; + + // FIXME: + { + translate_transform_t::Pointer ti = translate_transform_t::New(); + ti->SetOffset(-offset); + save_rgb(fn_save + suffix + "0-init.png", a, b, ti, mask_a, mask_b); + } +#endif + + std::vector metricArray; + metricArray.reserve(num_perms); + + std::vector transformArray; + transformArray.reserve(num_perms); + + for (int i = 0; i < (int)num_perms; i++) + { + translate_transform_t::Pointer ti = translate_transform_t::New(); + ti->SetOffset(t[i] - offset); + + double area_ratio = overlap_ratio(a, b, ti); + log << i << ": " << setw(3) << int(area_ratio * 100.0) << "% of overlap, "; + + if (area_ratio < overlap_min || area_ratio > overlap_max) + { + log << "skipping..." << endl; + continue; + } + +#if 0 + typedef itk::LinearInterpolateImageFunction interpolator_t; + // typedef itk::MeanSquaresImageToImageMetric metric_t; + typedef itk::NormalizedCorrelationImageToImageMetric + metric_t; + double metric = eval_metric(ti, + a, + b, + mask_a, + mask_b); +#else + double metric = my_metric(area_ratio, a, b, ti, mask_a, mask_b, overlap_min, overlap_max); + metricArray.push_back(metric); +#endif + + transformArray.push_back(ti); + } + + for (int i = 0; i < (int)metricArray.size(); i++) + { + double metric = metricArray[i]; + translate_transform_t::Pointer ti = transformArray[i]; + + log << metric; + if (metric < best_metric) + { + best_offset = t[i]; + best_metric = metric; + log << " - better..." << endl; + +#ifdef DEBUG_ESTIMATE_DISPLACEMENT + // FIXME: + save_rgb(fn_save + suffix + the_text_t::number(i + 1) + "-perm.png", a, b, ti, mask_a, mask_b); +#endif + } + else + { + log << " - worse..." << endl; + } + } + + metricArray.clear(); + transformArray.clear(); + + best_transform->SetOffset(best_offset); +} + + +//---------------------------------------------------------------- +// match_one_pair +// +// match two images, find the best matching transform and +// return the corresponding match metric value +// +template +double +match_one_pair(the_log_t & log, + translate_transform_t::Pointer & best_transform, + + const TImage * fi, + const mask_t * fi_mask, + + const TImage * mi, + const mask_t * mi_mask, + + // this could be the size of B (ususally when matching + // small neighborhoods in A and B), or the + // maximum dimesions of A and B (plain image matching): + const typename TImage::SizeType & period_sz, + + const double & overlap_min, + const double & overlap_max, + + image_t::PointType offset_min, + image_t::PointType offset_max, + + const double & lp_filter_r, + const double & lp_filter_s, + + const unsigned int max_peaks, + const bool & consider_zero_displacement) +{ + static const vec2d_t offset = vec2d(0, 0); + best_transform = NULL; + + std::list max_list; + unsigned int total_peaks = + find_correlation(max_list, fi, mi, lp_filter_r, lp_filter_s, overlap_min, overlap_max); + + unsigned int num_peaks = reject_negligible_maxima(max_list, 2.0); + log << num_peaks << '/' << total_peaks << " eligible peaks, "; + + if (num_peaks > max_peaks) + { + log << "skipping..." << endl; + return std::numeric_limits::max(); + } + + double best_metric = std::numeric_limits::max(); + best_transform = translate_transform_t::New(); + best_transform->SetIdentity(); + + if (consider_zero_displacement) + { + // make sure to consider the no-displacement case: + estimate_displacement(log, + best_metric, + best_transform, + offset, + fi, + mi, + period_sz, + local_max_t(0, 0, 0, 0), + 0, + 1, + fi_mask, + mi_mask, + 1); // don't try permutations + } + + // choose the best peak: + for (std::list::iterator j = max_list.begin(); j != max_list.end(); ++j) + { + log << "evaluating permutations..." << endl; + const local_max_t & lm = *j; + + double prev_metric = best_metric; + vec2d_t prev_offset = best_transform->GetOffset(); + + estimate_displacement(log, + best_metric, + best_transform, + offset, + fi, + mi, + period_sz, + lm, // local maxima record + overlap_min, + overlap_max, + fi_mask, + mi_mask); + + // re-evaluate the overlap in the context of the small neighborhoods: + double overlap = overlap_ratio(fi, mi, best_transform); + if (overlap < overlap_min || overlap > overlap_max) + { + best_metric = prev_metric; + best_transform->SetOffset(prev_offset); + } + } + + return best_metric; +} + + +//---------------------------------------------------------------- +// match_one_pair +// +// match two images, find the best matching transform and +// return the corresponding match metric value +// +template +double +match_one_pair(the_log_t & log, + translate_transform_t::Pointer & best_transform, + + const TImage * fi_large, + const mask_t * fi_large_mask, + + const TImage * fi, + const mask_t * /* fi_mask */, + + const TImage * mi, + const mask_t * mi_mask, + + // origin offset of the small fixed image neighborhood + // within the full image: + const vec2d_t & offset, + + const double & overlap_min, + const double & overlap_max, + + // image_t::PointType offset_min, + // image_t::PointType offset_max, + + const double & lp_filter_r, + const double & lp_filter_s, + + const unsigned int max_peaks, + const bool & consider_zero_displacement) +{ + best_transform = NULL; + + std::list max_list; + unsigned int total_peaks = + find_correlation(max_list, fi, mi, lp_filter_r, lp_filter_s, overlap_min, overlap_max); + + unsigned int num_peaks = reject_negligible_maxima(max_list, 2.0); + log << num_peaks << '/' << total_peaks << " eligible peaks, "; + + if (num_peaks > max_peaks) + { + log << "skipping..." << endl; + return std::numeric_limits::max(); + } + + double best_metric = std::numeric_limits::max(); + best_transform = translate_transform_t::New(); + best_transform->SetIdentity(); + + typename TImage::SizeType period_sz = mi->GetLargestPossibleRegion().GetSize(); + + if (consider_zero_displacement) + { + // make sure to consider the no-displacement case: + estimate_displacement(log, + best_metric, + best_transform, + offset, + fi_large, + mi, + period_sz, + local_max_t(0, 0, 0, 0), + 0, + 1, + fi_large_mask, + mi_mask, + 1); // don't try permutations + } + + // choose the best peak: + for (std::list::iterator j = max_list.begin(); j != max_list.end(); ++j) + { + log << "evaluating permutations..." << endl; + const local_max_t & lm = *j; + + double prev_metric = best_metric; + vec2d_t prev_offset = best_transform->GetOffset(); + + estimate_displacement(log, + best_metric, + best_transform, + offset, + fi_large, + mi, + period_sz, + lm, // local maxima record + overlap_min, + overlap_max, + fi_large_mask, + mi_mask); + + // re-evaluate the overlap in the context of the small neighborhoods: + double overlap = overlap_ratio(fi, mi, best_transform); + if (overlap < overlap_min || overlap > overlap_max) + { + best_metric = prev_metric; + best_transform->SetOffset(prev_offset); + } + } + + return best_metric; +} + + +//---------------------------------------------------------------- +// refine_one_point_fft +// +template +bool +refine_one_point_fft(the_log_t & log, + + vec2d_t & shift, + const pnt2d_t & origin, + + // the large images and their masks: + const TImage * tile_0, + const mask_t * mask_0, + + // the extracted small neighborhoods and their masks: + const TImage * img_0, + const mask_t * msk_0, + const TImage * img_1, + const mask_t * msk_1) +{ + // feed the two neighborhoods into the FFT translation estimator: + translate_transform_t::Pointer translate; + double metric = match_one_pair(log, + + // best translation transform: + translate, + + // fixed image in full: + tile_0, + mask_0, + + // fixed image small neighborhood: + img_0, + msk_0, + + // moving image small neighborhood: + img_1, + msk_1, + + // origin offset of the small fixed image + // neighborhood in the large image: + vec2d(origin[0], origin[1]), + + 0.25, // overlap min + 1.0, // overlap max + 0.5, // low pass filter r + 0.1, // low pass filter s + 10, // number of peaks + true); // consider the no-displacement case + +#ifdef DEBUG_REFINE_ONE_POINT + static const the_text_t fn_save("/tmp/refine_one_point_fft-"); + the_text_t suffix = the_text_t::number(COUNTER, 3, '0'); + + save_composite(fn_save + suffix + "-a.png", img_0, img_1, identity_transform_t::New(), true); +#endif // DEBUG_REFINE_ONE_POINT + +#if defined(DEBUG_REFINE_ONE_POINT) || defined(DEBUG_ESTIMATE_DISPLACEMENT) + COUNTER++; +#endif + + if (metric == std::numeric_limits::max()) + { + return false; + } + +#ifdef DEBUG_REFINE_ONE_POINT + save_composite(fn_save + suffix + "-b.png", img_0, img_1, translate.GetPointer(), true); +#endif // DEBUG_REFINE_ONE_POINT + + shift = -translate->GetOffset(); + return true; +} + + +//---------------------------------------------------------------- +// refine_one_point_helper +// +template +bool +refine_one_point_helper(const pnt2d_t & center, + const pnt2d_t & origin, + const double & min_overlap, + + // the large images and their masks: + const TImage * tile_0, + const mask_t * mask_0, + const TImage * tile_1, + const mask_t * mask_1, + + // transform that maps from the space of + // tile 0 to the space of tile 1: + const base_transform_t * t_01, + + // the extracted small neighborhoods and their masks: + TImage * img_0, + mask_t * msk_0, + TImage * img_1, + mask_t * msk_1, + + // neighborhood size and pixel spacing: + const typename TImage::SizeType & sz, + const typename TImage::SpacingType & sp) +{ + // setup the image interpolators: + typedef itk::LinearInterpolateImageFunction interpolator_t; + typename interpolator_t::Pointer interpolator[] = { interpolator_t::New(), interpolator_t::New() }; + + interpolator[0]->SetInputImage(tile_0); + interpolator[1]->SetInputImage(tile_1); + + if (!interpolator[0]->IsInsideBuffer(center)) + { + // don't bother extracting neigborhoods if the neighborhood center + // doesn't fall inside both images: + return false; + } + + // temporaries: + pnt2d_t mosaic_pt; + pnt2d_t tile_pt; + typename TImage::IndexType index; + + // keep count of pixel in the neighborhood: + unsigned int area[] = { 0, 0 }; + + // extract a neighborhood of the given point from both tiles: + for (unsigned int y = 0; y < sz[1]; y++) + { + mosaic_pt[1] = origin[1] + double(y) * sp[1]; + index[1] = y; + for (unsigned int x = 0; x < sz[0]; x++) + { + mosaic_pt[0] = origin[0] + double(x) * sp[0]; + index[0] = x; + + // fixed image: + tile_pt = mosaic_pt; + if (interpolator[0]->IsInsideBuffer(tile_pt) && pixel_in_mask(mask_0, tile_pt)) + { + double p = interpolator[0]->Evaluate(tile_pt); + img_0->SetPixel(index, (unsigned char)(std::min(255.0, p))); + msk_0->SetPixel(index, 1); + area[0]++; + } + else + { + img_0->SetPixel(index, 0); + msk_0->SetPixel(index, 0); + } + + // moving image: + tile_pt = t_01->TransformPoint(mosaic_pt); + if (interpolator[1]->IsInsideBuffer(tile_pt) && pixel_in_mask(mask_1, tile_pt)) + { + double p = interpolator[1]->Evaluate(tile_pt); + img_1->SetPixel(index, (unsigned char)(std::min(255.0, p))); + msk_1->SetPixel(index, 1); + area[1]++; + } + else + { + img_1->SetPixel(index, 0); + msk_1->SetPixel(index, 0); + } + } + } + + // skip points which don't have enough neighborhood information: + double max_area = double(sz[0] * sz[1]); + double a[] = { double(area[0]) / max_area, double(area[1]) / max_area }; + + if (a[0] < min_overlap || a[1] < min_overlap) + { + return false; + } + + return true; +} + +//---------------------------------------------------------------- +// refine_one_point_fft +// +template +bool +refine_one_point_fft(the_log_t & log, + vec2d_t & shift, + const pnt2d_t & center, + const pnt2d_t & origin, + const double & min_overlap, + + // the large images and their masks: + const TImage * tile_0, + const mask_t * mask_0, + const TImage * tile_1, + const mask_t * mask_1, + + // transform that maps from the space of + // tile 0 to the space of tile 1: + const base_transform_t * t_01, + + // the extracted small neighborhoods and their masks: + TImage * img_0, + mask_t * msk_0, + TImage * img_1, + mask_t * msk_1, + + // neighborhood size and pixel spacing: + const typename TImage::SizeType & sz, + const typename TImage::SpacingType & sp) +{ + if (!refine_one_point_helper( + center, origin, min_overlap, tile_0, mask_0, tile_1, mask_1, t_01, img_0, msk_0, img_1, msk_1, sz, sp)) + { + return false; + } + + return refine_one_point_fft(log, shift, origin, tile_0, mask_0, img_0, msk_0, img_1, msk_1); +} + +//---------------------------------------------------------------- +// refine_one_point_fft +// +template +bool +refine_one_point_fft(the_log_t & log, + + vec2d_t & shift, + const pnt2d_t & center, + const double & min_overlap, + + const TImage * tile_0, + const mask_t * mask_0, + const TImage * tile_1, + const mask_t * mask_1, + + // transform that maps from the space of + // tile 0 to the space of tile 1: + const base_transform_t * t_01, + + // neighborhood size: + const unsigned int & neighborhood) +{ + typename TImage::SpacingType sp = tile_1->GetSpacing(); + typename TImage::SizeType sz; + sz[0] = neighborhood; + sz[1] = neighborhood; + + pnt2d_t origin(center); + origin[0] -= 0.5 * double(sz[0]) * sp[0]; + origin[1] -= 0.5 * double(sz[1]) * sp[1]; + + typename TImage::Pointer img[] = { make_image(sz), make_image(sz) }; + + mask_t::Pointer msk[] = { make_image(sz), make_image(sz) }; + + img[0]->SetSpacing(sp); + img[1]->SetSpacing(sp); + msk[0]->SetSpacing(sp); + msk[1]->SetSpacing(sp); + + return refine_one_point_fft(log, + shift, + center, + origin, + min_overlap, + tile_0, + mask_0, + tile_1, + mask_1, + t_01, + img[0], + msk[0], + img[1], + msk[1], + sz, + sp); +} + +//---------------------------------------------------------------- +// refine_one_point_fft +// +template +bool +refine_one_point_fft(the_log_t & log, + vec2d_t & shift, + + // the extracted small neighborhoods and their masks: + const TImage * img_0, + const mask_t * msk_0, + const TImage * img_1, + const mask_t * msk_1) +{ + typename TImage::SizeType period_sz = img_1->GetLargestPossibleRegion().GetSize(); + + // feed the two neighborhoods into the FFT translation estimator: + translate_transform_t::Pointer translate; + double metric = match_one_pair(log, + + // best translation transform: + translate, + + // fixed image small neighborhood: + img_0, + msk_0, + + // moving image small neighborhood: + img_1, + msk_1, + + period_sz, + + 0.25, // overlap min + 1.0, // overlap max + 0.5, // low pass filter r + 0.1, // low pass filter s + 10, // number of peaks + true); // consider the no-displacement case + +#ifdef DEBUG_REFINE_ONE_POINT + static const the_text_t fn_save("/tmp/refine_one_point_fft-"); + the_text_t suffix = the_text_t::number(COUNTER, 3, '0'); + + save_composite(fn_save + suffix + "-a.png", img_0, img_1, identity_transform_t::New(), true); +#endif // DEBUG_REFINE_ONE_POINT + +#if defined(DEBUG_REFINE_ONE_POINT) || defined(DEBUG_ESTIMATE_DISPLACEMENT) + COUNTER++; +#endif + + if (metric == std::numeric_limits::max()) + { + return false; + } + +#ifdef DEBUG_REFINE_ONE_POINT + save_composite(fn_save + suffix + "-b.png", img_0, img_1, translate.GetPointer(), true); +#endif // DEBUG_REFINE_ONE_POINT + + shift = -translate->GetOffset(); + return true; +} + + +//---------------------------------------------------------------- +// extract +// +// Return the number of pixels in the region in the mask: +// +template +unsigned int +extract(const typename TImage::PointType & origin, + + const TImage * tile, + const TMask * mask, + + TImage * tile_region, + TMask * mask_region, + + const typename TImage::PixelType & bg = itk::NumericTraits::Zero) +{ + tile_region->SetOrigin(origin); + mask_region->SetOrigin(origin); + + // setup the image interpolator: + typedef itk::LinearInterpolateImageFunction interpolator_t; + typename interpolator_t::Pointer interpolator = interpolator_t::New(); + interpolator->SetInputImage(tile); + + // temporary: + typename TImage::PointType tile_pt; + typename TImage::PixelType tile_val; + typename TMask::IndexType mask_ix; + typename TMask::PixelType mask_val; + + static const typename TMask::PixelType mask_min = itk::NumericTraits::Zero; + + static const typename TMask::PixelType mask_max = itk::NumericTraits::One; + + unsigned int num_pixels = 0; + + typedef itk::ImageRegionIteratorWithIndex itex_t; + itex_t itex(tile_region, tile_region->GetLargestPossibleRegion()); + for (itex.GoToBegin(); !itex.IsAtEnd(); ++itex) + { + tile_val = bg; + mask_val = mask_min; + + tile_region->TransformIndexToPhysicalPoint(itex.GetIndex(), tile_pt); + if (interpolator->IsInsideBuffer(tile_pt)) + { + mask_val = mask_max; + if (mask != NULL) + { + mask->TransformPhysicalPointToIndex(tile_pt, mask_ix); + mask_val = mask->GetPixel(mask_ix); + } + + if (mask_val != mask_min) + { + tile_val = (typename TImage::PixelType)(interpolator->Evaluate(tile_pt)); + num_pixels++; + } + } + + itex.Set(tile_val); + mask_region->SetPixel(itex.GetIndex(), mask_val); + } + + return num_pixels; +} + + +//---------------------------------------------------------------- +// refine_one_point_helper +// +template +bool +refine_one_point_helper( // the large images and their masks: + const TImage * tile_0, + const mask_t * mask_0, + const TImage * tile_1, + const mask_t * mask_1, + + + // mosaic space neighborhood center: + const pnt2d_t & center, + pnt2d_t & origin, + + // minimum acceptable neighborhood overlap ratio: + const double & min_overlap, + + // the extracted small neighborhoods and their masks: + TImage * img_0, + mask_t * msk_0, + TImage * img_1, + mask_t * msk_1) +{ + const typename TImage::SizeType & sz = img_0->GetLargestPossibleRegion().GetSize(); + const typename TImage::SpacingType & sp = img_0->GetSpacing(); + + origin[0] = center[0] - (double(sz[0]) * sp[0]) / 2; + origin[1] = center[1] - (double(sz[1]) * sp[1]) / 2; + + pnt2d_t corner[] = { origin + vec2d(sz[0] * sp[0], 0), + origin + vec2d(sz[0] * sp[0], sz[1] * sp[1]), + origin + vec2d(0, sz[1] * sp[1]) }; + + // make sure both tiles overlap the region: + typename TImage::IndexType tmp_ix; + if (!(tile_0->TransformPhysicalPointToIndex(origin, tmp_ix) || + tile_0->TransformPhysicalPointToIndex(corner[0], tmp_ix) || + tile_0->TransformPhysicalPointToIndex(corner[1], tmp_ix) || + tile_0->TransformPhysicalPointToIndex(corner[2], tmp_ix)) || + !(tile_1->TransformPhysicalPointToIndex(origin, tmp_ix) || + tile_1->TransformPhysicalPointToIndex(corner[0], tmp_ix) || + tile_1->TransformPhysicalPointToIndex(corner[1], tmp_ix) || + tile_1->TransformPhysicalPointToIndex(corner[2], tmp_ix))) + { + return false; + } + + // keep count of pixel in the neighborhood: + unsigned int area[] = { 0, 0 }; + + area[0] = extract(origin, tile_0, mask_0, img_0, msk_0); + + area[1] = extract(origin, tile_1, mask_1, img_1, msk_1); + + // skip points which don't have enough neighborhood information: + double max_area = double(sz[0] * sz[1]); + double a[] = { double(area[0]) / max_area, double(area[1]) / max_area }; + + if (a[0] < min_overlap || a[1] < min_overlap) + { + return false; + } + + return true; +} + +//---------------------------------------------------------------- +// tiles_intersect +// +template +bool +tiles_intersect( // the large images and their masks: + const TImage * tile_0, + const TImage * tile_1, + + // transforms from mosaic space to tile space: + const base_transform_t * forward_0, + const base_transform_t * forward_1, + + // mosaic space neighborhood center: + const pnt2d_t & origin, + + // neighborhood size and pixel spacing: + const typename TImage::SizeType & sz, + const typename TImage::SpacingType & sp) +{ + pnt2d_t corner[] = { origin, + origin + vec2d(sz[0] * sp[0], 0), + origin + vec2d(sz[0] * sp[0], sz[1] * sp[1]), + origin + vec2d(0, sz[1] * sp[1]) }; + + // temporary: + typename TImage::IndexType tmp_ix; + pnt2d_t tmp_pt; + + // make sure both tiles overlap the region: + bool in[] = { false, false }; + + for (unsigned int i = 0; i < 4 && (!in[0] || !in[1]); i++) + { + tmp_pt = forward_0->TransformPoint(corner[i]); + in[0] = in[0] || tile_0->TransformPhysicalPointToIndex(tmp_pt, tmp_ix); + + tmp_pt = forward_1->TransformPoint(corner[i]); + in[1] = in[1] || tile_1->TransformPhysicalPointToIndex(tmp_pt, tmp_ix); + } + + return in[0] && in[1]; +} + +//---------------------------------------------------------------- +// refine_one_point_helper +// +template +bool +refine_one_point_helper( // the large images and their masks: + const TImage * tile_0, + const mask_t * mask_0, + const TImage * tile_1, + const mask_t * mask_1, + + // transforms from mosaic space to tile space: + const base_transform_t * forward_0, + const base_transform_t * forward_1, + + // mosaic space neighborhood center: + const pnt2d_t & center, + + // minimum acceptable neighborhood overlap ratio: + const double & min_overlap, + + // neighborhood size and pixel spacing: + const typename TImage::SizeType & sz, + const typename TImage::SpacingType & sp, + + // the extracted small neighborhoods and their masks: + TImage * img_0_large, + mask_t * msk_0_large, + TImage * img_0, + mask_t * msk_0, + TImage * img_1, + mask_t * msk_1) +{ + pnt2d_t origin = center; + origin[0] -= (double(sz[0]) * sp[0]) / 2; + origin[1] -= (double(sz[1]) * sp[1]) / 2; + + if (!tiles_intersect(tile_0, tile_1, forward_0, forward_1, origin, sz, sp)) + { + return false; + } + + // setup the image interpolators: + typedef itk::LinearInterpolateImageFunction interpolator_t; + typename interpolator_t::Pointer interpolator[] = { interpolator_t::New(), interpolator_t::New() }; + + interpolator[0]->SetInputImage(tile_0); + interpolator[1]->SetInputImage(tile_1); + + // temporaries: + pnt2d_t mosaic_pt; + pnt2d_t tile_pt; + typename TImage::IndexType index; + + // keep count of pixel in the neighborhood: + unsigned int area[] = { 0, 0 }; + + // extract a neighborhood of the given point from both tiles: + for (unsigned int y = 0; y < sz[1]; y++) + { + mosaic_pt[1] = origin[1] + double(y) * sp[1]; + index[1] = y; + for (unsigned int x = 0; x < sz[0]; x++) + { + mosaic_pt[0] = origin[0] + double(x) * sp[0]; + index[0] = x; + + // fixed image: + tile_pt = forward_0->TransformPoint(mosaic_pt); + if (interpolator[0]->IsInsideBuffer(tile_pt) && pixel_in_mask(mask_0, tile_pt)) + { + double p = interpolator[0]->Evaluate(tile_pt); + img_0->SetPixel(index, (unsigned char)(std::min(255.0, p))); + msk_0->SetPixel(index, 1); + area[0]++; + } + else + { + img_0->SetPixel(index, 0); + msk_0->SetPixel(index, 0); + } + + // moving image: + tile_pt = forward_1->TransformPoint(mosaic_pt); + if (interpolator[1]->IsInsideBuffer(tile_pt) && pixel_in_mask(mask_1, tile_pt)) + { + double p = interpolator[1]->Evaluate(tile_pt); + img_1->SetPixel(index, (unsigned char)(std::min(255.0, p))); + msk_1->SetPixel(index, 1); + area[1]++; + } + else + { + img_1->SetPixel(index, 0); + msk_1->SetPixel(index, 0); + } + } + } + + // skip points which don't have enough neighborhood information: + double max_area = double(sz[0] * sz[1]); + double a[] = { double(area[0]) / max_area, double(area[1]) / max_area }; + + if (a[0] < min_overlap || a[1] < min_overlap) + { + return false; + } + + if (img_0_large != NULL) + { + // extract the larger neighborhood from the fixed tile: + pnt2d_t origin_large(center); + origin_large[0] -= sz[0] * sp[0]; + origin_large[1] -= sz[1] * sp[1]; + + for (unsigned int y = 0; y < sz[1] * 2; y++) + { + mosaic_pt[1] = origin_large[1] + double(y) * sp[1]; + index[1] = y; + for (unsigned int x = 0; x < sz[0] * 2; x++) + { + mosaic_pt[0] = origin_large[0] + double(x) * sp[0]; + index[0] = x; + + // fixed image: + tile_pt = forward_0->TransformPoint(mosaic_pt); + if (interpolator[0]->IsInsideBuffer(tile_pt) && pixel_in_mask(mask_0, tile_pt)) + { + double p = interpolator[0]->Evaluate(tile_pt); + img_0_large->SetPixel(index, (unsigned char)(std::min(255.0, p))); + msk_0_large->SetPixel(index, 1); + } + else + { + img_0_large->SetPixel(index, 0); + msk_0_large->SetPixel(index, 0); + } + } + } + } + + return true; +} + + +//---------------------------------------------------------------- +// refine_one_point_fft +// +template +bool +refine_one_point_fft(the_log_t & log, + vec2d_t & shift, + + // fixed tile, in mosaic space: + const TImage * tile_0, + const mask_t * mask_0, + + // moving tile, in mosaic space: + const TImage * tile_1, + const mask_t * mask_1, + + // mosaic space neighborhood center: + const pnt2d_t & center, + + // minimum acceptable neighborhood overlap ratio: + const double & min_overlap, + + // the extracted small neighborhoods and their masks: + TImage * img_0, + mask_t * msk_0, + TImage * img_1, + mask_t * msk_1) +{ + pnt2d_t origin; + if (!refine_one_point_helper( + tile_0, mask_0, tile_1, mask_1, center, origin, min_overlap, img_0, msk_0, img_1, msk_1)) + { + return false; + } + + return refine_one_point_fft(log, + shift, + pnt2d(0, 0), // origin, + tile_0, + mask_0, + img_0, + msk_0, + img_1, + msk_1); +} + + +//---------------------------------------------------------------- +// refine_one_point_fft +// +template +bool +refine_one_point_fft(the_log_t & log, + vec2d_t & shift, + + // the large images and their masks: + const TImage * tile_0, + const mask_t * mask_0, + const TImage * tile_1, + const mask_t * mask_1, + + // transforms from mosaic space to tile space: + const base_transform_t * forward_0, + const base_transform_t * forward_1, + + // mosaic space neighborhood center: + const pnt2d_t & center, + + // minimum acceptable neighborhood overlap ratio: + const double & min_overlap, + + // neighborhood size and pixel spacing: + const typename TImage::SizeType & sz, + const typename TImage::SpacingType & sp, + + // the extracted larger neighborhood: + TImage * img_0_large, + mask_t * msk_0_large, + + // the extracted small neighborhoods and their masks: + TImage * img_0, + mask_t * msk_0, + TImage * img_1, + mask_t * msk_1) +{ + if (!refine_one_point_helper(tile_0, + mask_0, + tile_1, + mask_1, + forward_0, + forward_1, + center, + min_overlap, + sz, + sp, + img_0_large, + msk_0_large, + img_0, + msk_0, + img_1, + msk_1)) + { + return false; + } + + pnt2d_t origin; + origin[0] = sz[0] * sp[0] / 2; + origin[1] = sz[1] * sp[1] / 2; + return refine_one_point_fft(log, shift, origin, img_0_large, msk_0_large, img_0, msk_0, img_1, msk_1); +} + + +//---------------------------------------------------------------- +// refine_one_point_fft +// +template +bool +refine_one_point_fft(the_log_t & log, + vec2d_t & shift, + + // the large images and their masks: + const TImage * tile_0, + const mask_t * mask_0, + const TImage * tile_1, + const mask_t * mask_1, + + // transforms from mosaic space to tile space: + const base_transform_t * forward_0, + const base_transform_t * forward_1, + + // mosaic space neighborhood center: + const pnt2d_t & center, + + // minimum acceptable neighborhood overlap ratio: + const double & min_overlap, + + // neighborhood size and pixel spacing: + const typename TImage::SizeType & sz, + const typename TImage::SpacingType & sp, + + // the extracted small neighborhoods and their masks: + TImage * img_0, + mask_t * msk_0, + TImage * img_1, + mask_t * msk_1) +{ + pnt2d_t origin(center); + origin[0] -= sz[0] * sp[0] / 2; + origin[1] -= sz[1] * sp[1] / 2; + + if (!refine_one_point_helper(tile_0, + mask_0, + tile_1, + mask_1, + forward_0, + forward_1, + origin, + min_overlap, + sz, + sp, + NULL, + NULL, + img_0, + msk_0, + img_1, + msk_1)) + { + return false; + } + + return refine_one_point_fft(log, shift, img_0, msk_0, img_1, msk_1); +} + + +//---------------------------------------------------------------- +// refine_one_pair +// +template +double +refine_one_pair(the_log_t & log, + + typename TTransform::Pointer & t01, + + const TImage * i0, + const mask_t * m0, + + const TImage * i1, + const mask_t * m1, + + const unsigned int levels, + const unsigned int iterations, + const double & min_step, + const double & max_step) +{ + // typedef itk::MultiResolutionImageRegistrationMethod + typedef itk::ImageRegistrationMethod registration_t; + + // setup the registration object: + typename registration_t::Pointer registration = registration_t::New(); + + // setup the image interpolator: + typedef itk::LinearInterpolateImageFunction interpolator_t; + registration->SetInterpolator(interpolator_t::New()); + + // setup the optimizer: + optimizer_t::Pointer optimizer = optimizer_t::New(); + registration->SetOptimizer(optimizer); + optimizer_observer_t::Pointer observer = optimizer_observer_t::New(); + observer->log_ = &log; + optimizer->SetLog(&log); + optimizer->AddObserver(itk::IterationEvent(), observer); + optimizer->SetMinimize(true); + optimizer->SetNumberOfIterations(iterations); + optimizer->SetMinimumStepLength(min_step); + optimizer->SetMaximumStepLength(max_step); + optimizer->SetGradientMagnitudeTolerance(1e-6); + optimizer->SetRelaxationFactor(5e-1); + optimizer->SetPickUpPaceSteps(5); + optimizer->SetBackTracking(true); + + // FIXME: this is probably unnecessary: + typedef optimizer_t::ScalesType optimizer_scales_t; + optimizer_scales_t parameter_scales(t01->GetNumberOfParameters()); + parameter_scales.Fill(1.0); + + try + { + optimizer->SetScales(parameter_scales); + } + catch (itk::ExceptionObject &) + {} + + // setup the image-to-image metric: + typedef itk::NormalizedCorrelationImageToImageMetric metric_t; + + typename metric_t::Pointer metric = metric_t::New(); + registration->SetMetric(metric); + + registration->SetTransform(t01); + registration->SetInitialTransformParameters(t01->GetParameters()); + + // setup the masks: + typedef itk::ImageMaskSpatialObject<2> mask_so_t; + mask_t::ConstPointer fi_mask = m0; + if (m0 != NULL) + { + mask_so_t::Pointer fi_mask_so = mask_so_t::New(); + fi_mask_so->SetImage(fi_mask); + metric->SetFixedImageMask(fi_mask_so); + } + + mask_t::ConstPointer mi_mask = m1; + if (m1 != NULL) + { + mask_so_t::Pointer mi_mask_so = mask_so_t::New(); + mi_mask_so->SetImage(mi_mask); + metric->SetMovingImageMask(mi_mask_so); + } + + // setup the fixed and moving image: + typename TImage::ConstPointer fi = i0; + typename TImage::ConstPointer mi = i1; + + registration->SetFixedImageRegion(fi->GetLargestPossibleRegion()); + registration->SetFixedImage(fi); + registration->SetMovingImage(mi); + + // setup the multi-resolution image pyramids: + // registration->SetNumberOfLevels(levels); + + // evaluate the metric before the registration: + double metric_before = eval_metric(t01, fi, mi, fi_mask, mi_mask); + assert(metric_before != std::numeric_limits::max()); + + typename TTransform::ParametersType params_before = t01->GetParameters(); + + // perform the registration: + try + { + log << endl; + registration->StartRegistration(); + } + catch (itk::ExceptionObject & exception) + { + log << "image registration threw an exception: " << endl << exception.what() << endl; + } + t01->SetParameters(optimizer->GetBestParams()); + + // evaluate the metric after the registration: + double metric_after = eval_metric(t01, fi, mi, fi_mask, mi_mask); + + typename TTransform::ParametersType params_after = t01->GetParameters(); + + log << "BEFORE: " << metric_before << endl << "AFTER: " << metric_after << endl; + + if (metric_before <= metric_after || metric_after != metric_after) + { + log << "NOTE: minimization failed, ignoring registration results..." << endl; + t01->SetParameters(params_before); + return metric_before; + } + + return metric_after; +} + + +//---------------------------------------------------------------- +// refine_one_point_nofft +// +template +bool +refine_one_point_nofft(the_log_t & log, + + vec2d_t & shift, + const pnt2d_t & origin, + + // the large images and their masks: + const TImage * tile_0, + const mask_t * mask_0, + + // the extracted small neighborhood with masks: + const TImage * img_1, + const mask_t * msk_1) +{ + // feed the two neighborhoods into the optimizer translation estimator: + translate_transform_t::Pointer translate = translate_transform_t::New(); + vec2d_t offset = vec2d(origin[0], origin[1]); + translate->SetOffset(-offset); + + double metric = refine_one_pair(translate, + tile_0, + mask_0, + img_1, + msk_1, + 3, // number of pyramid levels + 100, // number of iterations + 1e-8, // min step + 1e+4); // max step + + shift = -(translate->GetOffset() + offset); + log << "shift: " << shift << endl; + +#ifdef DEBUG_REFINE_ONE_POINT + static const the_text_t fn_save("/tmp/refine_one_point_nofft-"); + the_text_t suffix = the_text_t::number(COUNTER, 3, '0'); + { + translate_transform_t::Pointer t = translate_transform_t::New(); + t->SetOffset(-offset); + save_composite(fn_save + suffix + "-a.png", tile_0, img_1, t.GetPointer(), true); + } +#endif // DEBUG_REFINE_ONE_POINT + +#if defined(DEBUG_REFINE_ONE_POINT) || defined(DEBUG_ESTIMATE_DISPLACEMENT) + COUNTER++; +#endif + + if (metric == std::numeric_limits::max()) + { + return false; + } + +#ifdef DEBUG_REFINE_ONE_POINT + save_composite(fn_save + suffix + "-b.png", tile_0, img_1, translate.GetPointer(), true); +#endif // DEBUG_REFINE_ONE_POINT + + return true; +} + + +//---------------------------------------------------------------- +// refine_one_point_nofft +// +template +bool +refine_one_point_nofft(vec2d_t & shift, + const pnt2d_t & center, + const pnt2d_t & origin, + const double & min_overlap, + + // the large images and their masks: + const TImage * tile_0, + const mask_t * mask_0, + const TImage * tile_1, + const mask_t * mask_1, + + // transform that maps from the space of + // tile 0 to the space of tile 1: + const base_transform_t * t_01, + + // the extracted small neighborhoods and their masks: + TImage * img_0, + mask_t * msk_0, + TImage * img_1, + mask_t * msk_1, + + // neighborhood size and pixel spacing: + const typename TImage::SizeType & sz, + const typename TImage::SpacingType & sp) +{ + if (!refine_one_point_helper( + center, origin, min_overlap, tile_0, mask_0, tile_1, mask_1, t_01, img_0, msk_0, img_1, msk_1, sz, sp)) + { + return false; + } + + // feed the two neighborhoods into the optimizer translation estimator: + translate_transform_t::Pointer translate = translate_transform_t::New(); + translate->SetIdentity(); + + double metric = refine_one_pair(translate, + img_0, + msk_0, + img_1, + msk_1, + 3, // number of pyramid levels + 100, // number of iterations + 1e-8, // min step + 1e+4); // max step + +#ifdef DEBUG_REFINE_ONE_POINT + static const the_text_t fn_save("/tmp/refine_one_point_nofft-"); + the_text_t suffix = the_text_t::number(COUNTER, 3, '0'); + save_composite( + fn_save + suffix + "-a.png", img_0.GetPointer(), img_1.GetPointer(), identity_transform_t::New(), true); +#endif // DEBUG_REFINE_ONE_POINT + +#if defined(DEBUG_REFINE_ONE_POINT) || defined(DEBUG_ESTIMATE_DISPLACEMENT) + COUNTER++; +#endif + + if (metric == std::numeric_limits::max()) + { + return false; + } + +#ifdef DEBUG_REFINE_ONE_POINT + save_composite(fn_save + suffix + "-b.png", img_0.GetPointer(), img_1.GetPointer(), translate.GetPointer(), true); +#endif // DEBUG_REFINE_ONE_POINT + + shift = -translate->GetOffset(); + return true; +} + +//---------------------------------------------------------------- +// refine_one_point_nofft +// +template +bool +refine_one_point_nofft(vec2d_t & shift, + const pnt2d_t & center, + const double & min_overlap, + const TImage * tile_0, + const mask_t * mask_0, + const TImage * tile_1, + const mask_t * mask_1, + + // transform that maps from the space of + // tile 0 to the space of tile 1: + const base_transform_t * t_01, + + // neighborhood size: + const unsigned int & neighborhood) +{ + typename TImage::SpacingType sp = tile_1->GetSpacing(); + typename TImage::SizeType sz; + sz[0] = neighborhood; + sz[1] = neighborhood; + + pnt2d_t origin(center); + origin[0] -= 0.5 * double(sz[0]) * sp[0]; + origin[1] -= 0.5 * double(sz[1]) * sp[1]; + + typename TImage::Pointer img[] = { make_image(sz), make_image(sz) }; + + mask_t::Pointer msk[] = { make_image(sz), make_image(sz) }; + + img[0]->SetSpacing(sp); + img[1]->SetSpacing(sp); + msk[0]->SetSpacing(sp); + msk[1]->SetSpacing(sp); + + return refine_one_point_nofft( + shift, center, origin, min_overlap, tile_0, mask_0, tile_1, mask_1, t_01, img[0], msk[0], img[1], msk[1], sz, sp); +} + + +#endif // GRID_COMMON_HXX_ diff --git a/include/IRGridTransform.h b/include/IRGridTransform.h new file mode 100644 index 0000000..b4b0099 --- /dev/null +++ b/include/IRGridTransform.h @@ -0,0 +1,272 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: nil -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_grid_transform.hxx +// Author : Pavel A. Koshevoy +// Created : Thu Nov 30 10:45:47 MST 2006 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : A discontinuous transform -- a uniform grid of vertices is +// mapped to an image. At each vertex, in addition to image +// space coordinates, a second set of coordinates is stored. +// This is similar to texture mapped OpenGL triangle meshes, +// where the texture coordinates correspond to the image space +// vertex coordinates. + +#ifndef THE_GRID_TRANSFORM_HXX_ +#define THE_GRID_TRANSFORM_HXX_ + +// local includes: +#include "itkIRCommon.h" + +// system includes: +#include +#include + + +//---------------------------------------------------------------- +// vertex_t +// +class vertex_t +{ +public: + // normalized tile space coordinates, typically [0, 1] x [0, 1]: + pnt2d_t uv_; + + // physical space coordinates: + pnt2d_t xy_; +}; + +//---------------------------------------------------------------- +// triangle_t +// +class triangle_t +{ +public: + triangle_t(); + + // check whether a given xy-point falls within this triangle, + // return corresponding uv-point (not triangle barycentric coordinates): + bool + xy_intersect(const vertex_t * v_arr, const pnt2d_t & xy, pnt2d_t & uv) const; + + // check whether a given uv-point falls within this triangle, + // return corresponding xy-point (not triangle barycentric coordinates): + bool + uv_intersect(const vertex_t * v_arr, const pnt2d_t & uv, pnt2d_t & xy) const; + + // triangle vertex indices, counterclockwise winding: + unsigned int vertex_[3]; + + // precomputed fast barycentric coordinate calculation coefficients: + + // for intersection calculation in xy-space: + double xy_pwb[3]; + double xy_pwc[3]; + + // for intersection calculation in uv-space: + double uv_pwb[3]; + double uv_pwc[3]; +}; + +//---------------------------------------------------------------- +// the_acceleration_grid_t +// +// The bounding grid triangle/point intersection acceleration +// structure used to speed up grid transform and mesh transform: +// +class the_acceleration_grid_t +{ +public: + the_acceleration_grid_t(); + + // find the grid cell containing a given xy-point: + unsigned int + xy_cell(const pnt2d_t & xy) const; + + // find the triangle containing a given xy-point, + // and calculate corresponding uv-point: + unsigned int + xy_triangle(const pnt2d_t & xy, pnt2d_t & uv) const; + + // find the grid cell containing a given uv-point: + unsigned int + uv_cell(const pnt2d_t & uv) const; + + // find the triangle containing a given uv-point, + // and calculate corresponding xy-point: + unsigned int + uv_triangle(const pnt2d_t & uv, pnt2d_t & xy) const; + + // update the vertex xy coordinates and rebuild the grid + void + update(const vec2d_t * xy_shift); + void + shift(const vec2d_t & xy_shift); + + // resize the grid: + void + resize(unsigned int rows, unsigned int cols); + + // rebuild the acceleration grid: + void + rebuild(); + +private: + // helper used to rebuild the grid: + void + update_grid(unsigned int t_idx); + +public: + // the acceleration structure: + std::vector> xy_; + std::vector> uv_; + unsigned int rows_; + unsigned int cols_; + + // the grid bounding box (in xy-space): + pnt2d_t xy_min_; + vec2d_t xy_ext_; + + // the triangle mesh: + std::vector mesh_; + std::vector tri_; +}; + + +//---------------------------------------------------------------- +// the_base_triangle_transform_t +// +class the_base_triangle_transform_t +{ +public: + the_base_triangle_transform_t() {} + + // transform the point: + bool + transform(const pnt2d_t & xy, pnt2d_t & uv) const; + + // inverse transform the point: + bool + transform_inv(const pnt2d_t & uv, pnt2d_t & xy) const; + + // calculate the derivatives of the transforms with respect to + // transform parameters: + bool + jacobian(const pnt2d_t & xy, unsigned int * idx, double * jac) const; + +public: + // tile bounding box: + pnt2d_t tile_min_; + vec2d_t tile_ext_; + + // the acceleration grid (stores triangle vertices, and triangles): + the_acceleration_grid_t grid_; +}; + + +//---------------------------------------------------------------- +// the_grid_transform_t +// +class the_grid_transform_t : public the_base_triangle_transform_t +{ +public: + the_grid_transform_t(); + + // check to see whether the transform has already been setup: + bool + is_ready() const; + + // vertex accessors: + inline const vertex_t & + vertex(size_t row, size_t col) const + { + return grid_.mesh_[row * (cols_ + 1) + col]; + } + + inline vertex_t & + vertex(size_t row, size_t col) + { + return grid_.mesh_[row * (cols_ + 1) + col]; + } + + // inverse transform the point: + bool + transform_inv(const pnt2d_t & uv, pnt2d_t & xy) const; + + // setup the transform: + void + setup(unsigned int rows, + unsigned int cols, + const pnt2d_t & tile_min, + const pnt2d_t & tile_max, + const std::vector & xy); + +private: + // helper used to setup the triangle mesh: + void + setup_mesh(); + +public: + // number of rows and columns of quads in the mesh + // (each quad is made up of 2 triangles): + size_t rows_; + size_t cols_; +}; + + +//---------------------------------------------------------------- +// the_mesh_transform_t +// +class the_mesh_transform_t : public the_base_triangle_transform_t +{ +public: + // check to see whether the transform has already been setup: + bool + is_ready() const; + + // setup the transform: + bool + setup(const pnt2d_t & tile_min, + const pnt2d_t & tile_max, + const std::vector & uv, + const std::vector & xy, + unsigned int accel_grid_rows = 16, + unsigned int accel_grid_cols = 16); + + // insert a point into the mesh, and re-triangulate + // using Delaunay triangulation: + bool + insert_point(const pnt2d_t & uv, const pnt2d_t & xy, const bool delay_setup = false); + + // insert a point into the mesh (xy-point is extrapolated), + // and re-triangulate using Delaunay triangulation: + bool + insert_point(const pnt2d_t & uv); + +private: + // helper used to setup the triangle mesh: + bool + setup_mesh(); +}; + + +#endif // THE_GRID_TRANSFORM_HXX_ diff --git a/include/IRLog.h b/include/IRLog.h new file mode 100644 index 0000000..7036743 --- /dev/null +++ b/include/IRLog.h @@ -0,0 +1,169 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_log.hxx +// Author : Pavel A. Koshevoy +// Created : Fri Mar 23 10:34:12 MDT 2007 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : A text log object -- behaves almost like a std::ostream. + +#ifndef THE_LOG_HXX_ +#define THE_LOG_HXX_ + +// system includes: +#include +#include +#include +#include + +// local includes: +#include "IRMutexInterface.h" +#include "itkIRUtils.h" + + +//---------------------------------------------------------------- +// the_log_t +// +class the_log_t +{ +protected: + void log_no_lock(std::ostream & (*f)(std::ostream &)); + +public: + the_log_t(); + virtual ~the_log_t(); + + virtual the_log_t & + operator << (std::ostream & (*f)(std::ostream &)); + + template + the_log_t & + operator << (const data_t & data) + { + the_lock_t lock(mutex_); + line_ << data; + return *this; + } + + std::streamsize precision(); + std::streamsize precision(std::streamsize n); + + std::ios::fmtflags flags() const; + std::ios::fmtflags flags(std::ios::fmtflags fmt); + + void setf(std::ios::fmtflags fmt); + void setf(std::ios::fmtflags fmt, std::ios::fmtflags msk); + void unsetf(std::ios::fmtflags fmt); + + void copyfmt(std::ostream & ostm); + + std::ostringstream line_; + mutable the_mutex_interface_t * mutex_; +}; + + +//---------------------------------------------------------------- +// the_null_log_t +// +class the_null_log_t : public the_log_t +{ +public: + // virtual: + the_log_t & operator << (std::ostream & (*)(std::ostream &)) + { return *this; } + + template + the_log_t & operator << (const data_t &) + { return *this; } +}; + +//---------------------------------------------------------------- +// the_stream_log_t +// +class the_stream_log_t : public the_log_t +{ +public: + the_stream_log_t(std::ostream & ostm): + ostm_(ostm) + {} + + // virtual: + the_log_t & operator << (std::ostream & (*f)(std::ostream &)) + { + the_lock_t lock(the_log_t::mutex_); + the_log_t::log_no_lock(f); + ostm_ << the_log_t::line_.str(); + the_log_t::line_.str(""); + return *this; + } + + template + the_log_t & operator << (const data_t & data) + { return the_log_t::operator << (data); } + + std::ostream & ostm_; +}; + + +//---------------------------------------------------------------- +// the_text_log_t +// +class the_text_log_t : public the_log_t +{ +public: + // virtual: + the_log_t & operator << (std::ostream & (*f)(std::ostream &)) + { + the_lock_t lock(the_log_t::mutex_); + the_log_t::log_no_lock(f); + text_ += the_log_t::line_.str(); + the_log_t::line_.str(""); + return *this; + } + + template + the_log_t & operator << (const data_t & data) + { return the_log_t::operator << (data); } + + inline std::string text() + { return text_; } + + std::string text_; +}; + +//---------------------------------------------------------------- +// null_log +// +extern the_null_log_t * null_log(); + +//---------------------------------------------------------------- +// cerr_log +// +extern the_stream_log_t * cerr_log(); + +//---------------------------------------------------------------- +// cout_log +// +extern the_stream_log_t * cout_log(); + + +#endif // THE_LOG_HXX_ diff --git a/include/IRMosaicRefinementCommon.h b/include/IRMosaicRefinementCommon.h new file mode 100644 index 0000000..bd2de41 --- /dev/null +++ b/include/IRMosaicRefinementCommon.h @@ -0,0 +1,1719 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : mosaic_refinement_common.hxx +// Author : Pavel A. Koshevoy, Joel Spaltenstein +// Created : Mon Mar 26 10:34:39 MDT 2007 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : Helper functions for automatic mosaic refinement. + +#ifndef MOSAIC_REFINEMENT_COMMON_HXX_ +#define MOSAIC_REFINEMENT_COMMON_HXX_ + +// the includes: +#include +#include +#include +#include +#include +#include + +#include "itkImageDuplicator.h" + +// system includes: +#include +#include +#include + +#ifndef WIN32 +# include +#endif + + +//---------------------------------------------------------------- +// regularize_displacements +// +extern void +regularize_displacements( // computed displacement vectors of the moving image + // grid transform control points, in mosaic space: + std::vector & xy_shift, + std::vector & mass, + + image_t::Pointer & dx, + image_t::Pointer & dy, + image_t::Pointer & db, + + // median filter radius: + const unsigned int & median_radius); + + +//---------------------------------------------------------------- +// refine_mosaic +// +template +void +refine_mosaic(the_log_t & log, + + // all the tiles in the mosaic, and their masks: + array2d(typename TImage::Pointer) & pyramid, + std::vector & mask, + std::vector & transform, + + unsigned int iterations_per_level) +{ + typedef itk::LinearInterpolateImageFunction interpolator_t; + + unsigned int pyramid_levels = pyramid.size(); + if (pyramid_levels == 0) + return; + + std::vector & image = pyramid[pyramid_levels - 1]; + unsigned int num_images = image.size(); + if (num_images == 0) + return; + + log << "iterations per level: " << iterations_per_level << endl + << "transform type: " << transform[0]->GetTransformTypeAsString() << endl; + + // try global refinement of the mosaic: + typedef itk::ImageMosaicVarianceMetric mosaic_metric_t; + + typename mosaic_metric_t::Pointer mosaic_metric = mosaic_metric_t::New(); + mosaic_metric->image_.resize(num_images); + mosaic_metric->mask_.resize(num_images); + mosaic_metric->transform_.resize(num_images); + for (unsigned int i = 0; i < num_images; i++) + { + mosaic_metric->image_[i] = image[i]; + mosaic_metric->mask_[i] = mask[i]; + mosaic_metric->transform_[i] = transform[i]; + } + + // FIXME: ITK doesn't have an API for this: + std::vector param_shared(transform[0]->GetNumberOfParameters(), false); + std::vector param_active(transform[0]->GetNumberOfParameters(), true); + + // setup the shared parameters mask: + mosaic_metric->setup_param_map(param_shared, param_active); + mosaic_metric->Initialize(); + + // setup the optimizer scales: + typename mosaic_metric_t::params_t parameter_scales = mosaic_metric->GetTransformParameters(); + parameter_scales.Fill(1.0); + + for (unsigned int level = 0; level < pyramid_levels && iterations_per_level > 0; level++) + { + for (unsigned int i = 0; i < num_images; i++) + { + mosaic_metric->image_[i] = pyramid[level][i]; + } + + typename mosaic_metric_t::measure_t metric_before = + mosaic_metric->GetValue(mosaic_metric->GetTransformParameters()); + + // run several iterations of the optimizer: + for (unsigned int k = 0; k < 3; k++) + { + typename mosaic_metric_t::params_t params_before = mosaic_metric->GetTransformParameters(); + + typename mosaic_metric_t::measure_t metric_after = + std::numeric_limits::max(); + + // use global refinement: + optimizer_t::Pointer optimizer = optimizer_t::New(); + typename optimizer_observer_t::Pointer observer = optimizer_observer_t::New(); + observer->log_ = &log; + optimizer->AddObserver(itk::IterationEvent(), observer); + optimizer->SetLog(&log); + optimizer->SetMinimize(true); + optimizer->SetNumberOfIterations(iterations_per_level); + optimizer->SetMinimumStepLength(1e-12); // min_step + optimizer->SetMaximumStepLength(1e-5); // max_step + optimizer->SetGradientMagnitudeTolerance(1e-6); + optimizer->SetRelaxationFactor(5e-1); + optimizer->SetCostFunction(mosaic_metric); + optimizer->SetInitialPosition(params_before); + optimizer->SetScales(parameter_scales); + optimizer->SetPickUpPaceSteps(5); + optimizer->SetBackTracking(true); + + // refine the mosaic: + try + { + log << "\n" << level << '.' << k << ": refining distortion transforms" << endl; + + optimizer->StartOptimization(); + } + catch (itk::ExceptionObject & exception) + { + // oops: + log << "optimizer threw an exception:" << endl << exception.what() << endl; + } + + mosaic_metric->SetTransformParameters(optimizer->GetBestParams()); + metric_after = optimizer->GetBestValue(); + + typename mosaic_metric_t::params_t params_after = mosaic_metric->GetTransformParameters(); + + log << "before: METRIC = " << metric_before << ", PARAMS = " << params_before << endl + << "after: METRIC = " << metric_after << ", PARAMS = " << params_after << endl; + + // quantify the improvement: + double improvement = 1.0 - metric_after / metric_before; + bool failed_to_improve = (metric_after - metric_before) >= 0.0; + bool negligible_improvement = !failed_to_improve && (improvement < 1e-3); + + if (!failed_to_improve) + { + log << "IMPROVEMENT: " << setw(3) << int(100.0 * improvement) << "%" << endl; + } + + if (failed_to_improve) + { + log << "NOTE: minimization failed, ignoring registration results..." << endl; + + // previous transform was better: + mosaic_metric->SetTransformParameters(params_before); + break; + } + else if (negligible_improvement) + { + log << "NOTE: improvement is negligible..." << endl; + break; + } + + // avoid recalculating the same metric: + metric_before = metric_after; + } + } +} + + +//---------------------------------------------------------------- +// calc_displacements +// +// This is the original version used for single-threaded execution +// +template +void +calc_displacements(the_log_t & log, + + // computed displacement vectors of the moving image + // grid transform control points, in mosaic space: + std::vector & xy_shift, + std::vector & mass, + + // a flag indicating whether the tiles + // have already been transformed into mosaic space: + bool tiles_already_warped, + + // fixed: + const TImage * tile_0, + const TMask * mask_0, + const base_transform_t * forward_0, + + // moving: + const TImage * tile_1, + const TMask * mask_1, + const itk::GridTransform * forward_1, + + // neighborhood size: + const unsigned int & neighborhood, + + // minimum acceptable neighborhood overlap ratio: + const double & min_overlap, + + // median filter radius: + const unsigned int & median_radius) +{ + // shortcuts: + const the_grid_transform_t & gt = forward_1->transform_; + unsigned int mesh_cols = gt.cols_ + 1; + unsigned int mesh_rows = gt.rows_ + 1; + unsigned mesh_size = gt.grid_.mesh_.size(); + xy_shift.assign(mesh_size, vec2d(0, 0)); + + // make sure both tiles have the same pixel spacing: + typename TImage::SpacingType sp = tile_1->GetSpacing(); + if (sp != tile_0->GetSpacing()) + return; + + // setup the local neighborhood: + typename TImage::SizeType sz; + sz[0] = neighborhood; + sz[1] = neighborhood; + + typename TImage::Pointer img[] = { make_image(sp, sz), make_image(sp, sz) }; + + typename TMask::Pointer msk[] = { make_image(sp, sz), make_image(sp, sz) }; + + // for each interpolation point, do a local neighborhood fft matching, + // and use the resulting displacement vector to adjust the mesh: + image_t::Pointer dx = make_image(mesh_cols, mesh_rows, 1.0, 0.0); + + image_t::Pointer dy = make_image(mesh_cols, mesh_rows, 1.0, 0.0); + + image_t::Pointer db = make_image(mesh_cols, mesh_rows, 1.0, 0.0); + + typename TImage::Pointer img_large; + typename TMask::Pointer msk_large; + + if (!tiles_already_warped) + { + typename TImage::SizeType sz_large(sz); + sz_large[0] *= 2; + sz_large[1] *= 2; + img_large = make_image(sp, sz_large); + msk_large = make_image(sp, sz_large); + } + + log << "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -" << endl; +#pragma omp parallel for + for (int i = 0; i < (int)mesh_size; i++) + { + // shortcut: + const vertex_t & vertex = gt.grid_.mesh_[i]; + + // find the mosaic space coordinates of this vertex: + pnt2d_t center; + gt.transform_inv(vertex.uv_, center); + + // extract a neighborhood of the vertex from both tiles: + image_t::IndexType index; + index[0] = i % mesh_cols; + index[1] = i / mesh_cols; + dx->SetPixel(index, 0); + dy->SetPixel(index, 0); + db->SetPixel(index, 0); + + // feed the two neighborhoods into the FFT translation estimator: + vec2d_t shift(vec2d(0, 0)); + bool ok = tiles_already_warped ? + + refine_one_point_fft(log, + shift, + + tile_0, + mask_0, + + tile_1, + mask_1, + + center, + min_overlap, + + img[0].GetPointer(), + msk[0].GetPointer(), + + img[1].GetPointer(), + msk[1].GetPointer()) + : + + refine_one_point_fft(log, + shift, + + tile_0, + mask_0, + + tile_1, + mask_1, + + forward_0, + forward_1, + + center, + min_overlap, + + sz, + sp, + + img_large.GetPointer(), + msk_large.GetPointer(), + + img[0].GetPointer(), + msk[0].GetPointer(), + + img[1].GetPointer(), + msk[1].GetPointer()); + if (!ok) + { + continue; + } + + log << i << ". shift: " << shift << endl; + dx->SetPixel(index, shift[0]); + dy->SetPixel(index, shift[1]); + db->SetPixel(index, 1); + } + + // regularize the displacement vectors here: + regularize_displacements(xy_shift, mass, dx, dy, db, median_radius); +} + + +//---------------------------------------------------------------- +// calc_displacements_t +// +// +template +class calc_displacements_t : public the_transaction_t +{ +public: + calc_displacements_t( // a flag indicating whether the tiles + // have already been transformed into mosaic space: + bool tiles_already_warped, + + // fixed: + const TImage * tile_0, + const TMask * mask_0, + const base_transform_t * forward_0, + + // moving: + const TImage * tile_1, + const TMask * mask_1, + const itk::GridTransform * forward_1, + + // neighborhood size: + const unsigned int & neighborhood_size, + + // minimum acceptable neighborhood overlap ratio: + const double & min_overlap, + + // mesh node displacements: + image_t::Pointer dx, + image_t::Pointer dy, + + // mesh node displacement weights: + image_t::Pointer db, + + // mesh node index: + const std::list & index, + + // mesh node mosaic space coordinates: + const std::list & center) + : tiles_already_warped_(tiles_already_warped) + , + + tile_0_(tile_0) + , mask_0_(mask_0) + , forward_0_(forward_0) + , + + tile_1_(tile_1) + , mask_1_(mask_1) + , forward_1_(forward_1) + , + + min_overlap_(min_overlap) + , + + dx_(dx) + , dy_(dy) + , db_(db) + , + + index_(index.begin(), index.end()) + , center_(center.begin(), center.end()) + { + // make sure both tiles have the same pixel spacing: + sp_ = tile_1->GetSpacing(); + bool ok = (sp_ == tile_0->GetSpacing()); + if (!ok) + { + assert(false); + return; + } + + // setup the local neighborhood: + sz_[0] = neighborhood_size; + sz_[1] = neighborhood_size; + } + + // virtual: + void + execute(the_thread_interface_t * thread) + { + WRAP(the_terminator_t terminator("calc_displacements_t")); + + typename TImage::Pointer img[] = { make_image(sp_, sz_), make_image(sp_, sz_) }; + + typename TMask::Pointer msk[] = { make_image(sp_, sz_), make_image(sp_, sz_) }; + + typename TImage::Pointer img_large; + typename TMask::Pointer msk_large; + + if (!tiles_already_warped_) + { + typename TImage::SizeType sz_large(sz_); + sz_large[0] *= 2; + sz_large[1] *= 2; + img_large = make_image(sp_, sz_large); + msk_large = make_image(sp_, sz_large); + } + + std::size_t num_nodes = center_.size(); + for (std::size_t i = 0; i < num_nodes; i++) + { + // shortcuts: + const image_t::IndexType & index = index_[i]; + const pnt2d_t & center = center_[i]; + + // feed the two neighborhoods into the FFT translation estimator: + vec2d_t shift(vec2d(0, 0)); + + bool ok = tiles_already_warped_ ? + // if the tiles are already warped we don't + // need the transforms + refine_one_point_fft(*null_log(), + shift, + + tile_0_, + mask_0_, + + tile_1_, + mask_1_, + + center, + min_overlap_, + + img[0].GetPointer(), + msk[0].GetPointer(), + + img[1].GetPointer(), + msk[1].GetPointer()) + : + + refine_one_point_fft(*null_log(), + shift, + + tile_0_, + mask_0_, + + tile_1_, + mask_1_, + + forward_0_, + forward_1_, + + center, + min_overlap_, + + sz_, + sp_, + + img_large.GetPointer(), + msk_large.GetPointer(), + + img[0].GetPointer(), + msk[0].GetPointer(), + + img[1].GetPointer(), + msk[1].GetPointer()); + if (ok) + { + dx_->SetPixel(index, shift[0]); + dy_->SetPixel(index, shift[1]); + db_->SetPixel(index, 1); + } + } + } + + // a flag indicating whether the tiles + // have already been transformed into mosaic space: + bool tiles_already_warped_; + + // fixed: + const TImage * tile_0_; + const TMask * mask_0_; + const base_transform_t * forward_0_; + + // moving: + const TImage * tile_1_; + const TMask * mask_1_; + const itk::GridTransform * forward_1_; + + // moving tile spacing (must be the same as fixed tile spacing): + typename TImage::SpacingType sp_; + typename TImage::SizeType sz_; + + // minimum acceptable neighborhood overlap ratio: + const double min_overlap_; + + // mesh node displacements: + image_t::Pointer dx_; + image_t::Pointer dy_; + + // mesh node displacement weights: + image_t::Pointer db_; + + // mesh node index: + std::vector index_; + + // mesh node mosaic space coordinates: + std::vector center_; +}; + + +//---------------------------------------------------------------- +// calc_displacements_mt +// +// calculate transform mesh displacement vectors multi-threaded +// +template +void +calc_displacements_mt(unsigned int num_threads, + + the_log_t & log, + + // computed displacement vectors of the moving image + // grid transform control points, in mosaic space: + std::vector & xy_shift, + std::vector & mass, + + // a flag indicating whether the tiles + // have already been transformed into mosaic space: + bool tiles_already_warped, + + // fixed: + typename TImage::ConstPointer tile_0, + typename TMask::ConstPointer mask_0, + typename base_transform_t::ConstPointer forward_0, + + // moving: + typename TImage::ConstPointer tile_1, + typename TMask::ConstPointer mask_1, + typename itk::GridTransform::ConstPointer & forward_1, + + // neighborhood size: + const unsigned int & neighborhood_size, + + // minimum acceptable neighborhood overlap ratio: + const double & min_overlap, + + // median filter radius: + const unsigned int & median_radius) +{ + // make sure both tiles have the same pixel spacing: + if (tile_1->GetSpacing() != tile_0->GetSpacing()) + return; + + // shortcuts: + const the_grid_transform_t & gt = forward_1->transform_; + unsigned int mesh_cols = gt.cols_ + 1; + unsigned int mesh_rows = gt.rows_ + 1; + unsigned mesh_size = gt.grid_.mesh_.size(); + xy_shift.assign(mesh_size, vec2d(0, 0)); + + // for each interpolation point, do a local neighborhood fft matching, + // and use the resulting displacement vector to adjust the mesh: + image_t::Pointer dx = make_image(mesh_cols, mesh_rows, 1.0, 0.0); + + image_t::Pointer dy = make_image(mesh_cols, mesh_rows, 1.0, 0.0); + + image_t::Pointer db = make_image(mesh_cols, mesh_rows, 1.0, 0.0); + + the_thread_pool_t thread_pool(num_threads); + thread_pool.set_idle_sleep_duration(50); // 50 usec + + // split nodes between threads: + std::vector> node_index_list(num_threads); + std::vector> node_center_list(num_threads); + +#pragma omp parallel for + for (int i = 0; i < mesh_size; i++) + { + // shortcuts: + const vertex_t & vertex = gt.grid_.mesh_[i]; + const unsigned int which_thread = i % num_threads; + + // find the mosaic space coordinates of this vertex: + pnt2d_t center; + gt.transform_inv(vertex.uv_, center); + node_center_list[which_thread].push_back(center); + + // extract a neighborhood of the vertex from both tiles: + image_t::IndexType index; + index[0] = i % mesh_cols; + index[1] = i / mesh_cols; + dx->SetPixel(index, 0); + dy->SetPixel(index, 0); + db->SetPixel(index, 0); + + node_index_list[which_thread].push_back(index); + } + + // setup a transaction for each thread: + for (unsigned int i = 0; i < num_threads; i++) + { + calc_displacements_t * t = new calc_displacements_t(tiles_already_warped, + + tile_0, + mask_0, + forward_0, + + tile_1, + mask_1, + forward_1, + + neighborhood_size, + min_overlap, + + dx, + dy, + db, + + node_index_list[i], + node_center_list[i]); + + thread_pool.push_back(t); + } + + // run the transactions: + thread_pool.pre_distribute_work(); + suspend_itk_multithreading_t suspend_itk_mt; + thread_pool.start(); + thread_pool.wait(); + + // regularize the displacement vectors here: + regularize_displacements(xy_shift, mass, dx, dy, db, median_radius); +} + + +//---------------------------------------------------------------- +// intermediate_result_t +// +class intermediate_result_t +{ +public: + intermediate_result_t() {} + + intermediate_result_t(const intermediate_result_t & r) { *this = r; } + + intermediate_result_t(unsigned int num_neighbors, unsigned int mesh_rows, unsigned int mesh_cols) + : dx_(num_neighbors) + , dy_(num_neighbors) + , db_(num_neighbors) + { + for (unsigned int i = 0; i < num_neighbors; i++) + { + dx_[i] = make_image(mesh_cols, mesh_rows, 1.0, 0.0); + + dy_[i] = make_image(mesh_cols, mesh_rows, 1.0, 0.0); + + db_[i] = make_image(mesh_cols, mesh_rows, 1.0, 0.0); + } + } + + intermediate_result_t & + operator=(const intermediate_result_t & r) + { + if (this != &r) + { + dx_.resize(r.dx_.size()); + for (unsigned int i = 0; i < dx_.size(); i++) + { + dx_[i] = cast(r.dx_[i]); + } + + dy_.resize(r.dy_.size()); + for (unsigned int i = 0; i < dy_.size(); i++) + { + dy_[i] = cast(r.dy_[i]); + } + + db_.resize(r.db_.size()); + for (unsigned int i = 0; i < db_.size(); i++) + { + db_[i] = cast(r.db_[i]); + } + } + + return *this; + } + + std::vector dx_; + std::vector dy_; + std::vector db_; +}; + +//---------------------------------------------------------------- +// calc_intermediate_results_t +// +template +class calc_intermediate_results_t : public the_transaction_t +{ +public: + typedef typename img_ptr_t::ObjectType::Pointer::ObjectType TImage; + typedef typename msk_ptr_t::ObjectType::Pointer::ObjectType TMask; + + calc_intermediate_results_t(the_log_t & log, + unsigned int thread_offset, + unsigned int thread_stride, + std::vector & transform, + const std::vector & warped_tile, + const std::vector & warped_mask, + const std::vector> & neighbors, + const bool & tiles_already_warped, + const unsigned int & neighborhood_size, + const double & minimum_overlap, + const bool & keep_first_tile_fixed, + std::vector & results) + : + + log_(log) + , thread_offset_(thread_offset) + , thread_stride_(thread_stride) + , transform_(transform) + , warped_tile_(warped_tile) + , warped_mask_(warped_mask) + , neighbors_(neighbors) + , tiles_already_warped_(tiles_already_warped) + , neighborhood_(neighborhood_size) + , minimum_overlap_(minimum_overlap) + , keep_first_tile_fixed_(keep_first_tile_fixed) + , results_(results) + {} + + // virtual: + void + execute(the_thread_interface_t * thread) + { + WRAP(the_terminator_t terminator("calc_intermediate_results_t::execute")); + + unsigned int num_tiles = warped_tile_.size(); + unsigned int start = keep_first_tile_fixed_ ? 1 : 0; + + // setup the local neighborhood image buffers: + typename TImage::SpacingType sp = warped_tile_[start]->GetSpacing(); + typename TImage::SizeType sz; + sz[0] = neighborhood_; + sz[1] = neighborhood_; + + typename TImage::Pointer img[] = { make_image(sp, sz), make_image(sp, sz) }; + + typename TMask::Pointer msk[] = { make_image(sp, sz), make_image(sp, sz) }; + + typename TImage::Pointer img_large; + typename TMask::Pointer msk_large; + + if (!tiles_already_warped_) + { + typename TImage::SizeType sz_large(sz); + sz_large[0] *= 2; + sz_large[1] *= 2; + img_large = make_image(sp, sz_large); + msk_large = make_image(sp, sz_large); + } + + for (unsigned int tile_index = start; tile_index < num_tiles; tile_index++) + { + // shortcuts: + const unsigned int num_neighbors = neighbors_[tile_index].size(); + const TImage * tile = warped_tile_[tile_index]; + const TMask * mask = warped_mask_[tile_index]; + const itk::GridTransform * transform = transform_[tile_index]; + + const the_grid_transform_t & gt = transform->transform_; + const unsigned int mesh_cols = gt.cols_ + 1; + const unsigned int mesh_size = gt.grid_.mesh_.size(); + + // shortcuts to intermediate mesh refinement results for this tile: + intermediate_result_t & results = results_[tile_index]; + + for (unsigned int neighbor = 0; neighbor < num_neighbors; neighbor++) + { + WRAP(terminator.terminate_on_request()); + + // shortcuts: + image_t::Pointer & dx = results.dx_[neighbor]; + image_t::Pointer & dy = results.dy_[neighbor]; + image_t::Pointer & db = results.db_[neighbor]; + + const unsigned int neighbor_index = neighbors_[tile_index][neighbor]; + log_ << thread_offset_ << " thread, matching " << tile_index << ":" << neighbor_index << endl; + + // shortcuts: + const TImage * neighbor_tile = warped_tile_[neighbor_index]; + const TMask * neighbor_mask = warped_mask_[neighbor_index]; + const base_transform_t * neighbor_xform = transform_[neighbor_index]; + + for (unsigned int mesh_index = thread_offset_; mesh_index < mesh_size; mesh_index += thread_stride_) + { + // shortcut: + const vertex_t & vertex = gt.grid_.mesh_[mesh_index]; + + // find the mosaic space coordinates of this vertex: + pnt2d_t center; + gt.transform_inv(vertex.uv_, center); + + // extract a neighborhood of the vertex from both tiles: + image_t::IndexType index; + index[0] = mesh_index % mesh_cols; + index[1] = mesh_index / mesh_cols; + dx->SetPixel(index, 0); + dy->SetPixel(index, 0); + db->SetPixel(index, 0); + + // feed the two neighborhoods into the FFT translation estimator: + vec2d_t shift(vec2d(0, 0)); + bool ok = tiles_already_warped_ ? + + refine_one_point_fft(*null_log(), + shift, + + // fixed: + neighbor_tile, + neighbor_mask, + + // moving: + tile, + mask, + + center, + minimum_overlap_, + + img[0].GetPointer(), + msk[0].GetPointer(), + + img[1].GetPointer(), + msk[1].GetPointer()) + : + + refine_one_point_fft(*null_log(), + shift, + + // fixed: + neighbor_tile, + neighbor_mask, + + // moving: + tile, + mask, + + neighbor_xform, + transform, + + center, + minimum_overlap_, + + sz, + sp, + + img_large.GetPointer(), + msk_large.GetPointer(), + + img[0].GetPointer(), + msk[0].GetPointer(), + + img[1].GetPointer(), + msk[1].GetPointer()); + if (!ok) + { + continue; + } + + dx->SetPixel(index, shift[0]); + dy->SetPixel(index, shift[1]); + db->SetPixel(index, 1); + } + } + } + } + + the_log_t & log_; + unsigned int thread_offset_; + unsigned int thread_stride_; + std::vector & transform_; + const std::vector & warped_tile_; + const std::vector & warped_mask_; + const std::vector> & neighbors_; + const bool tiles_already_warped_; + const unsigned int neighborhood_; + const double minimum_overlap_; // neighborhood overlap + const bool keep_first_tile_fixed_; + std::vector & results_; +}; + + +//---------------------------------------------------------------- +// update_tile_mesh_t +// +class update_tile_mesh_t : public the_transaction_t +{ +public: + update_tile_mesh_t(the_log_t & log, + unsigned int tile_index, + bool keep_first_tile_fixed, + unsigned int median_filter_radius, + std::vector & transform, + std::vector & results) + : log_(log) + , tile_index_(tile_index) + , keep_first_tile_fixed_(keep_first_tile_fixed) + , median_filter_radius_(median_filter_radius) + , transform_(transform) + , results_(results) + {} + + // virtual: + void + execute(the_thread_interface_t * thread) + { + WRAP(the_terminator_t terminator("update_tile_mesh_t::execute")); + + log_ << tile_index_ << " mesh update" << endl; + + // shortcuts: + itk::GridTransform * transform = transform_[tile_index_]; + the_grid_transform_t & gt = transform->transform_; + const unsigned int mesh_size = gt.grid_.mesh_.size(); + + // shortcuts to intermediate mesh refinement results for this tile: + intermediate_result_t & results = results_[tile_index_]; + const unsigned int num_neighbors = results.dx_.size(); + + std::vector shift(mesh_size, vec2d(0, 0)); + std::vector mass(mesh_size, 0); + + for (unsigned int neighbor = 0; neighbor < num_neighbors; neighbor++) + { + WRAP(terminator.terminate_on_request()); + + // shortcuts: + image_t::Pointer & dx = results.dx_[neighbor]; + image_t::Pointer & dy = results.dy_[neighbor]; + image_t::Pointer & db = results.db_[neighbor]; + + std::vector neighbor_pull(mesh_size, vec2d(0, 0)); + regularize_displacements(neighbor_pull, mass, dx, dy, db, median_filter_radius_); + + // blend the displacement vectors: + for (unsigned int i = 0; i < mesh_size; i++) + { + shift[i] += neighbor_pull[i]; + } + } + + // FIXME: if (num_neighbors > 1) + if (!keep_first_tile_fixed_) + { + // normalize: + for (unsigned int i = 0; i < mesh_size; i++) + { + double scale = 1 / (1 + mass[i]); + shift[i] *= scale; + } + } + + gt.grid_.update(&(shift[0])); + transform->setup(gt); + } + + the_log_t & log_; + const unsigned int tile_index_; + const bool keep_first_tile_fixed_; + const unsigned int median_filter_radius_; + std::vector & transform_; + std::vector & results_; +}; + + +//---------------------------------------------------------------- +// refine_mosaic +// +// use FFT to refine the grid transforms directly: +// +template +void +refine_mosaic(the_log_t & log, + std::vector & transform, + const std::vector & tile, + const std::vector & mask, + const unsigned int & neighborhood, + const bool & prewarp_tiles = true, + const double & minimum_overlap = 0.25, + const unsigned int & median_radius = 1, + const unsigned int & num_passes = 1, + const bool & keep_first_tile_fixed = false) +{ + typedef itk::GridTransform itk_grid_transform_t; + + // shortcut: + unsigned int num_tiles = tile.size(); + if (num_tiles < 2) + return; + + std::vector ConstTile(tile.size()); + ConstTile.assign(tile.begin(), tile.end()); + + // image space bounding boxes: + std::vector image_min; + std::vector image_max; + calc_image_bboxes(ConstTile, image_min, image_max); + + std::vector ConstTransform(transform.size()); + ConstTransform.assign(transform.begin(), transform.end()); + + // mosaic space bounding boxes: + std::vector mosaic_min; + std::vector mosaic_max; + calc_mosaic_bboxes(ConstTransform, image_min, image_max, mosaic_min, mosaic_max, 16); + + unsigned int start = keep_first_tile_fixed ? 1 : 0; + + // find overlapping neighbors for each tile: + std::vector> neighbors(num_tiles); + for (unsigned int i = start; i < num_tiles; i++) + { + the_aa_bbox_t ibox; + ibox << p3x1_t(mosaic_min[i][0], mosaic_min[i][1], 0) << p3x1_t(mosaic_max[i][0], mosaic_max[i][1], 0); + + for (unsigned int j = 0; j < num_tiles; j++) + { + if (i == j) + continue; + + the_aa_bbox_t jbox; + jbox << p3x1_t(mosaic_min[j][0], mosaic_min[j][1], 0) << p3x1_t(mosaic_max[j][0], mosaic_max[j][1], 0); + + if (!ibox.intersects(jbox)) + continue; + + neighbors[i].push_back(j); + } + } + + typedef itk::ImageDuplicator image_duplicator_t; + typedef itk::ImageDuplicator mask_duplicator_t; + + std::vector warped_tile(num_tiles); + std::vector warped_mask(num_tiles); + + if (keep_first_tile_fixed) + { + typename image_duplicator_t::Pointer image_duplicator = image_duplicator_t::New(); + typename mask_duplicator_t::Pointer mask_duplicator = mask_duplicator_t::New(); + + image_duplicator->SetInputImage(tile[0]); + mask_duplicator->SetInputImage(mask[0]); + + image_duplicator->Update(); + mask_duplicator->Update(); + + warped_tile[0] = image_duplicator->GetOutput(); + warped_mask[0] = mask_duplicator->GetOutput(); + } + + for (unsigned int pass = 0; pass < num_passes; pass++) + { + log << "--------------------------- pass " << pass << " ---------------------------" << endl; + + if (prewarp_tiles) + { + // warp the tiles: + for (unsigned int i = start; i < num_tiles; i++) + { + log << setw(4) << i << ". warping image tile" << endl; + warped_tile[i] = warp((typename image_t::ConstPointer)tile[i], transform[i].GetPointer()); + + if (mask[i].GetPointer() != NULL) + { + log << " warping image tile mask" << endl; + warped_mask[i] = warp((typename mask_t::ConstPointer)mask[i], transform[i].GetPointer()); + } + } + } + + std::vector> shift(num_tiles); + for (unsigned int i = start; i < num_tiles; i++) + { + the_grid_transform_t & gt = transform[i]->transform_; + unsigned int mesh_size = gt.grid_.mesh_.size(); + + std::vector> shift_i(neighbors[i].size()); + std::vector mass(mesh_size, 0); + + for (unsigned int k = 0; k < neighbors[i].size(); k++) + { + unsigned int j = neighbors[i][k]; + log << "matching " << i << ":" << j << endl; + + calc_displacements(*null_log(), + shift_i[k], + mass, + + // tiles-already-warped flag: + prewarp_tiles, + + // fixed: + warped_tile[j], + warped_mask[j], + transform[j].GetPointer(), + + // moving: + warped_tile[i], + warped_mask[i], + transform[i].GetPointer(), + + neighborhood, + minimum_overlap, + median_radius); + } + + // blend the displacement vectors: + shift[i].assign(mesh_size, vec2d(0, 0)); + + for (unsigned int j = 0; j < shift_i.size(); j++) + { + for (unsigned int k = 0; k < mesh_size; k++) + { + shift[i][k] += shift_i[j][k]; + } + } + + if (!keep_first_tile_fixed) + { + // normalize: + for (unsigned int k = 0; k < mesh_size; k++) + { + double scale = 1 / (1 + mass[k]); + shift[i][k] *= scale; + } + } + } + + for (unsigned int i = start; i < num_tiles; i++) + { + the_grid_transform_t & gt = transform[i]->transform_; + gt.grid_.update(&(shift[i][0])); + transform[i]->setup(gt); + } + } +} + + +//---------------------------------------------------------------- +// warp_tile_transaction_t +// +template +class warp_tile_transaction_t : public the_transaction_t +{ +public: + warp_tile_transaction_t(the_log_t & log, + unsigned int tile_index, + itk::GridTransform::Pointer & transform, + const typename image_t::ConstPointer & tile, + const typename mask_t::ConstPointer & mask, + std::vector & warped_tile, + std::vector & warped_mask) + : log_(log) + , tile_index_(tile_index) + , transform_(transform) + , tile_(tile) + , mask_(mask) + , warped_tile_(warped_tile) + , warped_mask_(warped_mask) + {} + + // virtual: + void + execute(the_thread_interface_t * thread) + { + WRAP(the_terminator_t terminator("warp_tile_transaction_t")); + + log_ << setw(4) << tile_index_ << ". warping image tile" << endl; + warped_tile_[tile_index_] = warp(tile_, transform_.GetPointer()); + + if (mask_.GetPointer() != NULL) + { + log_ << setw(4) << tile_index_ << ". warping image tile mask" << endl; + warped_mask_[tile_index_] = warp(mask_, transform_.GetPointer()); + } + } + + the_log_t & log_; + unsigned int tile_index_; + itk::GridTransform::Pointer & transform_; + const typename image_t::ConstPointer & tile_; + const typename mask_t::ConstPointer & mask_; + std::vector & warped_tile_; + std::vector & warped_mask_; +}; + + +//---------------------------------------------------------------- +// refine_one_tile_t +// +template +class refine_one_tile_t : public the_transaction_t +{ +public: + refine_one_tile_t(the_log_t & log, + unsigned int tile_index, + std::vector & transform, + const std::vector & warped_tile, + const std::vector & warped_mask, + const std::vector> & neighbors, + const bool & tiles_already_warped, + const unsigned int & neighborhood_size, + const double & minimum_overlap, // neighbrhood overlap + const unsigned int & median_filter_radius, // for outliers + const bool & keep_first_tile_fixed, + std::vector> & shift) + : + + log_(log) + , tile_index_(tile_index) + , transform_(transform) + , warped_tile_(warped_tile) + , warped_mask_(warped_mask) + , neighbors_(neighbors) + , tiles_already_warped_(tiles_already_warped) + , neighborhood_(neighborhood_size) + , minimum_overlap_(minimum_overlap) + , median_radius_(median_filter_radius) + , keep_first_tile_fixed_(keep_first_tile_fixed) + , shift_(shift) + {} + + // virtual: + void + execute(the_thread_interface_t * thread) + { + WRAP(the_terminator_t terminator("refine_one_tile_t")); + + the_grid_transform_t & gt = transform_[tile_index_]->transform_; + unsigned int mesh_size = gt.grid_.mesh_.size(); + std::vector mass(mesh_size, 0); + + unsigned int num_neighbors = neighbors_[tile_index_].size(); + std::vector> shift_i(num_neighbors); + + for (unsigned int k = 0; k < num_neighbors; k++) + { + unsigned int j = neighbors_[tile_index_][k]; + log_ << "matching " << tile_index_ << ":" << j << endl; + + calc_displacements(*null_log(), + shift_i[k], + mass, + + // tiles-already-warped flag: + tiles_already_warped_, + + // fixed: + warped_tile_[j], + warped_mask_[j], + transform_[j].GetPointer(), + + // moving: + warped_tile_[tile_index_], + warped_mask_[tile_index_], + transform_[tile_index_].GetPointer(), + + neighborhood_, + minimum_overlap_, + median_radius_); + } + + // blend the displacement vectors: + shift_[tile_index_].assign(mesh_size, vec2d(0, 0)); + + for (unsigned int j = 0; j < num_neighbors; j++) + { + for (unsigned int k = 0; k < mesh_size; k++) + { + shift_[tile_index_][k] += shift_i[j][k]; + } + } + + if (!keep_first_tile_fixed_) + { + // normalize: + for (unsigned int k = 0; k < mesh_size; k++) + { + double scale = 1 / (1 + mass[k]); + shift_[tile_index_][k] *= scale; + } + } + } + + the_log_t & log_; + unsigned int tile_index_; + std::vector & transform_; + const std::vector & warped_tile_; + const std::vector & warped_mask_; + const std::vector> & neighbors_; + const bool tiles_already_warped_; + const unsigned int neighborhood_; + const double minimum_overlap_; // neighbrhood overlap + const unsigned int median_radius_; // for outliers + const bool keep_first_tile_fixed_; + std::vector> & shift_; +}; + + +//---------------------------------------------------------------- +// refine_mosaic_mt +// +template +void +refine_mosaic_mt(the_log_t & log, // text output stream + std::vector & transform, + const std::vector & tile, + const std::vector & mask, + const unsigned int & neighborhood_size, + const bool & prewarp_tiles, // FIXME: always true? + const double & minimum_overlap, // neighbrhood overlap + const unsigned int & median_radius, // for outliers + const unsigned int & num_passes, + const bool & keep_first_tile_fixed, // FIXME: stos only? + const double & displacement_threshold, + unsigned int num_threads) // max concurrent threads +{ + if (num_threads == 1) + { + refine_mosaic(log, + transform, + tile, + mask, + neighborhood_size, + prewarp_tiles, + minimum_overlap, + median_radius, + num_passes, + keep_first_tile_fixed); + return; + } + + typedef itk::GridTransform itk_grid_transform_t; + + // shortcut: + unsigned int num_tiles = tile.size(); + if (num_tiles < 2) + return; + + log << "num tiles: " << num_tiles; + // image space bounding boxes: + std::vector ConstTile; + std::vector image_min; + std::vector image_max; + + ConstTile.reserve(tile.size()); + ConstTile.assign(tile.begin(), tile.end()); + + std::vector ConstTransforms; + ConstTransforms.reserve(transform.size()); + + ConstTransforms.assign(transform.begin(), transform.end()); + + calc_image_bboxes(ConstTile, image_min, image_max); + + // mosaic space bounding boxes: + std::vector mosaic_min; + std::vector mosaic_max; + calc_mosaic_bboxes(ConstTransforms, image_min, image_max, mosaic_min, mosaic_max, 16); + + // Relative to the image size. + // double threshold = + // displacement_threshold * std::abs(mosaic_min[0][0] - mosaic_max[0][0]); + + // Relative to a single pixel. + double threshold = displacement_threshold; + + unsigned int start = keep_first_tile_fixed ? 1 : 0; + + // find overlapping neighbors for each tile: + std::vector> neighbors(num_tiles); + for (unsigned int i = start; i < num_tiles; i++) + { + the_aa_bbox_t ibox; + ibox << p3x1_t(mosaic_min[i][0], mosaic_min[i][1], 0) << p3x1_t(mosaic_max[i][0], mosaic_max[i][1], 0); + + for (unsigned int j = 0; j < num_tiles; j++) + { + if (i == j) + continue; + + the_aa_bbox_t jbox; + jbox << p3x1_t(mosaic_min[j][0], mosaic_min[j][1], 0) << p3x1_t(mosaic_max[j][0], mosaic_max[j][1], 0); + + if (!ibox.intersects(jbox)) + continue; + + neighbors[i].push_back(j); + } + } + + std::vector warped_tile(num_tiles); + std::vector warped_mask(num_tiles); + + double last_average = std::numeric_limits::max(); + + typedef itk::ImageDuplicator image_duplicator_t; + typedef itk::ImageDuplicator mask_duplicator_t; + + // Initialize "warped" tiles. + // If warping is actually requested do it on each pass + for (int i = 0; i < num_tiles; i++) + { + typename image_duplicator_t::Pointer image_duplicator = image_duplicator_t::New(); + typename mask_duplicator_t::Pointer mask_duplicator = mask_duplicator_t::New(); + + image_duplicator->SetInputImage(tile[i]); + image_duplicator->Update(); + warped_tile[i] = image_duplicator->GetOutput(); + + if (mask[i].GetPointer() != NULL) + { + mask_duplicator->SetInputImage(mask[i]); + mask_duplicator->Update(); + warped_mask[i] = mask_duplicator->GetOutput(); + } + } + + the_thread_pool_t thread_pool(num_threads); + thread_pool.set_idle_sleep_duration(50); // 50 usec + + for (unsigned int pass = 0; pass < num_passes; pass++) + { + double major_percent = 0.15 + 0.8 * ((double)pass / (double)num_passes); + double next_major = 0.15 + 0.8 * ((double)(pass + 1) / (double)num_passes); + set_major_progress(major_percent); + + log << "--------------------------- pass " << pass << " ---------------------------" << endl; + + if (prewarp_tiles) + { + // warp the tiles -- split into a set of transactions and executed: + + std::list schedule; + for (unsigned int i = start; i < num_tiles; i++) + { + warp_tile_transaction_t * t = new warp_tile_transaction_t( + log, i, transform[i], tile[i], mask[i], warped_tile, warped_mask); + schedule.push_back(t); + } + + thread_pool.push_back(schedule); + thread_pool.pre_distribute_work(); + suspend_itk_multithreading_t suspend_itk_mt; + thread_pool.start(); + thread_pool.wait(); + } + + set_minor_progress(0.2, next_major); + +#if 1 + // calculating displacements: + std::vector> shift(num_tiles); + + // this is the original coarse-scale parallelization: + unsigned int num_tiles_distributed = (num_tiles - start) - (num_tiles - start) % num_threads; + + unsigned int num_tiles_remaining = (num_tiles - start) - num_tiles_distributed; + + std::list schedule; + for (unsigned int i = 0; i < num_tiles_distributed; i++) + { + unsigned int index = start + i; + + typedef refine_one_tile_t tile_transaction_t; + + tile_transaction_t * t = new tile_transaction_t(log, + index, + transform, + warped_tile, + warped_mask, + neighbors, + prewarp_tiles, + neighborhood_size, + minimum_overlap, + median_radius, + keep_first_tile_fixed, + shift); + schedule.push_back(t); + } + + thread_pool.push_back(schedule); + // thread_pool.pre_distribute_work(); + suspend_itk_multithreading_t suspend_itk_mt; + thread_pool.start(); + thread_pool.wait(); + + set_minor_progress(0.9, next_major); + + // this is the broken fine-scale parallelization: + for (unsigned int i = 0; i < num_tiles_remaining; i++) + { + unsigned int index = start + num_tiles_distributed + i; + + the_grid_transform_t & gt = transform[index]->transform_; + unsigned int mesh_size = gt.grid_.mesh_.size(); + + std::vector> shift_i(neighbors[index].size()); + std::vector mass(mesh_size, 0); + + for (unsigned int k = 0; k < neighbors[index].size(); k++) + { + unsigned int j = neighbors[index][k]; + log << "matching " << index << ":" << j << endl; + + calc_displacements(*null_log(), + shift_i[k], + mass, + + // tiles-already-warped flag: + prewarp_tiles, + + // fixed: + (typename image_t::ConstPointer)warped_tile[j], + (typename mask_t::ConstPointer)warped_mask[j], + (base_transform_t::ConstPointer)transform[j].GetPointer(), + + // moving: + (typename image_t::ConstPointer)warped_tile[index], + (typename mask_t::ConstPointer)warped_mask[index], + (itk::GridTransform::ConstPointer)transform[index].GetPointer(), + + neighborhood_size, + minimum_overlap, + median_radius); + } + + // blend the displacement vectors: + shift[index].assign(mesh_size, vec2d(0, 0)); + + for (unsigned int j = 0; j < shift_i.size(); j++) + { + for (unsigned int k = 0; k < mesh_size; k++) + { + shift[index][k] += shift_i[j][k]; + } + } + + if (!keep_first_tile_fixed) + { + // normalize: + for (unsigned int k = 0; k < mesh_size; k++) + { + double scale = 1 / (1 + mass[k]); + shift[index][k] *= scale; + } + } + } + + // once all displacement calculations are + // finished the transform grids can be updated and we + // can move on to the next pass: + + for (unsigned int i = start; i < num_tiles; i++) + { + the_grid_transform_t & gt = transform[i]->transform_; + gt.grid_.update(&(shift[i][0])); + transform[i]->setup(gt); + } + +#else + // this is the "improved" fine scale parallelization: + + // setup intermediate mesh refinement result structures: + std::vector results(num_tiles); + for (unsigned int i = start; i < num_tiles; i++) + { + const unsigned int num_neighbors = neighbors[i].size(); + const the_grid_transform_t & gt = transform[i]->transform_; + const unsigned int mesh_rows = gt.rows_ + 1; + const unsigned int mesh_cols = gt.cols_ + 1; + + intermediate_result_t & result = results[i]; + result = intermediate_result_t(num_neighbors, mesh_rows, mesh_cols); + } + + std::list schedule; + for (unsigned int i = 0; i < num_threads; i++) + { + typedef calc_intermediate_results_t tile_transaction_t; + + tile_transaction_t * t = new tile_transaction_t(log, + i, + num_threads, + transform, + warped_tile, + warped_mask, + neighbors, + prewarp_tiles, + neighborhood_size, + minimum_overlap, + keep_first_tile_fixed, + results); + schedule.push_back(t); + } + + thread_pool.push_back(schedule); + thread_pool.pre_distribute_work(); + suspend_itk_multithreading_t suspend_itk_mt; + thread_pool.start(); + thread_pool.wait(); + + for (unsigned int i = start; i < num_tiles; i++) + { + update_tile_mesh_t * t = new update_tile_mesh_t(log, i, keep_first_tile_fixed, median_radius, transform, results); + schedule.push_back(t); + } + + thread_pool.push_back(schedule); + thread_pool.pre_distribute_work(); + thread_pool.start(); + thread_pool.wait(); +#endif + double worst = 0.0, avg = 0.0, count = 0.0; + for (int i = 0; i < (int)shift.size(); i++) + { + for (int k = 0; k < (int)shift[i].size(); k++) + { + if (std::abs(shift[i][k][0]) > worst) + worst = std::abs(shift[i][k][0]); + if (std::abs(shift[i][k][1]) > worst) + worst = std::abs(shift[i][k][1]); + + avg += std::abs(shift[i][k][0]) + std::abs(shift[i][k][1]); + count += 2; + } + } + avg /= count; + cout << pass << " Average Displacement: " << avg << " Max Displacement: " << worst << endl; + + // If there's an exact cutoff... + if (count > 0) + { + if (avg <= threshold) + break; + else if (avg >= last_average) + break; + last_average = avg; + } + } +} + + +#endif // MOSAIC_REFINEMENT_COMMON_HXX_ diff --git a/include/IRMutex.h b/include/IRMutex.h new file mode 100644 index 0000000..75765a2 --- /dev/null +++ b/include/IRMutex.h @@ -0,0 +1,76 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_boost_mutex.hxx +// Author : Pavel A. Koshevoy +// Created : Sat Oct 25 12:33:43 MDT 2008 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : A thin wrapper for Boost mutex class. + +#ifndef THE_BOOST_MUTEX_HXX_ +#define THE_BOOST_MUTEX_HXX_ + +// local includes: +#include "IRMutexInterface.h" + +// Boost includes: +#include + + +//---------------------------------------------------------------- +// the_boost_mutex_t +// +class the_boost_mutex_t : public the_mutex_interface_t +{ +public: + the_boost_mutex_t(); + + // the destructor is protected on purpose, + // see delete_this for details: + virtual ~the_boost_mutex_t(); + + // In order to avoid memory management problems with shared libraries, + // whoever provides this interface instance (via it's creator), has to + // provide a way to delete the instance as well. This will avoid + // issues with multiple-instances of C runtime libraries being + // used by the app and whatever libraries it links against that + // either use or provide this interface: + virtual void delete_this(); + + // the creation method: + static the_mutex_interface_t * create(); + + // virtual: + void lock(); + + // virtual: + void unlock(); + + // virtual: + bool try_lock(); + +private: + std::mutex mutex_; +}; + + +#endif // THE_BOOST_MUTEX_HXX_ diff --git a/include/IRMutexInterface.h b/include/IRMutexInterface.h new file mode 100644 index 0000000..0f4e7ad --- /dev/null +++ b/include/IRMutexInterface.h @@ -0,0 +1,74 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_mutex_interface.hxx +// Author : Pavel A. Koshevoy +// Created : Sun Feb 18 16:12:00 MST 2007 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : An abstract mutex class interface. + +#ifndef THE_MUTEX_INTERFACE_HXX_ +#define THE_MUTEX_INTERFACE_HXX_ + + +//---------------------------------------------------------------- +// the_mutex_interface_t +// +class the_mutex_interface_t +{ +protected: + // the destructor is protected on purpose, + // see delete_this for details: + virtual ~the_mutex_interface_t(); + +public: + // In order to avoid memory management problems with shared libraries, + // whoever provides this interface instance (via it's creator), has to + // provide a way to delete the instance as well. This will avoid + // issues with multiple-instances of C runtime libraries being + // used by the app and whatever libraries it links against that + // either use or provide this interface: + virtual void delete_this() = 0; + + // mutex controls: + virtual void lock() = 0; + virtual void unlock() = 0; + virtual bool try_lock() = 0; + + //---------------------------------------------------------------- + // creator_t + // + typedef the_mutex_interface_t *(*creator_t)(); + + // specify a thread creation method: + static void set_creator(creator_t creator); + + // create a new instance of a thread: + static the_mutex_interface_t * create(); + +protected: + // an abstract mutex creator: + static creator_t creator_; +}; + + +#endif // THE_MUTEX_INTERFACE_HXX_ diff --git a/include/IROptimizerObserver.h b/include/IROptimizerObserver.h new file mode 100644 index 0000000..5c9737b --- /dev/null +++ b/include/IROptimizerObserver.h @@ -0,0 +1,74 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : optimizer_observer.hxx +// Author : Pavel A. Koshevoy +// Created : Fri Mar 23 12:47:03 MDT 2007 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : An optimizer observer class. + +#ifndef OPTIMIZER_OBSERVER_HXX_ +#define OPTIMIZER_OBSERVER_HXX_ + +// the includes: +#include "IRLog.h" + +// ITK includes: +#include + + +//---------------------------------------------------------------- +// optimizer_observer_t +// +template +class optimizer_observer_t : public itk::Command +{ +public: + typedef optimizer_observer_t Self; + typedef itk::Command Superclass; + typedef itk::SmartPointer Pointer; + + itkNewMacro(Self); + + void Execute(itk::Object *caller, const itk::EventObject & event) + { Execute((const itk::Object *)(caller), event); } + + void Execute(const itk::Object * object, const itk::EventObject & event) + { + if (typeid(event) != typeid(itk::IterationEvent)) return; + + const TOptimizer * optimizer = dynamic_cast(object); + *log_ << (unsigned int)(optimizer->GetCurrentIteration()) << '\t' + << optimizer->GetValue() << '\t' + << optimizer->GetCurrentPosition() << endl; + } + + the_log_t * log_; + +protected: + optimizer_observer_t(): + log_(cerr_log()) + {} +}; + + +#endif // OPTIMIZER_OBSERVER_HXX_ diff --git a/include/IRPath.h b/include/IRPath.h new file mode 100644 index 0000000..816101c --- /dev/null +++ b/include/IRPath.h @@ -0,0 +1,113 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: nil -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : IRPath.h +// Author : Bradley C. Grimm +// Created : 2009/09/15 11:24 AM +// Copyright : (C) 2009 University of Utah +// License : GPLv2 +// Description : Helpful path manipulation tools. + +#ifndef __IR_PATH_HXX__ +#define __IR_PATH_HXX__ + +#if defined(WIN32) + #pragma warning ( disable : 4996 ) +#endif +#include "dirent.h" +#if defined(WIN32) + #pragma warning ( default : 4996 ) +#endif + +#include + +class IRPath +{ +public: + static the_text_t CleanPath(const the_text_t &path) + { + the_text_t directory = path; + CleanSlashes(directory); + if ( !directory.is_empty() && !directory.match_tail("/") ) + directory += the_text_t("/"); + return directory; + } + + static the_text_t DirectoryFromPath(const the_text_t &path) + { + the_text_t directory = path; + CleanSlashes(directory); + + directory = directory.splitAt('/', std::numeric_limits::max())[0]; + if ( !directory.is_empty() && !directory.match_tail("/") ) + return directory + "/"; + else + return directory; + } + + static the_text_t FilenameFromPath(the_text_t &path) + { + CleanSlashes(path); + + the_text_t directory; + if ( path.contains('/') ) + directory = path.splitAt('/', std::numeric_limits::max())[1]; + else + directory = path; + return directory; + } + + static void CleanSlashes(the_text_t &path) + { + if ( path.contains('\\') ) + path.replace('\\','/'); + } + + static std::list DirectoryContents(const the_text_t &path) + { + std::list files; + + DIR *dp; + struct dirent *dirp; + if((dp = opendir(path.text())) == NULL) { + cout << "Error(" << errno << ") opening " << path << endl; + return files; + } + + while ((dirp = readdir(dp)) != NULL) { + files.push_back( dirp->d_name ); + } + closedir(dp); + + // Remove . + files.pop_front(); + + // Remove .. + files.pop_front(); + + return files; + } + +}; + +#endif + + diff --git a/include/IRStdMutex.h b/include/IRStdMutex.h new file mode 100644 index 0000000..8005fa4 --- /dev/null +++ b/include/IRStdMutex.h @@ -0,0 +1,76 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_boost_mutex.hxx +// Author : Pavel A. Koshevoy +// Created : Sat Oct 25 12:33:43 MDT 2008 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : A thin wrapper for Boost mutex class. + +#ifndef THE_BOOST_MUTEX_HXX_ +#define THE_BOOST_MUTEX_HXX_ + +// local includes: +#include "IRMutexInterface.h" + +// Boost includes: +#include + + +//---------------------------------------------------------------- +// the_boost_mutex_t +// +class the_std_mutex_t : public the_mutex_interface_t +{ +public: + the_std_mutex_t(); + + // the destructor is protected on purpose, + // see delete_this for details: + virtual ~the_std_mutex_t(); + + // In order to avoid memory management problems with shared libraries, + // whoever provides this interface instance (via it's creator), has to + // provide a way to delete the instance as well. This will avoid + // issues with multiple-instances of C runtime libraries being + // used by the app and whatever libraries it links against that + // either use or provide this interface: + virtual void delete_this(); + + // the creation method: + static the_mutex_interface_t * create(); + + // virtual: + void lock(); + + // virtual: + void unlock(); + + // virtual: + bool try_lock(); + +private: + std::mutex mutex_; +}; + + +#endif // THE_BOOST_MUTEX_HXX_ diff --git a/include/IRStdThread.h b/include/IRStdThread.h new file mode 100644 index 0000000..36819ed --- /dev/null +++ b/include/IRStdThread.h @@ -0,0 +1,130 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_std_thread.hxx +// Author : Pavel A. Koshevoy +// Created : Sat Oct 25 12:35:09 MDT 2008 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : A thin wrapper for Boost thread class. + +#ifndef the_std_thread_HXX_ +#define the_std_thread_HXX_ + +// std thread includes: +#include +#include + +// local includes: +#include "IRTerminator.h" +#include "IRThreadInterface.h" + +// forward declarations: +class the_mutex_interface_t; +class the_thread_storage_t; + + +//---------------------------------------------------------------- +// the_std_terminators_t +// +class the_std_terminators_t : public the_terminators_t +{ +public: + // virtual: concurrent access controls: + void lock() { mutex_.lock(); } + void unlock() { mutex_.unlock(); } + +private: + mutable std::mutex mutex_; +}; + + +//---------------------------------------------------------------- +// the_std_thread_t +// +// 1. the thread will not take ownership of the transactions. +// 2. the thread will take ownership of the mutex. +// +class the_std_thread_t : public the_thread_interface_t +{ +private: + struct callable_t + { + callable_t(the_std_thread_t * thread): + thread_(thread) + {} + + void operator()() + { + thread_->run(); + } + + private: + the_std_thread_t * thread_; + }; + +public: + the_std_thread_t(); + + // the destructor is protected on purpose, + // see delete_this for details: + virtual ~the_std_thread_t(); + + // In order to avoid memory management problems with shared libraries, + // whoever provides this interface instance (via it's creator), has to + // provide a way to delete the instance as well. This will avoid + // issues with multiple-instances of C runtime libraries being + // used by the app and whatever libraries it links against that + // either use or provide this interface: + virtual void delete_this(); + + // the creation method: + static the_thread_interface_t * create() + { return new the_std_thread_t(); } + + // the thread storage accessor: + static the_thread_storage_t & thread_storage(); + + // virtual: start the thread: + void start(); + + // virtual: + void wait(); + + // virtual: put the thread to sleep: + void take_a_nap(const unsigned long & microseconds); + + // virtual: accessor to the transaction terminators: + the_terminators_t & terminators(); + +protected: + // virtual: + void run(); + + // the boost thread: + std::thread * std_thread_; + + // a list of active terminators for this thread: + the_std_terminators_t terminators_; +}; + + +#endif // the_std_thread_HXX_ diff --git a/include/IRStdThreadStorage.h b/include/IRStdThreadStorage.h new file mode 100644 index 0000000..6c2dd71 --- /dev/null +++ b/include/IRStdThreadStorage.h @@ -0,0 +1,83 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_std_thread_storage.hxx +// Author : Pavel A. Koshevoy +// Created : Sat Oct 25 12:35:47 MDT 2008 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : A thin wrapper for std thread specific storage. + +#ifndef THE_STD_THREAD_STORAGE_HXX_ +#define THE_STD_THREAD_STORAGE_HXX_ + +// local includes: +#include "IRThreadStorage.h" + +// std includes: +#include +#include + + +//---------------------------------------------------------------- +// the_std_thread_storage_t +// +class the_std_thread_storage_t : + // public std::thread_specific_ptr, + public the_thread_storage_t +{ +public: + // virtual: check whether the thread storage has been initialized: + bool is_ready() const + { + // return (std::thread_specific_ptr::get() != NULL); + return true; + } + + // virtual: check whether the thread has been stopped: + bool thread_stopped() const + { + return + this->thread_observer_-> + thread_.stopped(); + } + + // virtual: terminator access: + the_terminators_t & terminators() + { + return + this->thread_observer_-> + thread_.terminators(); + } + + // virtual: + unsigned int thread_id() const + { + return + this->thread_observer_-> + thread_.id(); + } + + std::unique_ptr thread_observer_; +}; + + +#endif // THE_std_THREAD_STORAGE_HXX_ diff --git a/include/IRTerminator.h b/include/IRTerminator.h new file mode 100644 index 0000000..cefb107 --- /dev/null +++ b/include/IRTerminator.h @@ -0,0 +1,130 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_terminator.hxx +// Author : Pavel A. Koshevoy +// Created : Sun Sep 24 18:05:00 MDT 2006 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : a thread terminator convenience class + +#ifndef THE_TERMINATOR_HXX_ +#define THE_TERMINATOR_HXX_ + +#if defined(USE_THE_TERMINATORS) || defined(USE_ITK_TERMINATORS) +#define WRAP(x) +#else +#define WRAP(x) +#endif + +// system includes: +#include +#include + +// local includes: +#include "IRThreadStorage.h" +#include "itkIRUtils.h" + + +//---------------------------------------------------------------- +// the_terminator_t +// +class the_terminator_t +{ +public: + the_terminator_t(const char * id); + virtual ~the_terminator_t(); + + // terminator id accessor: + inline const std::string & id() const + { return id_; } + + // read-only accessor to the termination request flag: + inline const bool & termination_requested() const + { return termination_requested_; } + + // this function may be called periodically from any time consuming + // function -- in case the user has decided to terminate its execution: + inline void terminate_on_request() const + { + // if (termination_requested_ || should_terminate_immediately()) + if (termination_requested_) + { + throw_exception(); + } + } + + // this is a no-op for simple terminators, itk terminators will + // override this to turn on the process AbortGenerateData flag: + virtual void terminate(); + + // this function will throw an exception: + void throw_exception() const; + + // make sure there are no terminators left: + static bool verify_termination(); + +protected: + // consult the thread regarding whether termination has been requested + // for all terminators in the current thread: + static bool should_terminate_immediately(); + + // a list of active terminators per current thread: + static std::list & terminators(); + + // add/remove a terminator to/from the list of active terminators + // in the current thread: + static void add(the_terminator_t * terminator); + static void del(the_terminator_t * terminator); + + // id of this terminator: + std::string id_; + + // flag indicating that termination was requested explicitly + // for this terminator: + bool termination_requested_; +}; + +//---------------------------------------------------------------- +// the_terminators_t +// +class the_terminators_t +{ +public: + virtual ~the_terminators_t(); + + virtual void terminate(); + virtual bool verify_termination(); + + virtual void add(the_terminator_t * terminator); + virtual void del(the_terminator_t * terminator); + + // concurrent access controls: + virtual void lock() = 0; + virtual void unlock() = 0; + +private: + // the list of terminators: + std::list terminators_; +}; + + +#endif // THE_TERMINATOR_HXX_ diff --git a/include/IRThread.h b/include/IRThread.h new file mode 100644 index 0000000..2ff2cba --- /dev/null +++ b/include/IRThread.h @@ -0,0 +1,130 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_boost_thread.hxx +// Author : Pavel A. Koshevoy +// Created : Sat Oct 25 12:35:09 MDT 2008 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : A thin wrapper for Boost thread class. + +#ifndef THE_BOOST_THREAD_HXX_ +#define THE_BOOST_THREAD_HXX_ + +// std includes: +#include +#include + +// local includes: +#include "IRTerminator.h" +#include "IRThreadInterface.h" + +// forward declarations: +class the_mutex_interface_t; +class the_thread_storage_t; + + +//---------------------------------------------------------------- +// the_boost_terminators_t +// +class the_boost_terminators_t : public the_terminators_t +{ +public: + // virtual: concurrent access controls: + void lock() { mutex_.lock(); } + void unlock() { mutex_.unlock(); } + +private: + mutable std::mutex mutex_; +}; + + +//---------------------------------------------------------------- +// the_boost_thread_t +// +// 1. the thread will not take ownership of the transactions. +// 2. the thread will take ownership of the mutex. +// +class the_boost_thread_t : public the_thread_interface_t +{ +private: + struct callable_t + { + callable_t(the_boost_thread_t * thread): + thread_(thread) + {} + + void operator()() + { + thread_->run(); + } + + private: + the_boost_thread_t * thread_; + }; + +public: + the_boost_thread_t(); + + // the destructor is protected on purpose, + // see delete_this for details: + virtual ~the_boost_thread_t(); + + // In order to avoid memory management problems with shared libraries, + // whoever provides this interface instance (via it's creator), has to + // provide a way to delete the instance as well. This will avoid + // issues with multiple-instances of C runtime libraries being + // used by the app and whatever libraries it links against that + // either use or provide this interface: + virtual void delete_this(); + + // the creation method: + static the_thread_interface_t * create() + { return new the_boost_thread_t(); } + + // the thread storage accessor: + static the_thread_storage_t & thread_storage(); + + // virtual: start the thread: + void start(); + + // virtual: + void wait(); + + // virtual: put the thread to sleep: + void take_a_nap(const unsigned long & microseconds); + + // virtual: accessor to the transaction terminators: + the_terminators_t & terminators(); + +protected: + // virtual: + void run(); + + // the boost thread: + std::thread * boost_thread_; + + // a list of active terminators for this thread: + the_boost_terminators_t terminators_; +}; + + +#endif // THE_BOOST_THREAD_HXX_ diff --git a/include/IRThreadInterface.h b/include/IRThreadInterface.h new file mode 100644 index 0000000..8670839 --- /dev/null +++ b/include/IRThreadInterface.h @@ -0,0 +1,203 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_thread_interface.hxx +// Author : Pavel A. Koshevoy +// Created : Fri Feb 16 09:20:00 MST 2007 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : An abstract thread class interface. + +#ifndef THE_THREAD_INTERFACE_HXX_ +#define THE_THREAD_INTERFACE_HXX_ + +// system includes: +#include + +// local includes: +#include "IRTransaction.h" + +// forward declarations: +class the_mutex_interface_t; +class the_terminators_t; +class the_thread_pool_t; +struct the_thread_pool_data_t; + + +//---------------------------------------------------------------- +// the_thread_interface_t +// +// 1. the thread will not take ownership of the transactions. +// 2. the thread will take ownership of the mutex. +// +class the_thread_interface_t : public the_transaction_handler_t +{ + friend class the_thread_pool_t; + +protected: + // the destructor is protected on purpose, + // see delete_this for details: + virtual ~the_thread_interface_t(); + +public: + // In order to avoid memory management problems with shared libraries, + // whoever provides this interface instance (via it's creator), has to + // provide a way to delete the instance as well. This will avoid + // issues with multiple-instances of C runtime libraries being + // used by the app and whatever libraries it links against that + // either use or provide this interface: + virtual void delete_this() = 0; + + // the thread will own the mutex passed down to it, + // it will delete the mutex when the thread is deleted: + the_thread_interface_t(the_mutex_interface_t * mutex = NULL); + + //---------------------------------------------------------------- + // creator_t + // + typedef the_thread_interface_t *(*creator_t)(); + + // specify a thread creation method: + static void set_creator(creator_t creator); + + // create a new instance of a thread: + static the_thread_interface_t * create(); + + inline const unsigned int & id() const + { return id_; } + + // start the thread: + virtual void start() = 0; + + // wait for the thread to finish: + virtual void wait() = 0; + + // put the thread to sleep: + virtual void take_a_nap(const unsigned long & microseconds) = 0; + + // accessor to the transaction terminators: + virtual the_terminators_t & terminators() = 0; + + // give this thread an execution synchronization control: + void set_mutex(the_mutex_interface_t * mutex); + + // this controls whether the thread will voluntarily terminate + // once it runs out of transactions: + void set_idle_sleep_duration(bool enable, unsigned int microseconds = 10000); + + // schedule a transaction: + inline void add_transaction(the_transaction_t * transaction) + { push_back(transaction); } + + void push_back(the_transaction_t * transaction); + void push_front(the_transaction_t * transaction); + + // add transactions to the list: + void push_back(std::list & schedule); + + // NOTE: it is the responsibility of the caller to secure a mutex lock + // on this thread prior to checking whether the thread has any work left: + bool has_work() const; + + // schedule a transaction and start the thread: + void start(the_transaction_t * transaction); + + // abort the current transaction and clear pending transactions; + // transactionFinished will be emitted for the aborted transaction + // and the discarded pending transactions: + void stop(); + + // accessors to the thread "stopped" flag: + inline const bool & stopped() const + { return stopped_; } + + inline void set_stopped(bool stopped) + { stopped_ = stopped; } + + // clear all pending transactions, do not abort the current transaction: + void flush(); + + // this will call terminate_all for the terminators in this thread, + // but it will not stop the thread, so that new transactions may + // be scheduled while the old transactions are being terminated: + void terminate_transactions(); + + // terminate the current transactions and schedule a new transaction: + void stop_and_go(the_transaction_t * transaction); + void stop_and_go(std::list & schedule); + + // flush the current transactions and schedule a new transaction: + void flush_and_go(the_transaction_t * transaction); + void flush_and_go(std::list & schedule); + + // execute the scheduled transactions, return true if all + // transactions had been executed successfully: + virtual bool work(); + + // virtual: default transaction communication handlers: + void handle(the_transaction_t * transaction, the_transaction_t::state_t s); + void blab(const char * message) const; + + // transaction callback accessor: + inline void + set_thread_pool_cb(the_thread_pool_t * pool, + the_thread_pool_data_t * cb_data) + { + thread_pool_ = pool; + thread_pool_cb_data_ = cb_data; + } + + // mutex accessor: + inline the_mutex_interface_t * mutex() + { return mutex_; } + +protected: + // an abstract creator of threads: + static creator_t creator_; + + // FIXME: this may have been a bad idea: + unsigned int id_; + + // thread synchronization control: + the_mutex_interface_t * mutex_; + + // execution control flag: + bool stopped_; + + // these attributes control the thread behavior once all transactions + // have been processed. If sleep_when_idle_ flag is set to true, the + // thread will put itself to sleep instead of terminating: + bool sleep_when_idle_; + unsigned int sleep_microsec_; + + // currently executing transaction: + the_transaction_t * active_transaction_; + + // scheduled transactions: + std::list transactions_; + + // the transaction callback: + the_thread_pool_t * thread_pool_; + the_thread_pool_data_t * thread_pool_cb_data_; +}; + + +#endif // THE_THREAD_INTERFACE_HXX_ diff --git a/include/IRThreadPool.h b/include/IRThreadPool.h new file mode 100644 index 0000000..0b616ef --- /dev/null +++ b/include/IRThreadPool.h @@ -0,0 +1,217 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_thread_pool.hxx +// Author : Pavel A. Koshevoy +// Created : Wed Feb 21 08:30:00 MST 2007 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : A thread pool class. + +#ifndef THE_THREAD_POOL_HXX_ +#define THE_THREAD_POOL_HXX_ + +// system includes: +#include +#include + +// local includes: +#include "IRThreadInterface.h" +#include "IRTransaction.h" + +// forward declarations: +class the_mutex_interface_t; +class the_thread_pool_t; + + +//---------------------------------------------------------------- +// transaction_wrapper_t +// +class the_transaction_wrapper_t +{ +public: + the_transaction_wrapper_t(const unsigned int & num_parts, + the_transaction_t * transaction); + ~the_transaction_wrapper_t(); + + static void notify_cb(void * data, + the_transaction_t * t, + the_transaction_t::state_t s); + + bool notify(the_transaction_t * t, + the_transaction_t::state_t s); + +private: + the_transaction_wrapper_t(); + the_transaction_wrapper_t & operator = (const the_transaction_wrapper_t &); + + the_mutex_interface_t * mutex_; + + the_transaction_t::notify_cb_t cb_; + void * cb_data_; + + const unsigned int num_parts_; + unsigned int notified_[the_transaction_t::DONE_E + 1]; +}; + + +//---------------------------------------------------------------- +// the_thread_pool_data_t +// +struct the_thread_pool_data_t +{ + the_thread_pool_data_t(): + parent_(NULL), + thread_(NULL), + id_(~0u) + {} + + ~the_thread_pool_data_t() + { thread_->delete_this(); } + + the_thread_pool_t * parent_; + the_thread_interface_t * thread_; + unsigned int id_; +}; + + +//---------------------------------------------------------------- +// the_thread_pool_t +// +class the_thread_pool_t : public the_transaction_handler_t +{ + friend class the_thread_interface_t; + +public: + the_thread_pool_t(unsigned int num_threads); + virtual ~the_thread_pool_t(); + + // start the threads: + void start(); + + // this controls whether the thread will voluntarily terminate + // once it runs out of transactions: + void set_idle_sleep_duration(bool enable, unsigned int microseconds = 10000); + + // schedule a transaction: + // NOTE: when multithreaded is true, the transaction will be scheduled + // N times, where N is the number of threads in the pool. + // This means the transaction will be executed by N threads, so + // the transaction has to support concurrent execution internally. + virtual void push_front(the_transaction_t * transaction, + bool multithreaded = false); + + virtual void push_back(the_transaction_t * transaction, + bool multithreaded = false); + + virtual void push_back(std::list & schedule, + bool multithreaded = false); + + // split the work among the threads: + void pre_distribute_work(); + + // check whether the thread pool has any work left: + bool has_work() const; + + // schedule a transaction and start the thread: + virtual void start(the_transaction_t * transaction, + bool multithreaded = false); + + // abort the current transaction and clear pending transactions; + // transactionFinished will be emitted for the aborted transaction + // and the discarded pending transactions: + void stop(); + + // wait for all threads to finish: + void wait(); + + // clear all pending transactions, do not abort the current transaction: + void flush(); + + // this will call terminate_all for the terminators in this thread, + // but it will not stop the thread, so that new transactions may + // be scheduled while the old transactions are being terminated: + void terminate_transactions(); + + // terminate the current transactions and schedule a new transaction: + void stop_and_go(the_transaction_t * transaction, + bool multithreaded = false); + void stop_and_go(std::list & schedule, + bool multithreaded = false); + + // flush the current transactions and schedule a new transaction: + void flush_and_go(the_transaction_t * transaction, + bool multithreaded = false); + void flush_and_go(std::list & schedule, + bool multithreaded = false); + + // virtual: default transaction communication handlers: + void handle(the_transaction_t * transaction, the_transaction_t::state_t s); + void blab(const char * message) const; + + // access control: + inline the_mutex_interface_t * mutex() + { return mutex_; } + + inline const unsigned int & pool_size() const + { return pool_size_; } + +private: + // intentionally disabled: + the_thread_pool_t(const the_thread_pool_t &); + the_thread_pool_t & operator = (const the_thread_pool_t &); + +protected: + // thread callback handler: + virtual void handle_thread(the_thread_pool_data_t * data); + + // helpers: + inline the_thread_interface_t * thread(unsigned int id) const + { + assert(id < pool_size_); + return pool_[id].thread_; + } + + void no_lock_flush(); + void no_lock_terminate_transactions(); + void no_lock_push_front(the_transaction_t * transaction, bool multithreaded); + void no_lock_push_back(the_transaction_t * transaction, bool multithreaded); + void no_lock_push_back(std::list & schedule, bool mt); + + // thread synchronization control: + the_mutex_interface_t * mutex_; + + // the thread pool: + the_thread_pool_data_t * pool_; + unsigned int pool_size_; + + // the working threads: + std::list busy_; + + // the waiting threads: + std::list idle_; + + // scheduled transactions: + std::list transactions_; +}; + + +#endif // THE_THREAD_POOL_HXX_ diff --git a/include/IRThreadStorage.h b/include/IRThreadStorage.h new file mode 100644 index 0000000..e2444d6 --- /dev/null +++ b/include/IRThreadStorage.h @@ -0,0 +1,91 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_thread_storage.hxx +// Author : Pavel A. Koshevoy +// Created : Fri Jan 2 09:30:00 MDT 2007 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : A thread storage abstract interface class. + +#ifndef THE_THREAD_STORAGE_HXX_ +#define THE_THREAD_STORAGE_HXX_ + +// forward declarations: +class the_thread_interface_t; +class the_terminators_t; + +//---------------------------------------------------------------- +// the_thread_observer_t +// +class the_thread_observer_t +{ +public: + the_thread_observer_t(the_thread_interface_t & thread): + thread_(thread) + {} + + the_thread_interface_t & thread_; +}; + +//---------------------------------------------------------------- +// the_thread_storage_t +// +class the_thread_storage_t +{ +public: + virtual ~the_thread_storage_t() {} + + // check whether the thread storage has been initialized: + virtual bool is_ready() const = 0; + + // check whether the thread has been stopped: + virtual bool thread_stopped() const = 0; + + // terminator access: + virtual the_terminators_t & terminators() = 0; + + // thread id: + virtual unsigned int thread_id() const = 0; +}; + +//---------------------------------------------------------------- +// the_thread_storage_provider_t +// +typedef the_thread_storage_t&(*the_thread_storage_provider_t)(); + +//---------------------------------------------------------------- +// set_the_thread_storage_provider +// +// Set the new thread storage provider, return the old provider. +// +extern the_thread_storage_provider_t +set_the_thread_storage_provider(the_thread_storage_provider_t p); + +//---------------------------------------------------------------- +// the_thread_storage +// +// Thread storage accessors. +// +extern the_thread_storage_t & the_thread_storage(); + + +#endif // THE_THREAD_STORAGE_HXX_ diff --git a/include/IRTransaction.h b/include/IRTransaction.h new file mode 100644 index 0000000..acbad3a --- /dev/null +++ b/include/IRTransaction.h @@ -0,0 +1,216 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_transaction.hxx +// Author : Pavel A. Koshevoy +// Created : Fri Feb 16 09:52:00 MST 2007 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : A thread transaction class. + +#ifndef THE_TRANSACTION_HXX_ +#define THE_TRANSACTION_HXX_ + +// system includes: +#include +#include + +// the includes: +#include +#include + +// forward declarations: +class the_thread_interface_t; +class the_transaction_handler_t; + + +//---------------------------------------------------------------- +// the_transaction_t +// +// NOTE: the transaction will not take ownership of the mutex. +// +class the_transaction_t +{ +public: + the_transaction_t(); + virtual ~the_transaction_t(); + + // execute the transaction: + virtual void execute(the_thread_interface_t * thread) = 0; + + // transaction execution state: + typedef enum { + PENDING_E, + SKIPPED_E, + STARTED_E, + ABORTED_E, + DONE_E + } state_t; + + inline state_t state() const + { return state_; } + + inline void set_state(const state_t & s) + { state_ = s; } + + inline bool done() const + { return state_ == DONE_E; } + + //---------------------------------------------------------------- + // notify_cb_t + // + typedef void(*notify_cb_t)(void *, the_transaction_t *, state_t s); + + inline notify_cb_t notify_cb() const + { return notify_cb_; } + + inline void set_notify_cb(notify_cb_t cb, void * cb_data) + { + notify_cb_ = cb; + notify_cb_data_ = cb_data; + } + + //---------------------------------------------------------------- + // status_cb_t + // + // NOTE: if status is NULL, it means the transaction is requesting + // a callback from the main thread. This could be used by the + // transaction for some GUI user interaction (such as finding replacement + // tiles for a mosaic, etc...) + // + typedef void(*status_cb_t)(void *, the_transaction_t *, const char * status); + + inline status_cb_t status_cb() const + { return status_cb_; } + + inline void set_status_cb(status_cb_t cb, void * cb_data) + { + status_cb_ = cb; + status_cb_data_ = cb_data; + } + + // notify the transaction about a change in it's state: + virtual void notify(the_transaction_handler_t * handler, + state_t s, + const char * message = NULL); + + // helper: + virtual void blab(the_transaction_handler_t * handler, + const char * message); + + // FIXME: this is a relic: + inline static const the_text_t tr(const char * text) + { return the_text_t(text); } + + inline static const the_text_t & tr(const the_text_t & text) + { return text; } + + // if a transaction needs to have something executed in the main thread + // context it should call callback_request. This requires that a valid + // status callback is set for this transaction, and that the status + // callback will acknowledge the main thread callback request. This + // call will block until the callback is executed in the main thread, + // and the request is removed: + bool callback_request(); + + // if a transaction needs to have something executed in the main thread + // context it should override this callback. This method will be executed + // in the main thread context. The default implementation will simply + // remove the request: + virtual void callback(); + +protected: + // when requesting a callback from the main thread + // the status of request will be set to WAITING_E: + typedef enum { + NOTHING_E, + WAITING_E + } request_t; + + // synchronization control: + the_mutex_interface_t * mutex_; + + // status of callback request: + request_t request_; + + // current state of the transaction: + state_t state_; + +public: + // the callbacks: + notify_cb_t notify_cb_; + void * notify_cb_data_; + + status_cb_t status_cb_; + void * status_cb_data_; +}; + +//---------------------------------------------------------------- +// operator << +// +extern std::ostream & +operator << (std::ostream & so, const the_transaction_t::state_t & state); + + +//---------------------------------------------------------------- +// the_transaction_handler_t +// +class the_transaction_handler_t +{ +public: + virtual ~the_transaction_handler_t() {} + + virtual void handle(the_transaction_t * transaction, + the_transaction_t::state_t s) = 0; + virtual void blab(const char * message) const = 0; +}; + + +//---------------------------------------------------------------- +// the_transaction_log_t +// +class the_transaction_log_t : public the_log_t +{ +public: + the_transaction_log_t(the_transaction_handler_t * handler): + handler_(handler) + {} + + // virtual: + the_log_t & operator << (std::ostream & (*f)(std::ostream &)) + { + the_log_t::operator << (f); + std::string text(the_log_t::line_.str()); + the_log_t::line_.str(""); + handler_->blab(text.c_str()); + return *this; + } + + template + the_log_t & operator << (const data_t & data) + { return the_log_t::operator << (data); } + +private: + the_transaction_handler_t * handler_; +}; + + +#endif // THE_TRANSACTION_HXX_ diff --git a/include/IRV3X1P3X1.h b/include/IRV3X1P3X1.h new file mode 100644 index 0000000..e35e221 --- /dev/null +++ b/include/IRV3X1P3X1.h @@ -0,0 +1,971 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : v3x1p3x1.hxx +// Author : Pavel A. Koshevoy +// Created : Mon Jul 1 21:53:36 MDT 2002 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : 2D, 3D and 3D homogeneous points/vectors. + +#ifndef V3X1P3X1_HXX_ +#define V3X1P3X1_HXX_ + +// system includes: +#include +#include +#include +#include +#include +#include +#include + +// namespace access: +using std::ostream; +using std::setw; +using std::endl; +using std::ios; + +// forward declarations: +class m4x4_t; + +// MSVC does not define M_PI by default, so I will: +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +//---------------------------------------------------------------- +// THE_EPSILON +// +static const float +THE_EPSILON = 1e-6f; + +//---------------------------------------------------------------- +// THE_NEAR_ZERO_VECTOR_LENGTH +// +static const float +THE_NEAR_ZERO_VECTOR_LENGTH = 1e+1f * FLT_MIN; + + +// shorthand, undefined at the end of the file: +#define X_ data_[0] +#define Y_ data_[1] +#define Z_ data_[2] +#define W_ data_[3] + + +// In order to avoid memory leaks the member functions of these classes +// will not allocate anything 'new' on the heap. All operations +// work from the stack: + +//---------------------------------------------------------------- +// the_duplet_t +// +// base class for v2x1_t and p2x1_t: +// +template +class the_duplet_t +{ +public: + the_duplet_t() {} + + the_duplet_t(const T * data) + { + X_ = data[0]; + Y_ = data[1]; + } + + the_duplet_t(const T & x, const T & y) + { + X_ = x; + Y_ = y; + } + + // comparison operator: + inline bool operator == (const the_duplet_t & d) const + { return equal(d); } + + inline bool operator < (const the_duplet_t & d) const + { + return ((X_ < d.X_) || + (X_ == d.X_ && Y_ < d.Y_)); + } + + // compare this duplet to a given duplet for equality: + inline bool equal(const the_duplet_t & d) const + { + return ((X_ == d.X_) && + (Y_ == d.Y_)); + } + + // assign to this duplet: + inline void assign(const T * data) + { + X_ = data[0]; + Y_ = data[1]; + } + + inline void assign(const T & x, const T & y) + { + X_ = x; + Y_ = y; + } + + // scale this duplet by a given factor: + inline void scale(const T & s) + { X_ *= s; Y_ *= s; } + + // add a given duplet to this duplet: + inline void increment(const the_duplet_t & d) + { X_ += d.X_; Y_ += d.Y_; } + + // subtract a given duplet from this duplet: + inline void decrement(const the_duplet_t & d) + { X_ -= d.X_; Y_ -= d.Y_; } + + // this is for debugging purposes: + inline void dump(ostream & stream, const char * type_name) const + { + stream << '(' << type_name << " *)(" << (void *)this << ") " + << setw(12) << X_ << ' ' + << setw(12) << Y_; + } + + // const accessors: + inline const T & x() const { return X_; } + inline const T & y() const { return Y_; } + + // non-const accessors: + inline T & x() { return X_; } + inline T & y() { return Y_; } + + // array-like accessors: + inline const T & operator[] (const unsigned int & i) const + { return data_[i]; } + + inline T & operator[] (const unsigned int & i) + { return data_[i]; } + + // raw data accessors: + inline const T * data() const + { return data_; } + + inline T * data() + { return data_; } + +protected: + T data_[2]; +}; + +//---------------------------------------------------------------- +// operator << +// +template +inline ostream & +operator << (ostream & stream, const the_duplet_t & p) +{ + p.dump(stream, "the_duplet_t"); + return stream; +} + + +//---------------------------------------------------------------- +// the_triplet_t +// +// base class for v3x1_t and p3x1_t: +// +template +class the_triplet_t +{ + friend class m4x4_t; + +public: + the_triplet_t() {} + + the_triplet_t(const T * data) + { + X_ = data[0]; + Y_ = data[1]; + Z_ = data[2]; + } + + the_triplet_t(const T & x, const T & y, const T & z) + { + X_ = x; + Y_ = y; + Z_ = z; + } + + // comparison operator: + inline bool operator == (const the_duplet_t & d) const + { return equal(d); } + + inline bool operator < (const the_duplet_t & d) const + { + return ((X_ < d.X_) || + (X_ == d.X_ && Y_ < d.Y_) || + (X_ == d.X_ && Y_ == d.Y_ && Z_ < d.Z_)); + } + + // compare this triplet to a given triplet for equality: + inline bool equal(const the_triplet_t & d) const + { + return ((X_ == d.X_) && + (Y_ == d.Y_) && + (Z_ == d.Z_)); + } + + // assign to this triplet: + inline void assign(const T * data) + { + X_ = data[0]; + Y_ = data[1]; + Z_ = data[2]; + } + + inline void assign(const T & x, const T & y, const T & z) + { + X_ = x; + Y_ = y; + Z_ = z; + } + + // copy data from this triplet: + inline void get(T * data) const + { get(data[0], data[1], data[2]); } + + inline void get(T & x, T & y, T & z) const + { x = X_; y = Y_; z = Z_; } + + // scale this triplet by a given factor: + inline void scale(const T & s) + { X_ *= s; Y_ *= s; Z_ *= s; } + + // reverse the vector: + inline void negate() + { X_ = -X_; Y_ = -Y_; Z_ = -Z_; } + + // add a given triplet to this triplet: + inline void increment(const the_triplet_t & d) + { X_ += d.X_; Y_ += d.Y_; Z_ += d.Z_; } + + // subtract a given triplet from this triplet: + inline void decrement(const the_triplet_t & d) + { X_ -= d.X_; Y_ -= d.Y_; Z_ -= d.Z_; } + + // this is for debugging purposes: + inline void dump(ostream & stream, const char * type_name) const + { + stream << '(' << type_name << " *)(" << (void *)this << ") " + << setw(12) << X_ << ' ' + << setw(12) << Y_ << ' ' + << setw(12) << Z_; + } + + // const accessors: + inline const T & x() const { return X_; } + inline const T & y() const { return Y_; } + inline const T & z() const { return Z_; } + + // non-const accessors: + inline T & x() { return X_; } + inline T & y() { return Y_; } + inline T & z() { return Z_; } + + // array-like accessors: + inline const T & operator[] (const unsigned int & i) const + { return data_[i]; } + + inline T & operator[] (const unsigned int & i) + { return data_[i]; } + + // raw data accessors: + inline const T * data() const + { return data_; } + + inline T * data() + { return data_; } + +protected: + T data_[3]; +}; + +//---------------------------------------------------------------- +// operator << +// +template +inline ostream & +operator << (ostream & stream, const the_triplet_t & p) +{ + p.dump(stream, "the_triplet_t"); + return stream; +} + + +//---------------------------------------------------------------- +// the_quadruplet_t +// +// base class for v4x1_t and p4x1_t: +// +template +class the_quadruplet_t +{ + friend class m4x4_t; + +public: + the_quadruplet_t() {} + + the_quadruplet_t(const T * data) + { + X_ = data[0]; + Y_ = data[1]; + Z_ = data[2]; + W_ = data[3]; + } + + the_quadruplet_t(const T & x, const T & y, const T & z, const T & w) + { + X_ = x; + Y_ = y; + Z_ = z; + W_ = w; + } + + // comparison operator: + inline bool operator == (const the_duplet_t & d) const + { return equal(d); } + + inline bool operator < (const the_duplet_t & d) const + { + return ((X_ < d.X_) || + (X_ == d.X_ && Y_ < d.Y_) || + (X_ == d.X_ && Y_ == d.Y_ && Z_ < d.Z_) || + (X_ == d.X_ && Y_ == d.Y_ && Z_ == d.Z_ && W_ < d.W_)); + } + + // compare this quadruplet to a given quadruplet for equality: + inline bool equal(const the_quadruplet_t & d) const + { + return ((X_ == d.X_) && + (Y_ == d.Y_) && + (Z_ == d.Z_) && + (W_ == d.W_)); + } + + // assign to this quadruplet: + inline void assign(const T * data) + { + X_ = data[0]; + Y_ = data[1]; + Z_ = data[2]; + W_ = data[3]; + } + + inline void assign(const T & x, const T & y, const T & z, const T & w) + { + X_ = x; + Y_ = y; + Z_ = z; + W_ = w; + } + + // scale this quadruplet by a given factor: + inline void scale(const T & s) + { X_ *= s; Y_ *= s; Z_ *= s; W_ *= s; } + + // add a given quadruplet to this quadruplet: + inline void increment(const the_quadruplet_t & d) + { X_ += d.X_; Y_ += d.Y_; Z_ += d.Z_; W_ += d.W_; } + + // subtract a given quadruplet from this quadruplet: + inline void decrement(const the_quadruplet_t & d) + { X_ -= d.X_; Y_ -= d.Y_; Z_ -= d.Z_; W_ -= d.W_; } + + // this is for debugging purposes: + inline void dump(ostream & stream, const char * type_name) const + { + stream << '(' << type_name << " *)(" << (void *)this << ") " + << setw(12) << X_ << ' ' + << setw(12) << Y_ << ' ' + << setw(12) << Z_ << ' ' + << setw(12) << W_; + } + + // const accessors: + inline const T & x() const { return X_; } + inline const T & y() const { return Y_; } + inline const T & z() const { return Z_; } + inline const T & w() const { return W_; } + + // non-const accessors: + inline T & x() { return X_; } + inline T & y() { return Y_; } + inline T & z() { return Z_; } + inline T & w() { return W_; } + + // array-like accessors: + inline const T & operator[] (const unsigned int & i) const + { return data_[i]; } + + inline T & operator[] (const unsigned int & i) + { return data_[i]; } + + // raw data accessors: + inline const T * data() const + { return data_; } + + inline T * data() + { return data_; } + +protected: + T data_[4]; +}; + +//---------------------------------------------------------------- +// operator << +// +template +inline ostream & +operator << (ostream & stream, const the_quadruplet_t & p) +{ + p.dump(stream, "the_quadruplet_t"); + return stream; +} + + +//---------------------------------------------------------------- +// v2x1_t +// +class v2x1_t : public the_duplet_t +{ +public: + v2x1_t() {} + + v2x1_t(const float * data): + the_duplet_t(data) + {} + + v2x1_t(const float & x, const float & y): + the_duplet_t(x, y) + {} + + // equality/inequality test operators: + inline bool operator == (const v2x1_t & v) const { return equal(v); } + inline bool operator != (const v2x1_t & v) const { return !equal(v); } + + // scale this vector: + inline v2x1_t & operator *= (const float & s) + { scale(s); return *this; } + + inline v2x1_t & operator /= (const float & s) + { scale(1 / s); return *this; } + + // return a copy of this vector scaled by a given factor: + inline const v2x1_t operator * (const float & s) const + { v2x1_t r(*this); r *= s; return r; } + + inline const v2x1_t operator / (const float & s) const + { v2x1_t r(*this); r /= s; return r; } + + // increment/decrement this vector: + inline v2x1_t & operator += (const v2x1_t & v) + { increment(v); return *this; } + + inline v2x1_t & operator -= (const v2x1_t & v) + { decrement(v); return *this; } + + // return a copy of this vector plus/minus a given vector: + inline const v2x1_t operator + (const v2x1_t & v) const + { v2x1_t r(*this); r += v; return r; } + + inline const v2x1_t operator - (const v2x1_t & v) const + { v2x1_t r(*this); r -= v; return r; } + + // return the dot product between this vector and a given factor: + inline float operator * (const v2x1_t & v) const + { return (X_ * v.X_ + Y_ * v.Y_); } + + // norm operator (returns the magnitude/length/norm of this vector): + inline float operator ~ () const { return norm(); } + + // return a copy of this vector with norm 1.0, or 0.0 if it can not + // be normalized (divided by the norm): + inline const v2x1_t operator ! () const + { v2x1_t r(*this); r.normalize(); return r; } + + // this = this / |this| + inline bool normalize() + { + float n = norm(); + if (n < THE_NEAR_ZERO_VECTOR_LENGTH) + { + scale(0); + return false; + } + + scale(1 / n); + return true; + } + + // this is necessary in order to allow sorting: + inline bool operator < (const v2x1_t & v) const + { return norm_sqrd() < v.norm_sqrd(); } + + // the squared length of this vector + inline float norm_sqrd() const + { return operator * (*this); } + + // return the norm (length, magnitude) of this vector: + inline float norm() const + { return sqrtf(norm_sqrd()); } +}; + +//---------------------------------------------------------------- +// operator - +// +inline const v2x1_t +operator - (const v2x1_t & v) +{ + return (v * (-1.0)); +} + +//---------------------------------------------------------------- +// operator * +// +inline const v2x1_t +operator * (const float & s, const v2x1_t & v) +{ + return v * s; +} + +//---------------------------------------------------------------- +// operator << +// +inline ostream & +operator << (ostream & stream, const v2x1_t & v) +{ + v.dump(stream, "v2x1_t"); + return stream; +} + + +//---------------------------------------------------------------- +// v3x1_t +// +class v3x1_t : public the_triplet_t +{ +public: + v3x1_t() {} + + v3x1_t(const float * data): + the_triplet_t(data) + {} + + v3x1_t(const float x, const float & y, const float & z): + the_triplet_t(x, y, z) + {} + + v3x1_t(const v2x1_t & v, const float & z = 0.0): + the_triplet_t(v.x(), v.y(), z) + {} + + // equality/inequality test operators: + inline bool operator == (const v3x1_t & v) const { return equal(v); } + inline bool operator != (const v3x1_t & v) const { return !equal(v); } + + // scale this vector: + inline v3x1_t & operator *= (const float & s) + { scale(s); return *this; } + + inline v3x1_t & operator /= (const float & s) + { scale(1 / s); return *this; } + + // return a copy of this vector scaled by a given factor: + inline const v3x1_t operator * (const float & s) const + { v3x1_t r(*this); r *= s; return r; } + + inline const v3x1_t operator / (const float & s) const + { v3x1_t r(*this); r /= s; return r; } + + // increment/decrement this vector: + inline v3x1_t & operator += (const v3x1_t & v) + { increment(v); return *this; } + + inline v3x1_t & operator -= (const v3x1_t & v) + { decrement(v); return *this; } + + // return a copy of this vector plus/minus a given vector: + inline const v3x1_t operator + (const v3x1_t & v) const + { v3x1_t r(*this); r += v; return r; } + + inline const v3x1_t operator - (const v3x1_t & v) const + { v3x1_t r(*this); r -= v; return r; } + + // return the dot product between this vector and a given vector: + inline float operator * (const v3x1_t & v) const + { return (X_ * v.X_ + Y_ * v.Y_ + Z_ * v.Z_); } + + // return the cross product between this vector and a given vector: + inline const v3x1_t operator % (const v3x1_t & v) const + { + return v3x1_t((Y_ * v.Z_) - (Z_ * v.Y_), + (Z_ * v.X_) - (X_ * v.Z_), + (X_ * v.Y_) - (v.X_ * Y_)); + } + + // norm operator (returns the magnitude/length/norm of this vector): + inline float operator ~ () const { return norm(); } + + // return a copy of this vector with norm 1.0, or 0.0 if it can not + // be normalized (divided by the norm): + inline const v3x1_t operator ! () const + { v3x1_t r(*this); r.normalize(); return r; } + + // this = this / |this| + inline bool normalize() + { + float n = norm(); + if (n < THE_NEAR_ZERO_VECTOR_LENGTH) + { + scale(0); + return false; + } + + scale(1 / n); + return true; + } + + // this is necessary in order to allow sorting: + inline bool operator < (const v3x1_t & v) const + { return norm_sqrd() < v.norm_sqrd(); } + + // the squared length of this vector + inline float norm_sqrd() const + { return operator * (*this); } + + // return the norm (length, magnitude) of this vector: + inline float norm() const + { return sqrtf(norm_sqrd()); } + + // return a vector normal to this vector: + inline const v3x1_t normal() const + { + static const v3x1_t xyz[] = + { + v3x1_t(1.0, 0.0, 0.0), + v3x1_t(0.0, 1.0, 0.0), + v3x1_t(0.0, 0.0, 1.0) + }; + + v3x1_t unit_vec = !(*this); + for (unsigned int i = 0; i < 3; i++) + { + if (fabsf(unit_vec * xyz[i]) > 0.7) continue; + return !(unit_vec % xyz[i]); + } + + // this should never happen: + assert(false); + return v3x1_t(0.0, 0.0, 0.0); + } + + // conversion operator: + inline operator v2x1_t() const + { return v2x1_t(X_, Y_); } +}; + +//---------------------------------------------------------------- +// operator - +// +inline const v3x1_t +operator - (const v3x1_t & v) +{ + return v3x1_t(-v.x(), -v.y(), -v.z()); +} + +//---------------------------------------------------------------- +// operator * +// +inline const v3x1_t +operator * (const float & s, const v3x1_t & v) +{ + return v * s; +} + +//---------------------------------------------------------------- +// operator << +// +inline ostream & +operator << (ostream & stream, const v3x1_t & v) +{ + v.dump(stream, "v3x1_t"); + return stream; +} + + +//---------------------------------------------------------------- +// p2x1_t +// +class p2x1_t : public the_duplet_t +{ +public: + p2x1_t() {} + + p2x1_t(const float * data): + the_duplet_t(data) + {} + + p2x1_t(const float & x, const float & y): + the_duplet_t(x, y) + {} + + // equality/inequality test operators: + inline bool operator == (const p2x1_t & p) const { return equal(p); } + inline bool operator != (const p2x1_t & p) const { return !equal(p); } + + // this is necessary in order to allow sorting: + inline bool operator < (const p2x1_t & p) const + { + p2x1_t zero(0.0, 0.0); + return (*this - zero) < (p - zero); + } + + // scale this point: + inline p2x1_t & operator *= (const float & s) + { scale(s); return *this; } + + inline p2x1_t & operator /= (const float & s) + { scale(1 / s); return *this; } + + // return a copy of this point scaled by a given factor: + inline const p2x1_t operator * (const float & s) const + { p2x1_t r(*this); r *= s; return r; } + + inline const p2x1_t operator / (const float & s) const + { p2x1_t r(*this); r /= s; return r; } + + // this is used for linear combinations (in combination with scale): + inline const p2x1_t operator + (const p2x1_t & p) const + { p2x1_t r(*this); r.increment(p); return r; } + + // translate this point by a vector: + inline p2x1_t & operator += (const v2x1_t & v) + { increment(v); return *this; } + + inline p2x1_t & operator -= (const v2x1_t & v) + { decrement(v); return *this; } + + // return a copy of this point translated by a given vector: + inline const p2x1_t operator + (const v2x1_t & v) const + { p2x1_t r(data_); r += v; return r; } + + inline const p2x1_t operator - (const v2x1_t & v) const + { p2x1_t r(data_); r -= v; return r; } + + // return the vector difference between this point a given point: + inline const v2x1_t operator - (const p2x1_t & p) const + { v2x1_t r(data_); r.decrement(p); return r; } +}; + +//---------------------------------------------------------------- +// operator + +// +inline const p2x1_t +operator + (const v2x1_t & v, const p2x1_t & p) +{ + return p + v; +} + +//---------------------------------------------------------------- +// operator * +// +inline const p2x1_t +operator * (const float & s, const p2x1_t & p) +{ + return p * s; +} + +//---------------------------------------------------------------- +// operator << +// +inline ostream & +operator << (ostream & stream, const p2x1_t & p) +{ + p.dump(stream, "p2x1_t"); + return stream; +} + + +//---------------------------------------------------------------- +// p3x1_t +// +class p3x1_t : public the_triplet_t +{ +public: + p3x1_t() {} + + p3x1_t(const float * data): + the_triplet_t(data) + {} + + p3x1_t(const float & x, const float & y, const float & z): + the_triplet_t(x, y, z) + {} + + p3x1_t(const p2x1_t & p, const float & z = 0.0): + the_triplet_t(p.x(), p.y(), z) + {} + + // equality/inequality test operators: + inline bool operator == (const p3x1_t & p) const { return equal(p); } + inline bool operator != (const p3x1_t & p) const { return !equal(p); } + + // this is necessary in order to allow sorting: + inline bool operator < (const p3x1_t & p) const + { + p3x1_t zero(0.0, 0.0, 0.0); + return (*this - zero) < (p - zero); + } + + // scale this point: + inline p3x1_t & operator *= (const float & s) + { scale(s); return *this; } + + inline p3x1_t & operator /= (const float & s) + { scale(1 / s); return *this; } + + // return a copy of this point scaled by a given factor: + inline const p3x1_t operator * (const float & s) const + { p3x1_t r(*this); r *= s; return r; } + + inline const p3x1_t operator / (const float & s) const + { p3x1_t r(*this); r /= s; return r; } + + // this is used for linear combinations (in combination with scale): + inline const p3x1_t operator + (const p3x1_t & p) const + { p3x1_t r(*this); r.increment(p); return r; } + + // translate this point by a vector: + inline p3x1_t & operator += (const v3x1_t & v) + { increment(v); return *this; } + + inline p3x1_t & operator += (const p3x1_t & p) + { increment(p); return *this; } + + inline p3x1_t & operator -= (const v3x1_t & v) + { decrement(v); return *this; } + + // return a copy of this point translated by a given vector: + inline const p3x1_t operator + (const v3x1_t & v) const + { p3x1_t r(data_); r += v; return r; } + + inline const p3x1_t operator - (const v3x1_t & v) const + { p3x1_t r(data_); r -= v; return r; } + + // return the vector difference between this point a given point: + inline const v3x1_t operator - (const p3x1_t & p) const + { v3x1_t r(data_); r.decrement(p); return r; } + + // conversion operator: + inline operator p2x1_t() const + { return p2x1_t(X_, Y_); } +}; + +//---------------------------------------------------------------- +// operator + +// +inline const p3x1_t +operator + (const v3x1_t & v, const p3x1_t & p) +{ + return p + v; +} + +//---------------------------------------------------------------- +// operator * +// +inline const p3x1_t +operator * (const float & s, const p3x1_t & p) +{ + return p * s; +} + +//---------------------------------------------------------------- +// operator << +// +inline ostream & +operator << (ostream & stream, const p3x1_t & p) +{ + p.dump(stream, "p3x1_t"); + return stream; +} + + +//---------------------------------------------------------------- +// p4x1_t +// +class p4x1_t : public the_quadruplet_t +{ +public: + p4x1_t() {} + + p4x1_t(const float * data): + the_quadruplet_t(data) + {} + + p4x1_t(const float & x, + const float & y, + const float & z, + const float & w): + the_quadruplet_t(x, y, z, w) + {} + + p4x1_t(const p3x1_t & p, const float & w = 1.0): + the_quadruplet_t(p.x(), p.y(), p.z(), w) + {} + + // homogenize this point (x = x/w, y = y/w, z = z/w, w = 1.0): + inline void homogenize() + { X_ = X_ / W_; Y_ = Y_ / W_; Z_ = Z_ / W_; W_ = 1.0; } + + // conversion operators: + inline operator p3x1_t() const + { return p3x1_t(X_ / W_, Y_ / W_, Z_ / W_); } +}; + +//---------------------------------------------------------------- +// operator << +// +inline ostream & +operator << (ostream & stream, const p4x1_t & p) +{ + p.dump(stream, "p4x1_t"); + return stream; +} + + +// undefine the shorthand: +#undef X_ +#undef Y_ +#undef Z_ +#undef W_ + + +#endif // V3X1P3X1_HXX_ diff --git a/include/itkGridTransform.h b/include/itkGridTransform.h new file mode 100644 index 0000000..dbc3091 --- /dev/null +++ b/include/itkGridTransform.h @@ -0,0 +1,352 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : itkGridTransform.h +// Author : Pavel A. Koshevoy +// Created : 2006/11/20 22:48 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : A discontinuous transform -- a uniform grid of vertices is +// mapped to an image. At each vertex, in addition to image +// space coordinates, a second set of coordinates is stored. +// This is similar to texture mapped OpenGL triangle meshes, +// where the texture coordinates correspond to the image space +// vertex coordinates. + +#ifndef __itkGridTransform_h +#define __itkGridTransform_h + +// system includes: +#include +#include +#include + +// ITK includes: +#include +#include +#include +#include + +// local includes: +#include "IRGridTransform.h" + + +//---------------------------------------------------------------- +// itk::GridTransform +// +namespace itk +{ +class GridTransform : public Transform +{ +public: + // standard typedefs: + typedef GridTransform Self; + typedef SmartPointer Pointer; + typedef SmartPointer ConstPointer; + + typedef Transform Superclass; + + // Base inverse transform type: + typedef Superclass InverseTransformType; + typedef SmartPointer InverseTransformPointer; + + // RTTI: + itkTypeMacro(GridTransform, Transform); + + // macro for instantiation through the object factory: + itkNewMacro(Self); + + /** Standard scalar type for this class. */ + typedef double ScalarType; + + /** Dimension of the domain space. */ + itkStaticConstMacro(InputSpaceDimension, unsigned int, 2); + itkStaticConstMacro(OutputSpaceDimension, unsigned int, 2); + + // shortcuts: + typedef Superclass::ParametersType ParametersType; + typedef Superclass::JacobianType JacobianType; + + typedef Superclass::InputPointType InputPointType; + typedef Superclass::OutputPointType OutputPointType; + + // virtual: + OutputPointType + TransformPoint(const InputPointType & x) const + { + OutputPointType y; + if (is_inverse()) + { + pnt2d_t uv; + uv[0] = (x[0] - transform_.tile_min_[0]) / transform_.tile_ext_[0]; + uv[1] = (x[1] - transform_.tile_min_[1]) / transform_.tile_ext_[1]; + transform_.transform_inv(uv, y); + } + else + { + transform_.transform(x, y); + y[0] *= transform_.tile_ext_[0]; + y[1] *= transform_.tile_ext_[1]; + y[0] += transform_.tile_min_[0]; + y[1] += transform_.tile_min_[1]; + } + + // ITK does not handle NaN numbers: + if (y[0] != y[0]) + { + y[0] = std::numeric_limits::max(); + y[1] = y[0]; + } + + return y; + } + + // Inverse transformations: + // If y = Transform(x), then x = BackTransform(y); + // if no mapping from y to x exists, then an exception is thrown. + InputPointType + BackTransformPoint(const OutputPointType & y) const + { + InputPointType x; + if (is_inverse()) + { + transform_.transform(y, x); + x[0] *= transform_.tile_ext_[0]; + x[1] *= transform_.tile_ext_[1]; + x[0] += transform_.tile_min_[0]; + x[1] += transform_.tile_min_[1]; + } + else + { + pnt2d_t uv; + uv[0] = (y[0] - transform_.tile_min_[0]) / transform_.tile_ext_[0]; + uv[1] = (y[1] - transform_.tile_min_[1]) / transform_.tile_ext_[1]; + transform_.transform_inv(uv, x); + } + + // ITK does not handle NaN numbers: + if (x[0] != x[0]) + { + x[0] = std::numeric_limits::max(); + x[1] = x[0]; + } + + return x; + } + + using InputDiffusionTensor3DType = typename Superclass::InputDiffusionTensor3DType; + using OutputDiffusionTensor3DType = typename Superclass::OutputDiffusionTensor3DType; + OutputDiffusionTensor3DType + TransformDiffusionTensor3D(const InputDiffusionTensor3DType & inputTensor, const InputPointType & point) const override + { + itkExceptionMacro("TransformDiffusionTensor3D( const InputDiffusionTensor3DType & ) is " + "unimplemented for " + << this->GetNameOfClass()); + } + + using InputVectorPixelType = typename Superclass::InputVectorPixelType; + using OutputVectorPixelType = typename Superclass::OutputVectorPixelType; + OutputVectorPixelType + TransformDiffusionTensor3D(const InputVectorPixelType & inputTensor, const InputPointType & point) const override + { + itkExceptionMacro("TransformDiffusionTensor3D( const InputVectorPixelType & ) is " + "unimplemented for " + << this->GetNameOfClass()); + } + + + // virtual: + void + SetFixedParameters(const ParametersType & params) + { + this->m_FixedParameters = params; + } + + // virtual: + const ParametersType & + GetFixedParameters() const + { + ParametersType params = this->m_FixedParameters; + + params[1] = transform_.rows_; + params[2] = transform_.cols_; + params[3] = transform_.tile_min_[0]; + params[4] = transform_.tile_min_[1]; + params[5] = transform_.tile_ext_[0]; + params[6] = transform_.tile_ext_[1]; + + // update the parameters vector: + GridTransform * fake = const_cast(this); + fake->m_FixedParameters = params; + return this->m_FixedParameters; + } + + // virtual: + void + SetParameters(const ParametersType & params) + { + this->m_Parameters = params; + + size_t rows = int(this->m_FixedParameters[1]); + size_t cols = int(this->m_FixedParameters[2]); + + pnt2d_t tile_min; + tile_min[0] = this->m_FixedParameters[3]; + tile_min[1] = this->m_FixedParameters[4]; + + pnt2d_t tile_max; + tile_max[0] = tile_min[0] + this->m_FixedParameters[5]; + tile_max[1] = tile_min[1] + this->m_FixedParameters[6]; + + std::vector xy((rows + 1) * (cols + 1)); + + unsigned int num_points = xy.size(); + assert(2 * num_points == params.size()); + for (unsigned int i = 0; i < num_points; i++) + { + xy[i][0] = params[i * 2]; + xy[i][1] = params[i * 2 + 1]; + } + + transform_.setup(rows, cols, tile_min, tile_max, xy); + } + + // virtual: + const ParametersType & + GetParameters() const + { + ParametersType params(GetNumberOfParameters()); + unsigned int num_pts = params.size() / 2; + unsigned int num_cols = (transform_.cols_ + 1); + for (unsigned int i = 0; i < num_pts; i++) + { + unsigned int row = i / num_cols; + unsigned int col = i % num_cols; + + const pnt2d_t & xy = transform_.vertex(row, col).xy_; + unsigned int idx = 2 * i; + params[idx] = xy[0]; + params[idx + 1] = xy[1]; + } + + // update the parameters vector: + GridTransform * fake = const_cast(this); + fake->m_Parameters = params; + return this->m_Parameters; + } + + // virtual: + Superclass::NumberOfParametersType + GetNumberOfParameters() const override + { + return 2 * transform_.grid_.mesh_.size(); + } + + // virtual: return an inverse of this transform. + InverseTransformPointer + GetInverse() const + { + GridTransform::Pointer inv = GridTransform::New(); + inv->setup(transform_, !is_inverse()); + return inv.GetPointer(); + } + + // setup the transform: + void + setup(const the_grid_transform_t & transform, const bool & is_inverse = false) + { + transform_ = transform; + GetParameters(); + GetFixedParameters(); + this->m_FixedParameters[0] = is_inverse ? 1.0 : 0.0; + } + + // inverse transform flag check: + inline bool + is_inverse() const + { + return this->m_FixedParameters[0] != 0.0; + } + + void + ComputeJacobianWithRespectToParameters(const InputPointType & point, JacobianType & jacobian) const override + { + // FIXME: 20061227 -- this function was written and not tested: + + // these scales are necessary to account for the fact that + // the_grid_transform_t expects uv in the [0,1]x[0,1] range, + // where as we remap it into the image tile physical coordinates + // according to tile_min_ and tile_ext_: + double Su = transform_.tile_ext_[0]; + double Sv = transform_.tile_ext_[1]; + + unsigned int idx[3]; + double jac[12]; + jacobian.SetSize(2, GetNumberOfParameters()); + jacobian.Fill(0.0); + if (transform_.jacobian(point, idx, jac)) + { + for (unsigned int i = 0; i < 3; i++) + { + unsigned int addr = idx[i] * 2; + jacobian(0, addr) = Su * jac[i * 2]; + jacobian(0, addr + 1) = Su * jac[i * 2 + 1]; + jacobian(1, addr) = Sv * jac[i * 2 + 6]; + jacobian(1, addr + 1) = Sv * jac[i * 2 + 7]; + } + } + } + +protected: + GridTransform() + { + this->m_FixedParameters.SetSize(7); + + // initialize the inverse flag: + this->m_FixedParameters[0] = 0.0; + + // grid size: + this->m_FixedParameters[1] = 0.0; + this->m_FixedParameters[2] = 0.0; + + // tile bbox: + this->m_FixedParameters[3] = std::numeric_limits::max(); + this->m_FixedParameters[4] = this->m_FixedParameters[3]; + this->m_FixedParameters[5] = -(this->m_FixedParameters[3]); + this->m_FixedParameters[6] = -(this->m_FixedParameters[3]); + } + +private: + // disable default copy constructor and assignment operator: + GridTransform(const Self & other); + const Self & + operator=(const Self & t); + +public: + // the actual transform: + the_grid_transform_t transform_; + +}; // class GridTransform + +} // namespace itk + + +#endif // __itkGridTransform_h diff --git a/include/itkIRCommon.h b/include/itkIRCommon.h new file mode 100644 index 0000000..da554c2 --- /dev/null +++ b/include/itkIRCommon.h @@ -0,0 +1,6795 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: nil -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : common.hxx +// Author : Pavel A. Koshevoy +// Created : 2005/03/24 16:53 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : Helper functions for mosaicing, image warping, +// image preprocessing, convenience wrappers for +// ITK file and ITK filters. + +#ifndef itkIRCommon_h +#define itkIRCommon_h + +// common: +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// image metrics: +#include +#include + +// image interpolators: +#include +#include +#include + +// registration: +#include +#include + +// transforms: +#include +#include +#include + +// mosaic: +#include +#include +#include +#include +#include +#include +#include + +// local includes: +#include "itkIRText.h" +#include "itkIRUtils.h" +#include "itkIRTerminator.h" +#include "IRThread.h" +#include "IRTransaction.h" +#include "IRThreadPool.h" +// #include "utils/AsyncMosaicSave.h" +#include "itkNormalizeImageFilterWithMask.h" +#include "itkLegendrePolynomialTransform.h" + +// system includes: +#include +#include +#include +#include +#include +#include + +// namespace access: +using std::cout; +using std::cerr; +using std::endl; +using std::setw; +using std::ios; +using std::flush; + + +#include "IRPath.h" + +extern const char * g_Version; + + +//---------------------------------------------------------------- +// image_size_value_t +// +typedef itk::Size<2>::SizeValueType image_size_value_t; + +//---------------------------------------------------------------- +// mask_t +// +// 2D image with unsigned char pixel type. +// +typedef itk::Image mask_t; + +//---------------------------------------------------------------- +// mask_so_t +// +// Spatial object for mask_t, used to specify a mask to ITK image +// filters. +// +typedef itk::ImageMaskSpatialObject<2> mask_so_t; + +//---------------------------------------------------------------- +// mask_so +// +// Convenience function for setting up a mask spatial object for +// a give mask image. +// +inline mask_so_t::Pointer +mask_so(const mask_t * mask) +{ + mask_so_t::Pointer so = mask_so_t::New(); + so->SetImage(mask); + return so; +} + +//---------------------------------------------------------------- +// BREAK +// +// This is for debugging. Insert a call to BREAK somewhere in +// your code, recompile. Then load into a debugger and put a break +// point in BREAK. This is to be used in those cases when you know +// exactly when you want to hit the breakpoint. +// +extern int +BREAK(unsigned int i); + +//---------------------------------------------------------------- +// native_pixel_t +// +// Native refers to the usual 8 bit pixels here. These are native +// (standard) in the real world, but may look odd in the medical +// imaging world where float or short int are more common. +// +typedef unsigned char native_pixel_t; + +//---------------------------------------------------------------- +// native_image_t +// +// 8-bit grayscale image. +// +typedef itk::Image native_image_t; + +//---------------------------------------------------------------- +// pixel_t +// +// All-encompasing pixel type -- float. +// Works everywhere, takes up 4 times as much memory as 8-bit pixels. +// +typedef float pixel_t; + +//---------------------------------------------------------------- +// std_tile +// +// Load a mosaic tile image, reset the origin to zero, set pixel +// spacing as specified. Downscale the image according to the +// shrink factor. +// +template +typename T::Pointer +std_tile(const char * fn_image, const unsigned int & shrink_factor, const double & pixel_spacing, bool blab = true); + +//---------------------------------------------------------------- +// image_t +// +// float grayscale image. +// +typedef itk::Image image_t; + +//---------------------------------------------------------------- +// base_transform_t +// +// Shorthand for abstract 2D tranforms. +// +typedef itk::Transform base_transform_t; + +//---------------------------------------------------------------- +// identity_transform_t +// +// Shorthand for 2D identity ITK transform. +// +typedef itk::IdentityTransform identity_transform_t; + +//---------------------------------------------------------------- +// translate_transform_t +// +// Shorthand for 2D rigid translation ITK transform. +// +typedef itk::TranslationTransform translate_transform_t; + +//---------------------------------------------------------------- +// pnt2d_t +// +// Shorthand for 2D points. +// +typedef itk::Point pnt2d_t; + +//---------------------------------------------------------------- +// vec2d_t +// +// Shorthand for 2D vectors. +// +typedef itk::Vector vec2d_t; + +//---------------------------------------------------------------- +// xyz_t +// +// Shorthand for 3D points. This is typically used to represent RGB +// or HSV colors. +// +typedef itk::Vector xyz_t; + + +// Assuming two regions have the same size and the provided offset, return what percentage of the images overlap +inline static double +OverlapPercent(image_t::SizeType size, const vec2d_t offset) +{ + double xOverlap = size[0] - std::abs(offset[0]); + double yOverlap = size[1] - std::abs(offset[1]); + if (xOverlap < 0) + return 0; + if (yOverlap < 0) + return 0; + + double xPercent = xOverlap / size[0]; + double yPercent = yOverlap / size[1]; + + double ExpectedOverlap = xPercent * yPercent; + return ExpectedOverlap; +} + +//---------------------------------------------------------------- +// xyz +// +// Constructor function for xyz_t. +// +inline static xyz_t +xyz(const double & r, const double & g, const double & b) +{ + xyz_t rgb; + rgb[0] = r; + rgb[1] = g; + rgb[2] = b; + return rgb; +} + +//---------------------------------------------------------------- +// hsv_to_rgb +// +// Convenience function for converting between HSV/RGB color +// spaces. This is used for colormapping. +// +xyz_t +hsv_to_rgb(const xyz_t & HSV); + +//---------------------------------------------------------------- +// rgb_to_hsv +// +// Convenience function for converting between RGB/HSV color +// spaces. This is used for colormapping. +// +xyz_t +rgb_to_hsv(const xyz_t & RGB); + +//---------------------------------------------------------------- +// pnt2d +// +// Constructor function for pnt2d_t. +// +inline static const pnt2d_t +pnt2d(const double & x, const double & y) +{ + pnt2d_t pt; + pt[0] = x; + pt[1] = y; + return pt; +} + +//---------------------------------------------------------------- +// vec2d +// +// Constructor function for vec2d_t. +// +inline static const vec2d_t +vec2d(const double & x, const double & y) +{ + vec2d_t vc; + vc[0] = x; + vc[1] = y; + return vc; +} + +//---------------------------------------------------------------- +// add +// +// Arithmetics helper function. +// +inline static const pnt2d_t +add(const pnt2d_t & pt, const vec2d_t & vc) +{ + pnt2d_t out; + out[0] = pt[0] + vc[0]; + out[1] = pt[1] + vc[1]; + return out; +} + +//---------------------------------------------------------------- +// add +// +// Arithmetics helper function. +// +inline static const vec2d_t +add(const vec2d_t & a, const vec2d_t & b) +{ + vec2d_t out; + out[0] = a[0] + b[0]; + out[1] = a[1] + b[1]; + return out; +} + +//---------------------------------------------------------------- +// sub +// +// return (a - b) +// +inline static const vec2d_t +sub(const pnt2d_t & a, const pnt2d_t & b) +{ + vec2d_t out; + out[0] = a[0] - b[0]; + out[1] = a[1] - b[1]; + return out; +} + +//---------------------------------------------------------------- +// mul +// +// Arithmetics helper function. +// +inline static const vec2d_t +mul(const double & s, const vec2d_t & vc) +{ + vec2d_t out; + out[0] = s * vc[0]; + out[1] = s * vc[1]; + return out; +} + +//---------------------------------------------------------------- +// neg +// +// Arithmetics helper function. +// +inline static const vec2d_t +neg(const vec2d_t & v) +{ + return vec2d(-v[0], -v[1]); +} + +//---------------------------------------------------------------- +// is_empty_bbox +// +// Test whether a bounding box is empty (MIN > MAX) +// +extern bool +is_empty_bbox(const pnt2d_t & MIN, const pnt2d_t & MAX); + +//---------------------------------------------------------------- +// is_singular_bbox +// +// Test whether a bounding box is singular (MIN == MAX) +// +extern bool +is_singular_bbox(const pnt2d_t & MIN, const pnt2d_t & MAX); + +//---------------------------------------------------------------- +// clamp_bbox +// +// Restrict a bounding box to be within given limits. +// +extern void +clamp_bbox(const pnt2d_t & confines_min, const pnt2d_t & confines_max, pnt2d_t & MIN, pnt2d_t & MAX); + +//---------------------------------------------------------------- +// update_bbox +// +// Expand the bounding box to include a given point. +// +inline static void +update_bbox(pnt2d_t & MIN, pnt2d_t & MAX, const pnt2d_t & pt) +{ + if (MIN[0] > pt[0]) + MIN[0] = pt[0]; + if (MIN[1] > pt[1]) + MIN[1] = pt[1]; + if (MAX[0] < pt[0]) + MAX[0] = pt[0]; + if (MAX[1] < pt[1]) + MAX[1] = pt[1]; +} + + +//---------------------------------------------------------------- +// suspend_itk_multithreading_t +// +class suspend_itk_multithreading_t +{ +public: + suspend_itk_multithreading_t() + : itk_threads_(itk::MultiThreaderBase::GetGlobalDefaultNumberOfThreads()) + , max_threads_(itk::MultiThreaderBase::GetGlobalMaximumNumberOfThreads()) + { + // turn off ITK multithreading: + itk::MultiThreaderBase::SetGlobalDefaultNumberOfThreads(1); + itk::MultiThreaderBase::SetGlobalMaximumNumberOfThreads(1); + } + + ~suspend_itk_multithreading_t() + { + // restore ITK multithreading: + itk::MultiThreaderBase::SetGlobalMaximumNumberOfThreads(max_threads_); + itk::MultiThreaderBase::SetGlobalDefaultNumberOfThreads(itk_threads_); + } + +private: + int itk_threads_; + int max_threads_; +}; + + +//---------------------------------------------------------------- +// cast +// +// Return a copy of image T0 cast to T1. +// +template +typename T1::Pointer +cast(const T0 * a) +{ + typedef typename itk::CastImageFilter cast_t; + typename cast_t::Pointer filter = cast_t::New(); + filter->SetInput(a); + + // put a terminator on the filter: + WRAP(terminator_t terminator(filter)); + filter->Update(); + return filter->GetOutput(); +} + +//---------------------------------------------------------------- +// make_image +// +// make an image of the requested size, filled with some +// value (by default zero): +// +template +typename IMG::Pointer +make_image(const typename IMG::RegionType::SizeType & sz, + const typename IMG::PixelType & fill_value = itk::NumericTraits::Zero) +{ + typename IMG::Pointer image = IMG::New(); + image->SetRegions(sz); + image->Allocate(); + image->FillBuffer(fill_value); + return image; +} + +//---------------------------------------------------------------- +// make_image +// +// make an image of the requested size, filled with some +// value (by default zero): +// +template +typename IMG::Pointer +make_image(const unsigned int width, + const unsigned int height, + const double spacing, + typename IMG::PixelType fill_value = itk::NumericTraits::Zero) +{ + typename IMG::SizeType sz; + sz[0] = width; + sz[1] = height; + + typename IMG::Pointer image = make_image(sz, fill_value); + typename IMG::SpacingType sp; + sp[0] = spacing; + sp[1] = spacing; + image->SetSpacing(sp); + + return image; +} + +//---------------------------------------------------------------- +// make_image +// +// make an image of the requested size, filled with some +// value (by default zero): +// +template +typename IMG::Pointer +make_image(const unsigned int width, + const unsigned int height, + const double spacing, + typename IMG::PointType origin, + typename IMG::PixelType fill_value = itk::NumericTraits::Zero) +{ + typename IMG::Pointer image = make_image(width, height, spacing, fill_value); + image->SetOrigin(origin); + return image; +} + +//---------------------------------------------------------------- +// make_image +// +// make an image of the requested size, filled with some +// value (by default zero): +// +template +typename IMG::Pointer +make_image(const typename IMG::SpacingType & sp, + const typename IMG::RegionType::SizeType & sz, + const typename IMG::PixelType & fill_value = itk::NumericTraits::Zero) +{ + typename IMG::Pointer image = IMG::New(); + image->SetRegions(sz); + image->SetSpacing(sp); + image->Allocate(); + image->FillBuffer(fill_value); + return image; +} + +//---------------------------------------------------------------- +// make_image +// +// make an image of the requested size, filled with some +// value (by default zero): +// +template +typename IMG::Pointer +make_image(const typename IMG::PointType & origin, + const typename IMG::SpacingType & sp, + const typename IMG::RegionType::SizeType & sz, + const typename IMG::PixelType & fill_value = itk::NumericTraits::Zero) +{ + typename IMG::Pointer image = IMG::New(); + image->SetOrigin(origin); + image->SetRegions(sz); + image->SetSpacing(sp); + image->Allocate(); + image->FillBuffer(fill_value); + return image; +} + +//---------------------------------------------------------------- +// add +// +// image arithmetic -- add two images together: +// +template +typename IMG::Pointer +add(const IMG * a, const IMG * b) +{ + typedef typename itk::AddImageFilter sum_t; + + typename sum_t::Pointer sum = sum_t::New(); + sum->SetInput1(a); + sum->SetInput2(b); + + WRAP(terminator_t terminator(sum)); + sum->Update(); + return sum->GetOutput(); +} + +//---------------------------------------------------------------- +// subtract +// +// image arithmetic -- subtract two images: c = a - b +// +template +typename IMG::Pointer +subtract(const IMG * a, const IMG * b) +{ + typedef typename itk::SubtractImageFilter dif_t; + + typename dif_t::Pointer dif = dif_t::New(); + dif->SetInput1(a); + dif->SetInput2(b); + + WRAP(terminator_t terminator(dif)); + dif->Update(); + return dif->GetOutput(); +} + +//---------------------------------------------------------------- +// multiply +// +// image arithmetic -- return an image resulting from +// pixel-wise division a/b: +// +template +typename IMG::Pointer +multiply(const IMG * a, const IMG * b) +{ + typedef typename itk::MultiplyImageFilter mul_t; + + typename mul_t::Pointer mul = mul_t::New(); + mul->SetInput1(a); + mul->SetInput2(b); + + WRAP(terminator_t terminator(mul)); + mul->Update(); + return mul->GetOutput(); +} + +//---------------------------------------------------------------- +// divide +// +// image arithmetic -- return an image resulting from +// pixel-wise division a/b: +// +template +typename IMG::Pointer +divide(const IMG * a, const mask_t * b) +{ + typedef typename itk::DivideImageFilter div_t; + + typename div_t::Pointer div = div_t::New(); + div->SetInput1(a); + div->SetInput2(b); + + WRAP(terminator_t terminator(div)); + div->Update(); + return div->GetOutput(); +} + +//---------------------------------------------------------------- +// pixel_in_mask +// +// Check whether a given pixel falls inside a mask. +// +// NOTE: the mask is assumed to be at higher resolution +// than the image. +// +template +inline static bool +pixel_in_mask(const mask_t * mask, + const mask_t::SizeType & mask_size, + const typename IMG::IndexType & index, + const unsigned int spacing_scale) +{ + mask_t::IndexType mask_index(index); + mask_index[0] *= spacing_scale; + mask_index[1] *= spacing_scale; + + if (mask_index[0] >= 0 && mask_index[1] >= 0 && image_size_value_t(mask_index[0]) < mask_size[0] && + image_size_value_t(mask_index[1]) < mask_size[1]) + { + // check the mask: + return (mask == NULL) ? true : (mask->GetPixel(mask_index) != 0); + } + + // pixel falls outside the mask image: + return false; +} + +//---------------------------------------------------------------- +// pixel_in_mask +// +// Check whether a given physical point falls inside a mask. +// +template +inline static bool +pixel_in_mask(const T * mask, const typename T::PointType & physical_point) +{ + if (mask == NULL) + return true; + + typename T::IndexType mask_pixel_index; + if (mask->TransformPhysicalPointToIndex(physical_point, mask_pixel_index)) + { + // let the mask decide: + return mask->GetPixel(mask_pixel_index) != 0; + } + + // point falls outside the mask image: + return false; +} + +//---------------------------------------------------------------- +// pixel_in_mask +// +// Check whether a given pixel falls inside a mask. +// +template +inline static bool +pixel_in_mask(const T * image, + const itk::Image * mask, + const typename T::IndexType & image_pixel_index) +{ + if (mask == NULL) + { + return true; + } + + typename T::PointType physical_point; + image->TransformIndexToPhysicalPoint(image_pixel_index, physical_point); + + typedef itk::Image mask_t; + return pixel_in_mask(mask, physical_point); +} + +//---------------------------------------------------------------- +// image_min_max +// +// Find MIN/MAX pixel values, return mean pixel value (average): +// +template +double +image_min_max(const T * a, + typename T::PixelType & MIN, + typename T::PixelType & MAX, + const itk::Image * mask = NULL) +{ + WRAP(itk_terminator_t terminator("image_min_max")); + + typedef typename T::PixelType pixel_t; + typedef typename itk::ImageRegionConstIteratorWithIndex iter_t; + + MIN = std::numeric_limits::max(); + MAX = -MIN; + + double mean = 0.0; + double counter = 0.0; + iter_t iter(a, a->GetLargestPossibleRegion()); + for (iter.GoToBegin(); !iter.IsAtEnd(); ++iter) + { + // make sure there hasn't been an interrupt: + WRAP(terminator.terminate_on_request()); + + if (!pixel_in_mask(a, mask, iter.GetIndex())) + { + continue; + } + + pixel_t v = iter.Get(); + MIN = std::min(MIN, v); + MAX = std::max(MAX, v); + + mean += double(v); + counter += 1.0; + } + + mean /= counter; + return mean; +} + +//---------------------------------------------------------------- +// remap_min_max_inplace +// +// Rescale image intensities in-place +// +template +bool +remap_min_max_inplace(T * a, double MIN, double MAX, double new_min, double new_max) +{ + WRAP(itk_terminator_t terminator("remap_min_max_inplace")); + + typedef typename T::PixelType pixel_t; + typedef typename itk::ImageRegionIterator iter_t; + + // rescale the intensities: + double rng = MAX - MIN; + if (rng == 0.0) + return false; + + double new_rng = new_max - new_min; + + iter_t iter(a, a->GetLargestPossibleRegion()); + for (iter.GoToBegin(); !iter.IsAtEnd(); ++iter) + { + // make sure there hasn't been an interrupt: + WRAP(terminator.terminate_on_request()); + + double t = (iter.Get() - MIN) / rng; + iter.Set(pixel_t(new_min + t * new_rng)); + } + + return true; +} + +//---------------------------------------------------------------- +// remap_min_max_inplace +// +// Rescale image intensities in-place +// +template +bool +remap_min_max_inplace(T * a, typename T::PixelType new_min = 0, typename T::PixelType new_max = 255) +{ + WRAP(itk_terminator_t terminator("remap_min_max_inplace")); + + typedef typename T::PixelType pixel_t; + typedef typename itk::ImageRegionIterator iter_t; + + double MIN = std::numeric_limits::max(); + double MAX = -MIN; + + iter_t iter(a, a->GetLargestPossibleRegion()); + for (iter.GoToBegin(); !iter.IsAtEnd(); ++iter) + { + // make sure there hasn't been an interrupt: + WRAP(terminator.terminate_on_request()); + + double v = iter.Get(); + MIN = std::min(MIN, v); + MAX = std::max(MAX, v); + } + + return remap_min_max_inplace(a, MIN, MAX, new_min, new_max); +} + +//---------------------------------------------------------------- +// remap_min_max +// +// Return a copy of the image with rescaled pixel intensities +// +template +typename T::Pointer +remap_min_max(const T * a, typename T::PixelType new_min = 0, typename T::PixelType new_max = 255) +{ + WRAP(itk_terminator_t terminator("remap_min_max")); + + typedef typename T::RegionType rn_t; + typedef typename T::SizeType sz_t; + rn_t rn = a->GetLargestPossibleRegion(); + sz_t sz = rn.GetSize(); + + typename T::Pointer b = T::New(); + b->SetRegions(sz); + b->SetSpacing(a->GetSpacing()); + b->Allocate(); + + double MIN = std::numeric_limits::max(); + double MAX = -MIN; + + typename T::IndexType ix_end; + ix_end[0] = sz[0]; + ix_end[1] = sz[1]; + + typename T::IndexType ix; + for (ix[1] = 0; ix[1] < ix_end[1]; ++ix[1]) + { + // make sure there hasn't been an interrupt: + WRAP(terminator.terminate_on_request()); + + for (ix[0] = 0; ix[0] < ix_end[0]; ++ix[0]) + { + double v = a->GetPixel(ix); + MIN = std::min(MIN, v); + MAX = std::max(MAX, v); + } + } + + // rescale the intensities: + double rng = MAX - MIN; + if (rng == 0.0) + { + b->FillBuffer(itk::NumericTraits::Zero); + return b; + } + + double new_rng = new_max - new_min; + + for (ix[1] = 0; ix[1] < ix_end[1]; ++ix[1]) + { + // make sure there hasn't been an interrupt: + WRAP(terminator.terminate_on_request()); + + for (ix[0] = 0; ix[0] < ix_end[0]; ++ix[0]) + { + double v = a->GetPixel(ix); + double t = (v - MIN) / rng; + b->SetPixel(ix, typename T::PixelType(new_min + t * new_rng)); + } + } + + return b; +} + +//---------------------------------------------------------------- +// invert +// +// Invert pixel intensities in-place. +// +template +void +invert(typename T::Pointer & a) +{ + WRAP(itk_terminator_t terminator("invert")); + + typedef typename T::PixelType pixel_t; + typedef typename itk::ImageRegionIterator iter_t; + + pixel_t MIN = std::numeric_limits::max(); + pixel_t MAX = -MIN; + + iter_t iter(a, a->GetLargestPossibleRegion()); + for (iter.GoToBegin(); !iter.IsAtEnd(); ++iter) + { + // make sure there hasn't been an interrupt: + WRAP(terminator.terminate_on_request()); + + pixel_t v = iter.Get(); + MIN = std::min(MIN, v); + MAX = std::max(MAX, v); + } + + pixel_t rng = MAX - MIN; + if (rng == pixel_t(0)) + return; + + // rescale the value: + pixel_t new_min = MAX; + pixel_t new_max = MIN; + pixel_t new_rng = new_max - new_min; + + for (iter.GoToBegin(); !iter.IsAtEnd(); ++iter) + { + // make sure there hasn't been an interrupt: + WRAP(terminator.terminate_on_request()); + + pixel_t t = (iter.Get() - MIN) / rng; + iter.Set(new_min + t * new_rng); + } +} + +//---------------------------------------------------------------- +// threshold +// +// Return a copy of the image with pixels above and below +// a given threshold remapped to new values. +// +template +typename T::Pointer +threshold(const T * a, + typename T::PixelType MIN, + typename T::PixelType MAX, + typename T::PixelType new_min, + typename T::PixelType new_max) +{ + WRAP(itk_terminator_t terminator("threshold")); + + typename T::RegionType::SizeType sz = a->GetLargestPossibleRegion().GetSize(); + typename T::Pointer b = T::New(); + b->SetRegions(sz); + b->SetSpacing(a->GetSpacing()); + b->Allocate(); + + typedef typename itk::ImageRegionConstIteratorWithIndex itex_t; + itex_t itex(a, sz); + for (itex.GoToBegin(); !itex.IsAtEnd(); ++itex) + { + // make sure there hasn't been an interrupt: + WRAP(terminator.terminate_on_request()); + + typename T::PixelType v = itex.Get(); + if (v < MIN) + v = new_min; + else if (v > MAX) + v = new_max; + + b->SetPixel(itex.GetIndex(), v); + } + + return b; +} + +//---------------------------------------------------------------- +// clip_min_max +// +// Clip the pixel intensities to the specified MIN/MAX limits. +// +template +typename T::Pointer +clip_min_max(const T * a, typename T::PixelType MIN = 0, typename T::PixelType MAX = 255) +{ + return threshold(a, MIN, MAX, MIN, MAX); +} + +//---------------------------------------------------------------- +// calc_padding +// +// Given two 2D images, return the pixel bounding box encompasing both. +// +template +typename T::SizeType +calc_padding(const T * a, const T * b) +{ + typedef typename T::SizeType sz_t; + sz_t sa = a->GetLargestPossibleRegion().GetSize(); + const sz_t sb = b->GetLargestPossibleRegion().GetSize(); + + const unsigned int d = T::GetImageDimension(); + for (unsigned int i = 0; i < d; i++) + { + sa[i] = std::max(sa[i], sb[i]); + } + + return sa; +} + +//---------------------------------------------------------------- +// pad +// +// Pad an image to a given size (in pixels), return the padded image. +// The image is padded with zeros. +// +template +typename T::Pointer +pad(const T * a, const typename T::SizeType & size) +{ + typedef typename T::RegionType rn_t; + typedef typename T::SizeType sz_t; + rn_t r = a->GetLargestPossibleRegion(); + sz_t z = r.GetSize(); + + WRAP(itk_terminator_t terminator("pad")); + + typename T::Pointer b = T::New(); + b->SetRegions(size); + b->SetSpacing(a->GetSpacing()); + b->Allocate(); + + typename T::PixelType zero = itk::NumericTraits::Zero; + typename T::IndexType ix; + for (ix[1] = 0; (image_size_value_t)(ix[1]) < z[1]; ++ix[1]) + { + for (ix[0] = 0; (image_size_value_t)(ix[0]) < z[0]; ++ix[0]) + { + b->SetPixel(ix, a->GetPixel(ix)); + } + + for (ix[0] = z[0]; (image_size_value_t)(ix[0]) < size[0]; ++ix[0]) + { + b->SetPixel(ix, zero); + } + } + + for (ix[1] = z[1]; (image_size_value_t)(ix[1]) < size[1]; ++ix[1]) + { + for (ix[0] = 0; (image_size_value_t)(ix[0]) < size[0]; ++ix[0]) + { + b->SetPixel(ix, zero); + } + } + + return b; +} + +//---------------------------------------------------------------- +// crop +// +// Return a copy of the cropped image region +// +template +typename T::Pointer +crop(const T * image, const typename T::IndexType & MIN, const typename T::IndexType & MAX) +{ + WRAP(itk_terminator_t terminator("crop")); + typedef typename T::RegionType::SizeType sz_t; + + sz_t img_sz = image->GetLargestPossibleRegion().GetSize(); + sz_t reg_sz; + const unsigned int d = T::GetImageDimension(); + for (unsigned int i = 0; i < d; i++) + { + reg_sz[i] = std::min((unsigned int)(MAX[i] - MIN[i] + 1), (unsigned int)(img_sz[i] - MIN[i])); + } + + typename T::RegionType region; + region.SetIndex(MIN); + region.SetSize(reg_sz); + + typename T::Pointer out = make_image(reg_sz); + + typedef itk::ImageRegionIteratorWithIndex itex_t; + itex_t itex(out, out->GetLargestPossibleRegion()); + for (itex.GoToBegin(); !itex.IsAtEnd(); ++itex) + { + // make sure there hasn't been an interrupt: + WRAP(terminator.terminate_on_request()); + + typename T::IndexType ix1 = itex.GetIndex(); + typename T::IndexType ix0; + for (unsigned int i = 0; i < d; i++) + { + ix0[i] = ix1[i] + MIN[i]; + } + + out->SetPixel(ix1, image->GetPixel(ix0)); + } + + // FIXME: do I really want this? + { + typename T::PointType origin; + image->TransformIndexToPhysicalPoint(region.GetIndex(), origin); + out->SetOrigin(origin); + } + out->SetSpacing(image->GetSpacing()); + + return out; +} + +//---------------------------------------------------------------- +// smooth +// +// Return a copy of the image smothed with a Gaussian kernel. +// +template +typename T::Pointer +smooth(const T * in, const double sigma, const double maximum_error = 0.1) +{ + typedef typename itk::DiscreteGaussianImageFilter smoother_t; + typename smoother_t::Pointer smoother = smoother_t::New(); + + // FIXME: itk::SimpleFilterWatcher w(smoother.GetPointer(), "smoother"); + + smoother->SetInput(in); + smoother->SetUseImageSpacing(false); + smoother->SetVariance(sigma * sigma); + smoother->SetMaximumError(maximum_error); + + WRAP(terminator_t terminator(smoother)); + smoother->Update(); + return smoother->GetOutput(); +} + +//---------------------------------------------------------------- +// shrink +// +// Return a scaled down copy of the image. +// +template +typename T::Pointer +shrink(const T * in, + const unsigned int & shrink_factor, + const double maximum_error = 0.1, + const bool & antialias = true) +{ + // Create caster, smoother and shrinker filters + typedef itk::Image flt_img_t; + typename flt_img_t::Pointer image = cast(in); + + if (antialias) + { + double variance = double(shrink_factor) / 2.0; + double sigma = sqrt(variance); + image = ::smooth(image, sigma, maximum_error); + } + + typedef itk::ShrinkImageFilter shrinker_t; + typename shrinker_t::Pointer shrinker = shrinker_t::New(); + + // FIXME: itk::SimpleFilterWatcher w(shrinker.GetPointer(), "shrinker"); + + shrinker->SetInput(image); + shrinker->SetShrinkFactors(shrink_factor); + + WRAP(terminator_t terminator(shrinker)); + shrinker->Update(); + image = shrinker->GetOutput(); + return cast(image); +} + +//--------------------------------------------------------------- +// Utility functions for assembling images +// James A, used to reduce repetition in the library +//--------------------------------------------------------------- + +class AssembleUtil +{ +public: + //------- + // Return only the images in the array that are valid + // James A + //------- + template + static std::vector + PruneInvalidTiles(const std::vector & omit, const std::vector image) + { + unsigned int num_images = image.size(); + std::vector * ValidImages = new std::vector(); + ValidImages->reserve(num_images); + for (unsigned int k = 0; k < num_images; k++) + { + if (!(omit[k] || (image[k].GetPointer() == NULL))) + { + ValidImages->push_back(image[k]); + } + } + + return *ValidImages; + } + + // Return a sorted list of indicies according smallest point to largest point + template + static std::vector + GetSortedIndicies( // mosaic space tile bounding boxes: + const std::vector MIN, + const std::vector omit = std::vector(0)) + { + std::vector PotentialTiles; + PotentialTiles.reserve(MIN.size()); + + for (unsigned int i = 0; i < MIN.size(); i++) + { + if (omit.size() > i) + { + if (false == omit[i]) + PotentialTiles.push_back(i); + } + else + { + PotentialTiles.push_back(i); + } + } + + for (unsigned int iK = 0; iK < PotentialTiles.size(); iK++) + { + unsigned int iImageA = PotentialTiles[iK]; + for (unsigned int iJ = iK + 1; iJ < PotentialTiles.size(); iJ++) + { + unsigned int iImageB = PotentialTiles[iJ]; + + if (MIN[iImageA][1] > MIN[iImageB][1]) + { + PotentialTiles[iK] = iImageB; + PotentialTiles[iJ] = iImageA; + iK = iK - 1; + break; + } + else if (MIN[iImageA][1] < MIN[iImageB][1]) + { + continue; + } + else + { + if (MIN[iImageA][0] > MIN[iImageB][0]) + { + PotentialTiles[iK] = iImageB; + PotentialTiles[iJ] = iImageA; + iImageA = iImageB; + iK = iK - 1; + break; + } + else if (MIN[iImageA][0] < MIN[iImageB][0]) + { + continue; + } + } + } + } + /* + #ifdef _DEBUG + + for (unsigned int i = 0; i < MIN.size(); i++) + { + unsigned int iImageA = PotentialTiles[i]; + cout << "x: " << MIN[iImageA][0] << " y:" << MIN[iImageA][1] << endl; + } + + #endif + */ + return PotentialTiles; + } + + template + static void + GetEligibleIndiciesForSortedColumn(const pnt_t point, + // mosaic space tile bounding boxes: + const std::vector MIN, + const std::vector MAX, + unsigned int & iStart, + unsigned int & iEnd) + { + unsigned int iNewStart = 0; + unsigned int iNewEnd = MIN.size() - 1; + + if ((int)iStart < 0) + iStart = 0; + if (iStart > MIN.size() - 1) + iStart = 0; + + if ((int)iEnd < 0) + iEnd = 0; + if (iEnd > MIN.size() - 1) + iEnd = 0; + + for (unsigned int iK = iStart; iK < MIN.size(); iK++) + { + if (MIN[iK][1] - 0.1 <= point[1] && MAX[iK][1] + 0.1 >= point[1]) + { + iNewStart = iK; + break; + } + } + + if (iStart > iEnd) + iEnd = iStart; + + for (unsigned int iK = iEnd; iK < MIN.size(); iK++) + { + if (MIN[iK][1] - 0.1 <= point[1] && MAX[iK][1] + 0.1 >= point[1]) + { + iNewEnd = iK; + } + else if (MIN[iK][1] - 0.1 > point[1]) + { + break; + } + } + + iStart = iNewStart; + iEnd = iNewEnd; + + // cout << "Y: " << point[1] << " s: " << iStart << " e: " << iEnd << endl; + } + + + //---------------- + // Allocates and returns a pointer to a vector of indicies that can participate in this column, sorted by the minX. + // James A + //---------------- + template + static std::vector + GetSortedTilesImagesForColumn(const pnt_t point, + // mosaic space tile bounding boxes: + const std::vector MIN, + const std::vector MAX) + { + std::vector PotentialTiles; + PotentialTiles.reserve(MIN.size()); + + for (unsigned int iK = 0; iK < MIN.size(); iK++) + { + if (!(MIN[iK][1] > point[1] || MAX[iK][1] < point[1])) + { + if (PotentialTiles.size() == 0) + PotentialTiles.push_back(iK); + else + { + for (int iInsert = PotentialTiles.size() - 1; iInsert >= 0; iInsert--) + { + if (MIN[(PotentialTiles)[iInsert]][0] > MAX[iK][0]) + { + PotentialTiles.insert(PotentialTiles.begin() + iInsert + 1, iK); + } + else if (iInsert == 0) + { + PotentialTiles.insert(PotentialTiles.begin(), iK); + } + } + } + } + } + + // #ifdef DEBUG + /* for (unsigned int i = 0; i < PotentialTiles.size(); i++) + { + unsigned int iImageA = PotentialTiles[i]; + cout << MIN[iImageA][0] << " x " << MIN[iImageA][1] << endl; + } + */ + // #endif + + return PotentialTiles; + } +}; + +template +std::vector> +DivideROIAmongThreads(typename T::ConstPointer fi, + typename T::RegionType roi, + int num_Threads = std::thread::hardware_concurrency()) +{ + typedef typename itk::ImageRegionConstIteratorWithIndex itex_t; + typedef typename T::IndexType index_t; + + // Divvy up the fixed image roi for each thread to work over... + // set an ROI for each thread to work over, prefer x as the largest dimension + typename T::SizeType fi_size = roi.GetSize(); + + std::vector list_fi_thread_itex; + list_fi_thread_itex.reserve(num_Threads); + + int iMaxDim = fi_size[0] >= fi_size[1] ? 0 : 1; + int iMinDim = iMaxDim == 0 ? 1 : 0; + + double Width = fi_size[iMaxDim] / num_Threads; + + index_t fi_min = roi.GetIndex(); + index_t fi_max = fi_min; + + fi_max[0] += fi_size[0]; + fi_max[1] += fi_size[1]; + + std::vector listThreadStartIndex(num_Threads + 1); + + listThreadStartIndex[0] = fi_min; + listThreadStartIndex[num_Threads][iMaxDim] = fi_max[iMaxDim]; + listThreadStartIndex[num_Threads][iMinDim] = fi_min[iMinDim]; + + for (int iThread = 1; iThread < num_Threads; iThread++) + { + listThreadStartIndex[iThread][iMaxDim] = fi_min[iMaxDim] + (Width * iThread); + listThreadStartIndex[iThread][iMinDim] = fi_min[iMinDim]; + + // cout << iThread << " fi start index, " << "X0: " << listThreadStartIndex[iThread][0] << " Y0: " << + // listThreadStartIndex[iThread][1] << endl; + } + + for (int iThread = 0; iThread < num_Threads; iThread++) + { + index_t thread_min_index = listThreadStartIndex[iThread]; + index_t thread_max_index = listThreadStartIndex[iThread + 1]; + thread_max_index[iMinDim] = fi_max[iMinDim]; + + if (iThread == num_Threads - 1) + { + thread_max_index = fi_max; + } + + typename T::SizeType roi_size; + roi_size[0] = (thread_max_index[0] - thread_min_index[0]); + roi_size[1] = (thread_max_index[1] - thread_min_index[1]); + + // cout << iThread << " fixed roi, " << "X0: " << thread_min_index[0] << " Y0: " << thread_min_index[1] << " W: " << + // roi_size[0] << " H: " << roi_size[1] << endl; + + image_t::RegionType roi; + roi.SetIndex(thread_min_index); + roi.SetSize(roi_size); + // list_fi_thread_roi.push_back(roi); + + itex_t itex(fi, roi); + list_fi_thread_itex.push_back(itex); + } + + return list_fi_thread_itex; +} + +template +std::vector> +DivideROIAmongThreads(typename T::Pointer fi, + typename T::RegionType roi, + int num_Threads = std::thread::hardware_concurrency()) +{ + typedef typename itk::ImageRegionIteratorWithIndex itex_t; + typedef typename T::IndexType index_t; + + // Divvy up the fixed image roi for each thread to work over... + // set an ROI for each thread to work over, prefer x as the largest dimension + typename T::SizeType fi_size = roi.GetSize(); + + std::vector list_fi_thread_itex; + list_fi_thread_itex.reserve(num_Threads); + + int iMaxDim = fi_size[0] >= fi_size[1] ? 0 : 1; + int iMinDim = iMaxDim == 0 ? 1 : 0; + + double Width = fi_size[iMaxDim] / num_Threads; + + index_t fi_min = roi.GetIndex(); + index_t fi_max = fi_min; + + fi_max[0] += fi_size[0]; + fi_max[1] += fi_size[1]; + + std::vector listThreadStartIndex(num_Threads + 1); + + listThreadStartIndex[0] = fi_min; + listThreadStartIndex[num_Threads][iMaxDim] = fi_max[iMaxDim]; + listThreadStartIndex[num_Threads][iMinDim] = fi_min[iMinDim]; + + for (int iThread = 1; iThread < num_Threads; iThread++) + { + listThreadStartIndex[iThread][iMaxDim] = fi_min[iMaxDim] + (Width * iThread); + listThreadStartIndex[iThread][iMinDim] = fi_min[iMinDim]; + + // cout << iThread << " fi start index, " << "X0: " << listThreadStartIndex[iThread][0] << " Y0: " << + // listThreadStartIndex[iThread][1] << endl; + } + + for (int iThread = 0; iThread < num_Threads; iThread++) + { + index_t thread_min_index = listThreadStartIndex[iThread]; + index_t thread_max_index = listThreadStartIndex[iThread + 1]; + thread_max_index[iMinDim] = fi_max[iMinDim]; + + if (iThread == num_Threads - 1) + { + thread_max_index = fi_max; + } + + typename T::SizeType roi_size; + roi_size[0] = (thread_max_index[0] - thread_min_index[0]); + roi_size[1] = (thread_max_index[1] - thread_min_index[1]); + + // cout << iThread << " fixed roi, " << "X0: " << thread_min_index[0] << " Y0: " << thread_min_index[1] << " W: " << + // roi_size[0] << " H: " << roi_size[1] << endl; + + image_t::RegionType roi; + roi.SetIndex(thread_min_index); + roi.SetSize(roi_size); + // list_fi_thread_roi.push_back(roi); + + itex_t itex(fi, roi); + list_fi_thread_itex.push_back(itex); + } + + return list_fi_thread_itex; +} + +//---------------------------------------------------------------- +// load +// +// Convenience functions for loading an ITK image. +// +template +typename T::Pointer +load(const char * filename, bool blab = true) +{ + typedef typename itk::ImageFileReader reader_t; + typename reader_t::Pointer reader = reader_t::New(); + + // FIXME: itk::SimpleFilterWatcher w(reader.GetPointer(), "reader"); + + try + { + reader->SetFileName(filename); + if (blab) + cout << "loading " << filename << endl; + + WRAP(terminator_t terminator(reader)); + + reader->Update(); + return reader->GetOutput(); + } + catch (itk::ExceptionObject & e) + { + cout << "Exception loading " << filename << endl; + cout << e.GetDescription() << endl; + return NULL; + } +} + +//---------------------------------------------------------------- +// save +// +// Convenience functions for saving an ITK image. +// +template +void +save(const T * image, const char * filename, bool blab = true) +{ + typedef typename itk::ImageFileWriter writer_t; + typename writer_t::Pointer writer = writer_t::New(); + writer->SetInput(image); + + if (blab) + cout << "saving " << filename << endl; + writer->SetFileName(filename); + writer->Update(); +} + +//---------------------------------------------------------------- +// save_image_tile +// +// Convenience functions for saving out a section of an ITK image. +// +template +void +save_image_tile(T * image, + const char * filename, + const unsigned int x, + const unsigned int y, + const unsigned int source_width, + const unsigned int source_height, + const unsigned int tile_width, + const unsigned int tile_height, + bool blab = true) +{ + typename itk::PasteImageFilter::Pointer pasteImageFilter = itk::PasteImageFilter::New(); + + pasteImageFilter->SetSourceImage(image); + + typename T::RegionType mosaicRegion = image->GetBufferedRegion(); + typename T::SizeType mosaicSize = mosaicRegion.GetSize(); + + // Setup the region we're interested in pasting to a new image. + typename T::IndexType sourceIndex; + sourceIndex[0] = x; + sourceIndex[1] = y; + + typename T::SizeType sourceSize; + sourceSize[0] = source_width; + sourceSize[1] = source_height; + + typename T::RegionType sourceRegion; + sourceRegion.SetIndex(sourceIndex); + sourceRegion.SetSize(sourceSize); + pasteImageFilter->SetSourceRegion(sourceRegion); + + // Create a new image to paste the section onto. + typename T::Pointer destImage = T::New(); + pasteImageFilter->SetDestinationImage(destImage); + + typename T::IndexType destIndex; + destIndex[0] = 0; + destIndex[1] = 0; + + typename T::SizeType destSize; + destSize[0] = tile_width; + destSize[1] = tile_height; + + typename T::RegionType sectionRegion; + sectionRegion.SetIndex(destIndex); + sectionRegion.SetSize(destSize); + destImage->SetRegions(sectionRegion); + destImage->Allocate(); + destImage->FillBuffer(0); + + pasteImageFilter->SetDestinationIndex(destIndex); + + typename T::Pointer partialImage = pasteImageFilter->GetOutput(); + + typedef typename itk::ImageFileWriter writer_t; + typename writer_t::Pointer writer = writer_t::New(); + writer->SetInput(partialImage); + + if (blab) + { + cout << "saving " << filename << endl; + } + + writer->SetFileName(filename); + writer->Update(); +} + +//---------------------------------------------------------------- +// save_as_tiles +// +// Convenience functions for saving out series of tiles as ITK images. Also +// writes out an xml file explaining the position of these files. +// +template +bool +save_as_tiles(T * image, + const char * prefix, + const char * extension, + unsigned int w, + unsigned int h, + const double downsample, + bool save_image = true, + bool blab = true) +{ + the_text_t fileName = prefix; + the_text_t xmlFileName = fileName; + xmlFileName += ".xml"; + std::ofstream xmlOut(xmlFileName); + + if (!xmlOut.is_open()) + { + cout << "Error opening xml file for writing: " << xmlFileName << endl; + return false; + } + + typename T::RegionType mosaicRegion = image->GetBufferedRegion(); + typename T::SizeType mosaicSize = mosaicRegion.GetSize(); + if (w == std::numeric_limits::max()) + { + w = mosaicSize[0]; + } + + if (h == std::numeric_limits::max()) + { + h = mosaicSize[1]; + } + + unsigned int numTilesWide = (mosaicSize[0] + (w - 1)) / w; + unsigned int numTilesTall = (mosaicSize[1] + (h - 1)) / h; + + // extract just the name portion + size_t num_forward = fileName.contains('/'); + size_t num_backward = fileName.contains('\\'); + char slash = (num_forward > num_backward) ? '/' : '\\'; + the_text_t name_part = fileName.reverse().cut(slash, 0, 0).reverse() + "_"; + + xmlOut << "" << endl; + xmlOut << "" << endl; + + for (unsigned int x = 0, xid = 0; x < mosaicSize[0]; x += w, xid++) + { + for (unsigned int y = 0, yid = 0; y < mosaicSize[1]; y += h, yid++) + { + the_text_t fn_partialSave = prefix; + fn_partialSave += "_X"; + fn_partialSave += the_text_t::number(xid, 3, '0'); + fn_partialSave += "_Y"; + fn_partialSave += the_text_t::number(yid, 3, '0'); + fn_partialSave += extension; + + unsigned int sectionWidth = std::min(w, mosaicSize[0] - x); + unsigned int sectionHeight = std::min(h, mosaicSize[1] - y); + if (save_image) + { + save_image_tile(image, fn_partialSave, x, y, sectionWidth, sectionHeight, w, h); + } + } + } + + xmlOut.close(); + return true; +} + +//---------------------------------------------------------------- +// save_tile_xml +// +// Exports the same xml file as save_as_tiles. But without actually +// saving the tiles out. +// +template +bool +save_tile_xml(const char * prefix, + const char * extension, + unsigned int w, + unsigned int h, + unsigned int full_width, + unsigned int full_height, + const double downsample, + bool save_image = true, + bool blab = true) +{ + the_text_t fileName = prefix; + the_text_t xmlFileName = fileName; + xmlFileName += ".xml"; + std::ofstream xmlOut(xmlFileName); + + if (!xmlOut.is_open()) + { + cout << "Error opening xml file for writing: " << xmlFileName << endl; + return false; + } + + if (w == std::numeric_limits::max()) + { + w = full_width; + } + + if (h == std::numeric_limits::max()) + { + h = full_height; + } + + unsigned int numTilesWide = (full_width + (w - 1)) / w; + unsigned int numTilesTall = (full_height + (h - 1)) / h; + + // extract just the name portion + size_t num_forward = fileName.contains('/'); + size_t num_backward = fileName.contains('\\'); + char slash = (num_forward > num_backward) ? '/' : '\\'; + the_text_t name_part = fileName.reverse().cut(slash, 0, 0).reverse() + "_"; + + xmlOut << "" << endl; + xmlOut << "" << endl; + + for (unsigned int x = 0, xid = 0; x < full_width; x += w, xid++) + { + for (unsigned int y = 0, yid = 0; y < full_height; y += h, yid++) + { + the_text_t fn_partialSave = prefix; + fn_partialSave += "_X"; + fn_partialSave += the_text_t::number(xid, 3, '0'); + fn_partialSave += "_Y"; + fn_partialSave += the_text_t::number(yid, 3, '0'); + fn_partialSave += extension; + } + } + + xmlOut.close(); + return true; +} + +//---------------------------------------------------------------- +// calc_area +// +// Calculate the area covered by a given number of pixels +// at the specified pixels spacing. +// +inline double +calc_area(const itk::Vector & spacing, const unsigned long int pixels) +{ + double pixel_area = spacing[0] * spacing[1]; + double area = pixel_area * double(pixels); + return area; +} + +//---------------------------------------------------------------- +// calc_area +// +// Calculate image area under the mask. +// +template +double +calc_area(const T * image, const mask_t * mask) +{ + WRAP(itk_terminator_t terminator("calc_area")); + unsigned long int pixels = 0; + + typedef itk::ImageRegionConstIteratorWithIndex itex_t; + itex_t itex(image, image->GetLargestPossibleRegion()); + + for (itex.GoToBegin(); !itex.IsAtEnd(); ++itex) + { + // make sure there hasn't been an interrupt: + WRAP(terminator.terminate_on_request()); + + if (pixel_in_mask(image, mask, itex.GetIndex())) + { + pixels++; + } + } + + return calc_area(image->GetSpacing(), pixels); +} + +//---------------------------------------------------------------- +// get_area +// +template +double +get_area(const T * image) +{ + const typename T::RegionType::SizeType & sz = image->GetLargestPossibleRegion().GetSize(); + const unsigned int num_pixels = sz[0] * sz[1]; + return calc_area(image->GetSpacing(), num_pixels); +} + +//---------------------------------------------------------------- +// calc_image_bbox +// +// Calculate the bounding box for a given image, +// expressed in image space. +// +template +void +calc_image_bbox(typename IMG::ConstPointer image, pnt2d_t & bbox_min, pnt2d_t & bbox_max) +{ + typename IMG::SizeType sz = image->GetLargestPossibleRegion().GetSize(); + typename IMG::SpacingType sp = image->GetSpacing(); + bbox_min = image->GetOrigin(); + bbox_max[0] = bbox_min[0] + sp[0] * double(sz[0]); + bbox_max[1] = bbox_min[1] + sp[1] * double(sz[1]); +} + +//---------------------------------------------------------------- +// eval_metric +// +// A wrapper function for evaluating an image registration metric for +// two images (one fixed, one moving) in the overlap region defined +// by the fixed image to moving image transform and image masks. +// +template +double +eval_metric(const base_transform_t * fi_to_mi, + const typename metric_t::FixedImageType * fi, + const typename metric_t::MovingImageType * mi, + const mask_t * fi_mask = NULL, + const mask_t * mi_mask = NULL) +{ + typename metric_t::Pointer metric = metric_t::New(); + metric->SetFixedImage(fi); + metric->SetMovingImage(mi); + metric->SetTransform(const_cast(fi_to_mi)); + metric->SetInterpolator(interpolator_t::New()); + + // Instead of iterating over the whole fixed image, find the + // bounding box of the overlapping region of the fixed and + // moving images, clip it to the fixed image bounding box, and iterate + // over that -- this will produce a dramatic speedup in situations + // where the fixed image is large and the moving image is small: + typedef typename metric_t::FixedImageType fi_image_t; + typename fi_image_t::RegionType fi_roi = fi->GetLargestPossibleRegion(); + + // find the bounding box of the moving image in the space of the fixed image: + pnt2d_t mi_min; + pnt2d_t mi_max; + if (calc_tile_mosaic_bbox(fi_to_mi, mi, mi_min, mi_max, 16)) + { + // find the bounding box of the fixed image: + pnt2d_t fi_min; + pnt2d_t fi_max; + calc_image_bbox(fi, fi_min, fi_max); + + // clip the bounding box to the bounding box of the fixed image: + clamp_bbox(fi_min, fi_max, mi_min, mi_max); + + // reset the region of interest: + typename fi_image_t::IndexType roi_index[2]; + if (fi->TransformPhysicalPointToIndex(mi_min, roi_index[0]) && + fi->TransformPhysicalPointToIndex(mi_max, roi_index[1])) + { + typename fi_image_t::SizeType roi_size; + roi_size[0] = roi_index[1][0] - roi_index[0][0] + 1; + roi_size[1] = roi_index[1][1] - roi_index[0][1] + 1; + fi_roi.SetIndex(roi_index[0]); + fi_roi.SetSize(roi_size); + } + } + + metric->SetFixedImageRegion(fi_roi); + + if (fi_mask != NULL) + { + metric->SetFixedImageMask(mask_so(fi_mask)); + } + + if (mi_mask != NULL) + { + metric->SetMovingImageMask(mask_so(mi_mask)); + } + + metric->Initialize(); + + double measure; + try + { + measure = metric->GetValue(fi_to_mi->GetParameters()); + } + catch (itk::ExceptionObject & exception) + { + cerr << "image metric threw an exception:" << endl << exception << endl; + measure = std::numeric_limits::max(); + } + + return measure; +} + + +//---------------------------------------------------------------- +// my_metric +// +// Calculate a normalized cross correlation metric between two +// masked images, calculate the area of overlap. +// +template +double +my_metric_mt(double & area, + + const TImage * fi, + const TImage * mi, + + base_transform_t::ConstPointer fi_to_mi, + const mask_t * fi_mask, + const mask_t * mi_mask, + + const TInterpolator * mi_interpolator, + int num_Threads = std::thread::hardware_concurrency()) +{ + // return my_metric(area, fi, mi, fi_to_mi, fi_mask, mi_mask, mi_interpolator); + + WRAP(itk_terminator_t terminator("my_metric")); + typedef typename itk::ImageRegionConstIteratorWithIndex itex_t; + typedef typename TImage::IndexType index_t; + typedef typename TImage::PointType point_t; + typedef typename TImage::PixelType pixel_t; + + // Instead of iterating over the whole fixed image, find the + // bounding box of the overlapping region of the fixed and + // moving images, clip it to the fixed image bounding box, and iterate + // over that -- this will produce a dramatic speedup in situations + // where the fixed image is large and the moving image is small: + typename TImage::RegionType fi_roi = fi->GetLargestPossibleRegion(); + + // std::vector list_fi_thread_roi; + // list_fi_thread_roi.reserve(num_Threads); + + std::vector list_fi_thread_itex; + list_fi_thread_itex.reserve(num_Threads); + + // find the bounding box of the moving image in the space of the fixed image: + pnt2d_t mi_min; + pnt2d_t mi_max; + if (calc_tile_mosaic_bbox(fi_to_mi, mi, mi_min, mi_max, 16)) + { + // find the bounding box of the fixed image: + pnt2d_t fi_min; + pnt2d_t fi_max; + calc_image_bbox(fi, fi_min, fi_max); + + // clip the bounding box to the bounding box of the fixed image: + clamp_bbox(fi_min, fi_max, mi_min, mi_max); + if (is_singular_bbox(mi_min, mi_max)) + { + // cout << "my_metric_mt: " << "no overlap"; + + return std::numeric_limits::max(); + } + + typename image_t::SizeType fi_size = fi_roi.GetSize(); + + // reset the region of interest: + index_t fi_roi_min; + index_t fi_roi_max; + + // If we cannot transform the moving image point, then use the bottom corner + if (!fi->TransformPhysicalPointToIndex(mi_min, fi_roi_min)) + { + fi_roi_min = fi_roi.GetIndex(); + // cout << "my_metric_mt: " << "cannot transform lower index" << fi_roi_min[0] << " " << fi_roi_min[1] << endl; + } + + // If we cannot transform the moving image point upper corner then use the known size + if (!fi->TransformPhysicalPointToIndex(mi_max, fi_roi_max)) + { + index_t fi_min_index = fi_roi.GetIndex(); + + fi_roi_max = fi_roi_min; + fi_roi_max[0] += mi_max[0] - mi_min[0]; + fi_roi_max[1] += mi_max[1] - mi_min[1]; + + if (fi_roi_max[0] >= (int)fi_size[0] - fi_min_index[0]) + fi_roi_max[0] = (fi_size[0] - fi_min_index[0]) - 1; + + if (fi_roi_max[1] >= (int)fi_size[1] - fi_min_index[1]) + fi_roi_max[1] = (fi_size[1] - fi_min_index[1]) - 1; + + + // cout << "my_metric_mt: " << "cannot transform upper index x: " << fi_roi_max[0] << " y: " << fi_roi_max[1] << + // endl; cout << "my_metric_mt: " << " lower index x: " << fi_roi_min[0] << " y: " << + // fi_roi_min[1] << endl; cout << "my_metric_mt: " << " fi size w: " << fi_size[0] << " h: + // " << fi_size[1] << endl; + } + + // Hmmm.... this shouldn't happen + typename TImage::SizeType roi_size; + roi_size[0] = fi_roi_max[0] - fi_roi_min[0] + 1; + roi_size[1] = fi_roi_max[1] - fi_roi_min[1] + 1; + fi_roi.SetIndex(fi_roi_min); + fi_roi.SetSize(roi_size); + } + + // Divvy up the fixed image roi for each thread to work over... + // set an ROI for each thread to work over, prefer x as the largest dimension + + list_fi_thread_itex = DivideROIAmongThreads(fi, fi_roi, num_Threads); + /* + typename image_t::SizeType fi_size = fi_roi.GetSize(); + int iMaxDim = fi_size[0] >= fi_size[1] ? 0 : 1; + int iMinDim = iMaxDim == 0 ? 1 : 0; + + double Width = fi_size[iMaxDim] / num_Threads; + + typename index_t fi_min = fi_roi.GetIndex(); + typename index_t fi_max = fi_min; + + fi_max[0] += fi_size[0]; + fi_max[1] += fi_size[1]; + + std::vector listThreadStartIndex(num_Threads+1); + listThreadStartIndex[0] = fi_min; + listThreadStartIndex[num_Threads][iMaxDim] = fi_max[iMaxDim]; + listThreadStartIndex[num_Threads][iMinDim] = fi_min[iMinDim]; + + for(int iThread = 1; iThread < num_Threads; iThread++) + { + listThreadStartIndex[iThread][iMaxDim] = fi_min[iMaxDim] + (Width * iThread); + listThreadStartIndex[iThread][iMinDim] = fi_min[iMinDim]; + + //cout << iThread << " fi start index, " << "X0: " << listThreadStartIndex[iThread][0] << " Y0: " << + listThreadStartIndex[iThread][1] << endl; + + } + + for(int iThread = 0; iThread < num_Threads; iThread++) + { + typename index_t thread_min_index = listThreadStartIndex[iThread]; + typename index_t thread_max_index = listThreadStartIndex[iThread+1]; + thread_max_index[iMinDim] = fi_max[iMinDim]; + + if(iThread == num_Threads -1) + { + thread_max_index = fi_max; + } + + typename TImage::SizeType roi_size; + roi_size[0] = (thread_max_index[0] - thread_min_index[0]); + roi_size[1] = (thread_max_index[1] - thread_min_index[1]); + + //cout << iThread << " fixed roi, " << "X0: " << thread_min_index[0] << " Y0: " << thread_min_index[1] << " W: " << + roi_size[0] << " H: " << roi_size[1] << endl; + + TImage::RegionType roi; + roi.SetIndex(thread_min_index); + roi.SetSize(roi_size); + list_fi_thread_roi.push_back(roi); + + itex_t itex(fi, roi); + list_fi_thread_itex.push_back(itex); + } + + */ + const typename TImage::RegionType mi_roi = mi->GetLargestPossibleRegion(); + + // Counters + double final_ab = 0.0; + double final_aa = 0.0; + double final_bb = 0.0; + double final_sa = 0.0; + double final_sb = 0.0; + unsigned long int final_pixels = 0; + + // #pragma omp parallel for + for (int iThread = 0; iThread < (int)list_fi_thread_itex.size(); iThread++) + { + // std::vector indexList = threadIndexList[iThread]; + // performance shortcuts: + mask_t::SizeType fi_mask_size = fi->GetLargestPossibleRegion().GetSize(); + unsigned int fi_spacing_scale = 1; + if (fi_mask) + { + mask_t::SizeType sz = fi_mask->GetLargestPossibleRegion().GetSize(); + fi_spacing_scale = sz[0] / fi_mask_size[0]; + fi_mask_size = sz; + } + + itex_t itex = list_fi_thread_itex[iThread]; + + double ab = 0.0; + double aa = 0.0; + double bb = 0.0; + double sa = 0.0; + double sb = 0.0; + unsigned long int pixels = 0; + + point_t xy; + point_t uv; + pixel_t m; + pixel_t f; + index_t fi_ix; + + for (itex.GoToBegin(); !itex.IsAtEnd(); ++itex) + { + fi_ix = itex.GetIndex(); + + if (fi_mask && !pixel_in_mask(fi_mask, fi_mask_size, fi_ix, fi_spacing_scale)) + { + continue; + } + + fi->TransformIndexToPhysicalPoint(fi_ix, xy); + + uv = fi_to_mi->TransformPoint(xy); + + if (uv[0] <= std::numeric_limits::min() && uv[1] <= std::numeric_limits::min()) + { + continue; + } + + typename TInterpolator::ContinuousIndexType mi_cindex; + mi_interpolator->ConvertPointToContinuousIndex(uv, mi_cindex); + + // TInterpolator::IndexType mi_cindex; + // mi_interpolator->ConvertPointToNearestIndex(uv, mi_cindex); + + + if (!mi_interpolator->IsInsideBuffer(mi_cindex)) + { + continue; + } + + if (mi_mask) + { + if (mi_roi.IsInside(mi_cindex)) + { + if (!pixel_in_mask(mi_mask, uv)) + continue; + } + } + + m = pixel_t(mi_interpolator->EvaluateAtContinuousIndex(mi_cindex)); + + f = itex.Get(); + + /* + + fi_ix = itex.GetIndex(); + if (fi_mask && !pixel_in_mask(fi_mask, + fi_mask_size, + fi_ix, + fi_spacing_scale)) + { + continue; + } + + // point coordinates in the fixed image: + point_t xy; + fi->TransformIndexToPhysicalPoint(fi_ix, xy); + + // corresponding coordinates in the moving image: + const point_t uv = fi_to_mi->TransformPoint(xy); + + // check whether the point is within the moving image: + if (!pixel_in_mask(mi_mask, uv) || + !mi_interpolator->IsInsideBuffer(uv)) + { + continue; + } + + + pixel_t m = pixel_t(mi_interpolator->Evaluate(uv)); + pixel_t f = itex.Get(); + + */ + + double A = f; + double B = m; + ab += A * B; + aa += A * A; + bb += B * B; + sa += A; + sb += B; + pixels++; + } + + final_ab += ab; + final_aa += aa; + final_bb += bb; + final_sa += sa; + final_sb += sb; + final_pixels += pixels; + } + + list_fi_thread_itex.clear(); + + area = calc_area(fi->GetSpacing(), final_pixels); + if (area == 0) + { + // cout << "mt: zero area" << endl; + return std::numeric_limits::max(); + } + + final_aa = final_aa - ((final_sa * final_sa) / double(final_pixels)); + final_bb = final_bb - ((final_sb * final_sb) / double(final_pixels)); + final_ab = final_ab - ((final_sa * final_sb) / double(final_pixels)); + + double result = -final_ab / sqrt(final_aa * final_bb); + + return result; +} + + +//---------------------------------------------------------------- +// my_metric +// +// Calculate a normalized cross correlation metric between two +// masked images, calculate the area of overlap. +// +template +double +my_metric(double & area, + + const TImage * fi, + const TImage * mi, + + const base_transform_t * fi_to_mi, + const mask_t * fi_mask, + const mask_t * mi_mask, + + const TInterpolator * mi_interpolator) +{ + WRAP(itk_terminator_t terminator("my_metric")); + typedef typename itk::ImageRegionConstIteratorWithIndex itex_t; + typedef typename TImage::IndexType index_t; + typedef typename TImage::PointType point_t; + typedef typename TImage::PixelType pixel_t; + + // Instead of iterating over the whole fixed image, find the + // bounding box of the overlapping region of the fixed and + // moving images, clip it to the fixed image bounding box, and iterate + // over that -- this will produce a dramatic speedup in situations + // where the fixed image is large and the moving image is small: + typename TImage::RegionType fi_roi = fi->GetLargestPossibleRegion(); + + // find the bounding box of the moving image in the space of the fixed image: + pnt2d_t mi_min; + pnt2d_t mi_max; + if (calc_tile_mosaic_bbox(fi_to_mi, mi, mi_min, mi_max, 16)) + { + // find the bounding box of the fixed image: + pnt2d_t fi_min; + pnt2d_t fi_max; + calc_image_bbox(fi, fi_min, fi_max); + + // clip the bounding box to the bounding box of the fixed image: + clamp_bbox(fi_min, fi_max, mi_min, mi_max); + if (is_singular_bbox(mi_min, mi_max)) + { + return std::numeric_limits::max(); + } + + // reset the region of interest: + index_t roi_index[2]; + if (fi->TransformPhysicalPointToIndex(mi_min, roi_index[0]) && + fi->TransformPhysicalPointToIndex(mi_max, roi_index[1])) + { + typename TImage::SizeType roi_size; + roi_size[0] = roi_index[1][0] - roi_index[0][0] + 1; + roi_size[1] = roi_index[1][1] - roi_index[0][1] + 1; + fi_roi.SetIndex(roi_index[0]); + fi_roi.SetSize(roi_size); + } + } + + // performance shortcuts: + mask_t::SizeType fi_mask_size = fi->GetLargestPossibleRegion().GetSize(); + unsigned int fi_spacing_scale = 1; + if (fi_mask) + { + mask_t::SizeType sz = fi_mask->GetLargestPossibleRegion().GetSize(); + fi_spacing_scale = sz[0] / fi_mask_size[0]; + fi_mask_size = sz; + } + + // counters: + double ab = 0.0; + double aa = 0.0; + double bb = 0.0; + double sa = 0.0; + double sb = 0.0; + unsigned long int pixels = 0; + + // iterate over the image: + itex_t itex(fi, fi_roi); + index_t fi_ix; + + point_t xy; + point_t uv; + pixel_t m; + pixel_t f; + + typename TImage::RegionType mi_roi = mi->GetLargestPossibleRegion(); + + for (itex.GoToBegin(); !itex.IsAtEnd(); ++itex) + { + // make sure there hasn't been an interrupt: + WRAP(terminator.terminate_on_request()); + + fi_ix = itex.GetIndex(); + + if (fi_mask && !pixel_in_mask(fi_mask, fi_mask_size, fi_ix, fi_spacing_scale)) + { + continue; + } + + fi->TransformIndexToPhysicalPoint(fi_ix, xy); + + uv = fi_to_mi->TransformPoint(xy); + + if (uv[0] <= std::numeric_limits::min() && uv[1] <= std::numeric_limits::min()) + { + continue; + } + + + typename TInterpolator::ContinuousIndexType mi_cindex; + mi_interpolator->ConvertPointToContinuousIndex(uv, mi_cindex); + + // TInterpolator::IndexType mi_cindex; + // mi_interpolator->ConvertPointToNearestIndex(uv, mi_cindex); + + + if (!mi_interpolator->IsInsideBuffer(mi_cindex)) + { + continue; + } + + if (mi_mask) + { + if (mi_roi.IsInside(mi_cindex)) + { + if (!pixel_in_mask(mi_mask, uv)) + continue; + } + } + + m = pixel_t(mi_interpolator->EvaluateAtContinuousIndex(mi_cindex)); + + f = itex.Get(); + + /* + + fi_ix = itex.GetIndex(); + if (fi_mask && !pixel_in_mask(fi_mask, + fi_mask_size, + fi_ix, + fi_spacing_scale)) + { + continue; + } + + // point coordinates in the fixed image: + point_t xy; + fi->TransformIndexToPhysicalPoint(fi_ix, xy); + + // corresponding coordinates in the moving image: + const point_t uv = fi_to_mi->TransformPoint(xy); + + // check whether the point is within the moving image: + if (!pixel_in_mask(mi_mask, uv) || + !mi_interpolator->IsInsideBuffer(uv)) + { + continue; + } + + + pixel_t m = pixel_t(mi_interpolator->Evaluate(uv)); + pixel_t f = itex.Get(); + + */ + + double A = f; + double B = m; + + ab += A * B; + aa += A * A; + bb += B * B; + sa += A; + sb += B; + + pixels++; + } + + area = calc_area(fi->GetSpacing(), pixels); + if (area == 0) + { + return std::numeric_limits::max(); + } + + aa = aa - ((sa * sa) / double(pixels)); + bb = bb - ((sb * sb) / double(pixels)); + ab = ab - ((sa * sb) / double(pixels)); + + double result = -ab / sqrt(aa * bb); + // cout << "st: " << result << endl; + return result; +} + +//---------------------------------------------------------------- +// my_metric +// +// Calculate the normalized cross correlation metric between two +// masked images and the ratio of area of overlap to the area of the +// smaller image. The metric is restricted by the MIN/MAX overlap +// ratio thresholds. +// +template +double +my_metric(double & overlap, + const TImage * fi, + const TImage * mi, + const base_transform_t * fi_to_mi, + const mask_t * fi_mask = NULL, + const mask_t * mi_mask = NULL, + const double min_overlap = 0.05, + const double max_overlap = 1.0) +{ + overlap = 0; + + typedef itk::NearestNeighborInterpolateImageFunction interpolator_t; + typename interpolator_t::Pointer mi_interpolator = interpolator_t::New(); + mi_interpolator->SetInputImage(mi); + + double overlap_area = 0; + double m = my_metric_mt(overlap_area, fi, mi, fi_to_mi, fi_mask, mi_mask, mi_interpolator); + // James Anderson: This needs more testing with other registration methods, however my_metric ir-refine-translate + // handles the overlap calculation ahead of time. so we should not need to test again + + /* + if (overlap_area != 0) + { +#if 0 + // this is very slow, but accurate: + double area_a = calc_area(fi, fi_mask); + double area_b = calc_area(mi, mi_mask); +#else + // this is fast, but approximate -- the mask is ingored, + // only the image dimensions and pixel spacing are used: + double area_a = get_area(fi); + double area_b = get_area(mi); +#endif + + double smaller_area = std::min(area_a, area_b); + overlap = overlap_area / smaller_area; + + cout << "Overlap " << overlap << endl; + if (overlap < min_overlap || overlap > max_overlap) + { + return std::numeric_limits::max(); + } + } + */ + return m; +} + +//---------------------------------------------------------------- +// my_metric +// +// Calculate the normalized cross correlation metric between two +// masked images and the ratio of area of overlap to the area of the +// smaller image. The metric is restricted by the MIN/MAX overlap +// ratio thresholds. +// +template +double +my_metric(const TImage * fi, + const TImage * mi, + const base_transform_t * fi_to_mi, + const mask_t * fi_mask = NULL, + const mask_t * mi_mask = NULL, + const double min_overlap = 0.0, + const double max_overlap = 1.0) +{ + double overlap = 0.0; + return my_metric(overlap, fi, mi, fi_to_mi, fi_mask, mi_mask, min_overlap, max_overlap); +} + +//---------------------------------------------------------------- +// calc_image_bboxes +// +// Calculate the bounding boxes for a set of given images, +// expressed in image space. +// +template +void +calc_image_bboxes(const std::vector & image, + + // image space bounding boxes (for feathering): + std::vector & image_min, + std::vector & image_max) +{ + typedef typename IMG::RegionType::SizeType imagesz_t; + typedef typename IMG::SpacingType spacing_t; + + const unsigned int num_images = image.size(); + image_min.resize(num_images); + image_max.resize(num_images); + + // calculate the bounding boxes: + for (unsigned int i = 0; i < num_images; i++) + { + // initialize an empty bounding box: + image_min[i][0] = std::numeric_limits::max(); + image_min[i][1] = image_min[i][0]; + image_max[i][0] = -image_min[i][0]; + image_max[i][1] = -image_min[i][0]; + + // it happens: + if (image[i].GetPointer() == NULL) + continue; + + // bounding box in the image space: + calc_image_bbox(image[i], image_min[i], image_max[i]); + } +} + +//---------------------------------------------------------------- +// calc_image_bboxes_load_images +// +// Calculate the bounding boxes for a set of images, +// expressed in image space. These must be loaded, and are loaded +// one by one to save memory on large images. +// +template +void +calc_image_bboxes_load_images(const std::list & in, + + // image space bounding boxes (for feathering): + std::vector & image_min, + std::vector & image_max, + + const unsigned int shrink_factor, + const double pixel_spacing) +{ + typedef typename IMG::RegionType::SizeType imagesz_t; + typedef typename IMG::SpacingType spacing_t; + + const unsigned int num_images = in.size(); + image_min.resize(num_images); + image_max.resize(num_images); + + // calculate the bounding boxes: + std::vector vector_in(in.size()); + vector_in.assign(in.begin(), in.end()); + // #pragma omp parallel for + for (int i = 0; i < (int)vector_in.size(); i++) + { + the_text_t * iter = &vector_in[i]; + // initialize an empty bounding box: + image_min[i][0] = std::numeric_limits::max(); + image_min[i][1] = image_min[i][0]; + image_max[i][0] = -image_min[i][0]; + image_max[i][1] = -image_min[i][0]; + + // Read in just the OutputInformation for speed. + typedef typename itk::ImageFileReader reader_t; + typename reader_t::Pointer reader = reader_t::New(); + + reader->SetFileName((*iter)); + cout << "loading region from " << (*iter) << endl; + + // WRAP(terminator_t terminator(reader)); + + // Load up the OutputInformation, faster than the whole image + reader->UpdateOutputInformation(); + typename IMG::ConstPointer image = reader->GetOutput(); + + // bounding box in the image space: + calc_image_bbox(image, image_min[i], image_max[i]); + + // Unload the image. + image = typename IMG::Pointer(NULL); + } +} + +//---------------------------------------------------------------- +// calc_tile_mosaic_bbox +// +// Calculate the warped image bounding box in the mosaic space. +// +extern bool +calc_tile_mosaic_bbox(const base_transform_t * mosaic_to_tile, + + // image space bounding boxes of the tile: + const pnt2d_t & tile_min, + const pnt2d_t & tile_max, + + // mosaic space bounding boxes of the tile: + pnt2d_t & mosaic_min, + pnt2d_t & mosaic_max, + + // sample points along the image edges: + const unsigned int np = 15); + +//---------------------------------------------------------------- +// calc_tile_mosaic_bbox +// +// Calculate the warped image bounding box in the mosaic space. +// +template +bool +calc_tile_mosaic_bbox(const base_transform_t * mosaic_to_tile, + const T * tile, + + // mosaic space bounding boxes of the tile: + pnt2d_t & mosaic_min, + pnt2d_t & mosaic_max, + + // sample points along the image edges: + const unsigned int np = 15) +{ + typename T::SizeType sz = tile->GetLargestPossibleRegion().GetSize(); + typename T::SpacingType sp = tile->GetSpacing(); + + pnt2d_t tile_min = tile->GetOrigin(); + pnt2d_t tile_max; + tile_max[0] = tile_min[0] + sp[0] * double(sz[0]); + tile_max[1] = tile_min[1] + sp[1] * double(sz[1]); + + return calc_tile_mosaic_bbox(mosaic_to_tile, tile_min, tile_max, mosaic_min, mosaic_max, np); +} + +//---------------------------------------------------------------- +// calc_mosaic_bboxes +// +// Calculate the warped image bounding boxes in the mosaic space. +// +template +bool +calc_mosaic_bboxes(const std::vector & xform, + + // image space bounding boxes of mosaic tiles:: + const std::vector & image_min, + const std::vector & image_max, + + // mosaic space bounding boxes of individual tiles: + std::vector & mosaic_min, + std::vector & mosaic_max, + + // sample points along the image edges: + const unsigned int np = 15) +{ + const unsigned int num_images = xform.size(); + mosaic_min.resize(num_images); + mosaic_max.resize(num_images); + + point_t image_point; + point_t mosaic_point; + + // calculate the bounding boxes + bool ok = true; + + // #pragma omp parallel for + for (int i = 0; i < (int)num_images; i++) + { + ok = ok & calc_tile_mosaic_bbox(xform[i], image_min[i], image_max[i], mosaic_min[i], mosaic_max[i], np); + } + + return ok; +} + + +//---------------------------------------------------------------- +// calc_mosaic_bbox +// +// Calculate the mosaic bounding box (a union of mosaic space +// bounding boxes of the warped tiles). +// +template +void +calc_mosaic_bbox( // mosaic space bounding boxes of individual mosaic tiles: + const std::vector & mosaic_min, + const std::vector & mosaic_max, + + // mosiac bounding box: + point_t & MIN, + point_t & MAX) +{ + // initialize an empty bounding box: + MIN[0] = std::numeric_limits::max(); + MIN[1] = MIN[0]; + MAX[0] = -MIN[0]; + MAX[1] = -MIN[0]; + + // calculate the bounding box: + const unsigned int num_images = mosaic_min.size(); + for (unsigned int i = 0; i < num_images; i++) + { + // it happens: + if (mosaic_min[i][0] == std::numeric_limits::max()) + continue; + + // update the mosaic bounding box: + update_bbox(MIN, MAX, mosaic_min[i]); + update_bbox(MIN, MAX, mosaic_max[i]); + } +} + +//---------------------------------------------------------------- +// calc_mosaic_bbox +// +// Calculate the mosaic bounding box (a union of mosaic space +// bounding boxes of the warped tiles). +// +template +void +calc_mosaic_bbox(const std::vector & transform, + const std::vector & image, + + // mosaic bounding box: + typename IMG::PointType & MIN, + typename IMG::PointType & MAX, + + // points along the image edges: + const unsigned int np = 15) +{ + typedef typename IMG::PointType point_t; + + // image space bounding boxes: + std::vector image_min; + std::vector image_max; + calc_image_bboxes(image, image_min, image_max); + + // mosaic space bounding boxes: + std::vector mosaic_min; + std::vector mosaic_max; + + // FIXME: + calc_mosaic_bboxes(transform, image_min, image_max, mosaic_min, mosaic_max, np); + + // mosiac bounding box: + calc_mosaic_bbox(mosaic_min, mosaic_max, MIN, MAX); +} + +//---------------------------------------------------------------- +// calc_mosaic_bbox_load_images +// +// Calculate the mosaic bounding box (a union of mosaic space +// bounding boxes of the warped tiles). +// +template +void +calc_mosaic_bbox_load_images(const std::vector & transform, + const std::list & fn_image, + + // mosaic bounding box: + typename IMG::PointType & MIN, + typename IMG::PointType & MAX, + std::vector & mosaic_min, + std::vector & mosaic_max, + const unsigned int shrink_factor, + const double pixel_spacing, + + // points along the image edges: + const unsigned int np = 15) +{ + typedef typename IMG::PointType point_t; + + // image space bounding boxes: + std::vector image_min; + std::vector image_max; + + calc_image_bboxes_load_images(fn_image, image_min, image_max, shrink_factor, pixel_spacing); + + // mosaic space bounding boxes: + // FIXME: + calc_mosaic_bboxes(transform, image_min, image_max, mosaic_min, mosaic_max, np); + + // mosiac bounding box: + calc_mosaic_bbox(mosaic_min, mosaic_max, MIN, MAX); +} + + +//---------------------------------------------------------------- +// bbox_overlap +// +// Test whether two bounding boxes overlap. +// +inline bool +bbox_overlap(const pnt2d_t & min_box1, const pnt2d_t & max_box1, const pnt2d_t & min_box2, const pnt2d_t & max_box2) +{ + return max_box1[0] > min_box2[0] && min_box1[0] < max_box2[0] && max_box1[1] > min_box2[1] && + min_box1[1] < max_box2[1]; +} + +//---------------------------------------------------------------- +// inside_bbox +// +// Test whether a given point is inside the bounding box. +// +inline bool +inside_bbox(const pnt2d_t & MIN, const pnt2d_t & MAX, const pnt2d_t & pt) +{ + return MIN[0] <= pt[0] && pt[0] <= MAX[0] && MIN[1] <= pt[1] && pt[1] <= MAX[1]; +} + +//---------------------------------------------------------------- +// calc_pixel_weight +// +// Calculate a pixel feathering weight used to blend mosaic tiles. +// +inline static double +calc_pixel_weight(const pnt2d_t & bbox_min, const pnt2d_t & bbox_max, const pnt2d_t & pt) +{ + double w = bbox_max[0] - bbox_min[0]; + double h = bbox_max[1] - bbox_min[1]; + double r = 0.5 * ((w > h) ? h : w); + double sx = std::min(pt[0] - bbox_min[0], bbox_max[0] - pt[0]); + double sy = std::min(pt[1] - bbox_min[1], bbox_max[1] - pt[1]); + double s = (1.0 + std::min(sx, sy)) / (r + 1.0); + return s * s * s * s; +} + + +//---------------------------------------------------------------- +// feathering_t +// +// Supported pixel feathering modes +// +typedef enum +{ + FEATHER_NONE_E, + FEATHER_BLEND_E, + FEATHER_BINARY_E +} feathering_t; + +//---------------------------------------------------------------- +// make_mosaic_st +// +// Assemble a portion of the mosaic positioned at mosaic_min. +// Each tile may be individually tinted with a grayscale color. +// Individual tiles may be omitted. +// Background color (outside the mask) may be specified. +// Tile masks are optional and may be NULL. +// +template +typename IMG::Pointer +make_mosaic_st( + bool assemble_mosaic_mask, + mask_t::Pointer & mosaic_mask, + const typename IMG::SpacingType & mosaic_sp, + const typename IMG::PointType & mosaic_min, + const typename IMG::SizeType & mosaic_sz, + const unsigned int num_images, + const std::vector & omit, + const std::vector & tint, + const std::vector & transform, + const std::vector & image, + + // optional image masks: + const std::vector & image_mask = std::vector(0), + + // feathering to reduce image blurring is optional: + const feathering_t feathering = FEATHER_NONE_E, + typename IMG::PixelType background = 255.0, + + bool dont_allocate = false) +{ + WRAP(itk_terminator_t terminator("make_mosaic_st")); + + typedef typename transform_t::Pointer transform_pointer_t; + typedef typename IMG::Pointer image_pointer_t; + typedef typename IMG::IndexType index_t; + typedef typename IMG::PointType point_t; + typedef typename IMG::PixelType pixel_t; + typedef typename IMG::SpacingType spacing_t; + typedef typename IMG::RegionType::SizeType imagesz_t; + typedef typename itk::ImageRegionIteratorWithIndex itex_t; + typedef typename IMG::RegionType::IndexType ix_t; + typedef typename IMG::RegionType rn_t; + typedef typename IMG::RegionType::SizeType sz_t; + + // setup the image interpolators: + std::vector img(num_images); + + for (unsigned int i = 0; i < num_images; i++) + { + img[i] = img_interpolator_t::New(); + img[i]->SetInputImage(image[i]); + } + + + // setup the image mask interpolators: + typedef itk::NearestNeighborInterpolateImageFunction msk_interpolator_t; + std::vector msk(num_images); + msk.assign(num_images, typename msk_interpolator_t::Pointer(NULL)); + + for (unsigned int i = 0; i < image_mask.size() && i < num_images; i++) + { + if (image_mask[i].GetPointer() == NULL) + continue; + + msk[i] = msk_interpolator_t::New(); + msk[i]->SetInputImage(image_mask[i]); + } + + // image space bounding boxes (for feathering): + std::vector bbox_min(num_images); + std::vector bbox_max(num_images); + calc_image_bboxes(image, bbox_min, bbox_max); + + // mosaic space bounding boxes: + std::vector MIN(num_images); + std::vector MAX(num_images); + + calc_mosaic_bboxes(transform, bbox_min, bbox_max, MIN, MAX); + + // setup the mosaic image: + typename IMG::Pointer mosaic = IMG::New(); + mosaic->SetOrigin(mosaic_min); + mosaic->SetRegions(mosaic_sz); + mosaic->SetSpacing(mosaic_sp); + + mosaic_mask = NULL; + if (assemble_mosaic_mask) + { + mosaic_mask = mask_t::New(); + mosaic_mask->SetOrigin(mosaic_min); + mosaic_mask->SetRegions(mosaic_sz); + mosaic_mask->SetSpacing(mosaic_sp); + } + + if (dont_allocate) + { + // this is useful for estimating the mosaic size: + return mosaic; + } + + rn_t region = mosaic->GetLargestPossibleRegion(); + ix_t origin = region.GetIndex(); + sz_t extent = region.GetSize(); + typename ix_t::IndexValueType x_end = origin[0] + extent[0]; + typename ix_t::IndexValueType y_end = origin[1] + extent[1]; + + mosaic->Allocate(); + + if (assemble_mosaic_mask) + { + mosaic_mask->Allocate(); + } + + std::vector InvalidTiles(num_images, false); + std::vector ValidTilesIndicies; + ValidTilesIndicies.reserve(num_images); + for (unsigned int k = 0; k < num_images; k++) + { + InvalidTiles[k] = (omit[k] || (image[k].GetPointer() == NULL)); + if (!InvalidTiles[k]) + { + ValidTilesIndicies.push_back(k); + } + } + + // this is needed in order to prevent holes in the mask mosaic: + bool integer_pixel = std::numeric_limits::is_integer; + pixel_t pixel_max = pixel_t(std::numeric_limits::max()); + pixel_t pixel_min = integer_pixel ? pixel_t(std::numeric_limits::min()) : -pixel_max; + + ix_t ix = origin; + for (ix[1] = origin[1]; ix[1] < y_end; ++ix[1]) + { + + + // make sure there hasn't been an interrupt: + WRAP(terminator.terminate_on_request()); + + + // JamesA: Figure out which tiles can contribute to this column + // this was added after profiling highlighted this search every tile + // at every pixel code was very slow. + // TODO: Ideally we'd sort the list of tiles along X and only search + // a subset of the indicies for a given x value + + point_t pointColumn; + mosaic->TransformIndexToPhysicalPoint(ix, pointColumn); + std::vector PotentialTiles = + AssembleUtil::GetSortedTilesImagesForColumn(pointColumn, MIN, MAX); + if (PotentialTiles.size() <= 0) + { + continue; + } + + for (ix[0] = origin[0]; ix[0] < x_end; ix[0]++) + { + // check whether termination was requested: + WRAP(terminator.terminate_on_request()); + + point_t point; + mosaic->TransformIndexToPhysicalPoint(ix, point); + + pixel_t pixel = 0.0; + pixel_t weight = 0.0; + unsigned int num_pixels = 0; + for (unsigned int iK = 0; iK < PotentialTiles.size(); iK++) + { + unsigned int k = PotentialTiles[iK]; + + + // don't try to add missing or omitted images to the mosaic: + // if (omit[k] || (image[k].GetPointer() == NULL)) continue; + + // avoid undesirable distortion artifacts: + if (!inside_bbox(MIN[k], MAX[k], point)) + continue; + + point_t pt_k = transform[k]->TransformPoint(point); + if (pt_k[0] >= std::numeric_limits::max()) + continue; + + // make sure the pixel maps into the image: + if (!img[k]->IsInsideBuffer(pt_k)) + continue; + + // make sure the pixel maps into the image mask: + pixel_t alpha = 1.0; + if ((msk[k].GetPointer() != NULL) && (alpha = msk[k]->Evaluate(pt_k)) < 1.0) + continue; + + // feather out the edges by giving them a tiny weight: + num_pixels++; + pixel_t wp = tint[k]; + pixel_t p = img[k]->Evaluate(pt_k) * wp; + pixel_t wa = ((alpha == 1.0) ? 1e-0 : 1e-6) * wp; + + if (feathering == FEATHER_NONE_E) + { + pixel += p * wa; + weight += wa; + } + else + { + pixel_t pixel_weight = calc_pixel_weight(bbox_min[k], bbox_max[k], pt_k); + + if (feathering == FEATHER_BLEND_E) + { + pixel += pixel_weight * p * wa; + weight += pixel_weight * wa; + } + else // FEATHER_BINARY_E + { + if (pixel_weight > weight) + { + pixel = pixel_weight * p * wa; + weight = pixel_weight * wa; + } + } + } + } + + // calculate the final pixel value: + if (weight > 0.0) + { + pixel /= weight; + if (integer_pixel) + { + pixel = floor(pixel + 0.5); + + // make sure we don't exceed the intensity range: + pixel = std::max(pixel_min, std::min(pixel_max, pixel)); + } + + pixel_t tmp = pixel_t(pixel); + mosaic->SetPixel(ix, tmp); + } + else + { + mosaic->SetPixel(ix, background); + } + + if (mosaic_mask.GetPointer()) + { + mask_t::PixelType mask_pixel = (weight > 0.0) ? 255 : 0; + mosaic_mask->SetPixel(ix, mask_pixel); + } + } + } + + // mosaic->Register(); + return mosaic; +} + + +//---------------------------------------------------------------- +// assemble_mosaic_t +// +// Parallelized mosaic assembly mechanism +// itile_t is the interpolator function to use +// +template +class assemble_mosaic_t : public the_transaction_t +{ +public: + typedef typename IMG::PointType pnt_t; + typedef typename IMG::PixelType pxl_t; + typedef typename IMG::RegionType rn_t; + typedef typename IMG::RegionType::SizeType sz_t; + typedef typename IMG::RegionType::IndexType ix_t; + typedef itk::NearestNeighborInterpolateImageFunction imask_t; + + assemble_mosaic_t( // thread index and number of threads, used to avoid + // concurrent access to the same point in the mosaic: + unsigned int thread_offset, + unsigned int thread_stride, + + // mosaic being assembled: + typename IMG::Pointer & mosaic, + typename mask_t::Pointer & mosaic_mask, + + // number of tiles to use for this mosaic (may be fewer + // then the number of tiles available): + unsigned int num_tiles, + + // omitted tile flags: + const std::vector & omit, + + // tile tint: + const std::vector & tint, + + // tile trasforms: + const std::vector & transform, + + // tiles and tile masks + const std::vector & tile, + const std::vector & mask, + + // tile and tile mask interpolators: + const std::vector & itile, + const std::vector & imask, + + // image space tile bounding boxes (for feathering): + const std::vector & bbox_min, + const std::vector & bbox_max, + + // mosaic space tile bounding boxes: + const std::vector & MIN, + const std::vector & MAX, + + // overlap region feathering method: + feathering_t feathering, + + // default mosaic pixel value: + pxl_t background) + : + + thread_offset_(thread_offset) + , thread_stride_(thread_stride) + , mosaic_(mosaic) + , mosaic_mask_(mosaic_mask) + , num_tiles_(num_tiles) + , omit_(omit) + , feathering_(feathering) + , background_(background) + { + tile_.reserve(num_tiles); + mask_.reserve(num_tiles); + itile_.reserve(num_tiles); + imask_.reserve(num_tiles); + bbox_min_.reserve(num_tiles); + bbox_max_.reserve(num_tiles); + min_.reserve(num_tiles); + max_.reserve(num_tiles); + transform_.reserve(num_tiles); + + std::vector SortedTiles = AssembleUtil::GetSortedIndicies(MIN, omit); + + for (unsigned int k = 0; k < num_tiles_; k++) + { + unsigned int j = SortedTiles[k]; + + if (tile[k].GetPointer() == NULL) + { + continue; + } + + tint_.push_back(tint[j]); + transform_.push_back(transform[j]); + tile_.push_back(tile[j]); + + if (j < mask.size()) + mask_.push_back(mask[j]); + + itile_.push_back(itile[j]); + imask_.push_back(imask[j]); + bbox_min_.push_back(bbox_min[j]); + bbox_max_.push_back(bbox_max[j]); + min_.push_back(MIN[j]); + max_.push_back(MAX[j]); + } + } + + void + execute(the_thread_interface_t * thread) + { + WRAP(itk_terminator_t terminator("assemble_mosaic_t::execute")); + + // this is needed in order to prevent holes in the mask mosaic: + const bool integer_pixel = std::numeric_limits::is_integer; + const pxl_t pixel_max = pxl_t(std::numeric_limits::max()); + const pxl_t pixel_min = integer_pixel ? pxl_t(std::numeric_limits::min()) : -pixel_max; + + rn_t region = mosaic_->GetLargestPossibleRegion(); + ix_t origin = region.GetIndex(); + sz_t extent = region.GetSize(); + typename ix_t::IndexValueType x_end = origin[0] + extent[0]; + typename ix_t::IndexValueType y_end = origin[1] + extent[1]; + + // std::vector InvalidTiles(num_tiles_, false); + + unsigned int iStartTile = 0; + unsigned int iEndTile = num_tiles_; + + ix_t ix = origin; + for (ix[1] = origin[1]; ix[1] < y_end; ++ix[1]) + { + // check whether termination was requested: + WRAP(terminator.terminate_on_request()); + + // JamesA: Figure out which tiles can contribute to this column + // this was added after profiling highlighted this search every tile + // at every pixel code was very slow. + // TODO: Ideally we'd sort the list of tiles along X and only search + // a subset of the indicies for a given x value + + pnt_t pointColumn; + mosaic_->TransformIndexToPhysicalPoint(ix, pointColumn); + // std::vector PotentialTiles = AssembleUtil::GetSortedTilesImagesForColumn(pointColumn, + // min_, max_); + + // Tiles are sorted in constructor + AssembleUtil::GetEligibleIndiciesForSortedColumn(pointColumn, min_, max_, iStartTile, iEndTile); + + if (iStartTile > iEndTile) + { + continue; + } + + // Tiles are sorted in y, then in x. So StartTile can increase as we pass tiles in x + // and we can exit loops once the point is outside a tile + + unsigned int RowStartTile = iStartTile; + + for (ix[0] = origin[0] + thread_offset_; ix[0] < x_end; ix[0] += thread_stride_) + { + // check whether termination was requested: + WRAP(terminator.terminate_on_request()); + + pnt_t point; + mosaic_->TransformIndexToPhysicalPoint(ix, point); + + pxl_t pixel = 0.0; + pxl_t weight = 0.0; + unsigned int num_pixels = 0; + + for (unsigned int iK = RowStartTile; iK <= iEndTile; iK++) + { + unsigned int k = iK; + + // don't try to add missing or omitted images to the mosaic: + // James A: Profiler showed that omit_[k] was very expensive, so we check outside the loop once and only use + // valid indicies if (InvalidTiles[k]) + //{ + // continue; + //} + if (min_[k][0] >= point[0] || max_[k][0] <= point[0]) + { + // avoid undesirable distortion artifacts: + continue; + } + + pnt_t pt_k = transform_[k]->TransformPoint(point); + + if (pt_k[0] == std::numeric_limits::max()) + continue; + + if (pt_k[1] == std::numeric_limits::max()) + continue; + + // make sure the pixel maps into the image: + if (!itile_[k]->IsInsideBuffer(pt_k)) + { + continue; + } + + // make sure the pixel maps into the image mask: + pxl_t alpha = 1.0; + if ((imask_[k].GetPointer() != NULL) && (alpha = imask_[k]->Evaluate(pt_k)) < 1.0) + { + continue; + } + + // feather out the edges by giving them a tiny weight: + num_pixels++; + pxl_t wp = tint_[k]; + pxl_t p = itile_[k]->Evaluate(pt_k) * wp; + pxl_t wa = ((alpha == 1.0) ? 1e-0 : 1e-6) * wp; + + if (feathering_ == FEATHER_NONE_E) + { + pixel += p * wa; + weight += wa; + } + else + { + pxl_t pixel_weight = calc_pixel_weight(bbox_min_[k], bbox_max_[k], pt_k); + + if (feathering_ == FEATHER_BLEND_E) + { + pixel += pixel_weight * p * wa; + weight += pixel_weight * wa; + } + else // FEATHER_BINARY_E + { + if (pixel_weight > weight) + { + pixel = pixel_weight * p * wa; + weight = pixel_weight * wa; + } + } + } + } + + // calculate the final pixel value: + if (weight > 0.0) + { + pixel /= weight; + if (integer_pixel) + { + pixel = floor(pixel + 0.5); + + // make sure we don't exceed the intensity range: + pixel = std::max(pixel_min, std::min(pixel_max, pixel)); + } + + pxl_t output = pxl_t(pixel); + mosaic_->SetPixel(ix, output); + } + else + { + pxl_t output = num_pixels > 0 ? 0 : pxl_t(background_); + mosaic_->SetPixel(ix, output); + } + + if (mosaic_mask_) + { + mask_t::PixelType mask_pixel = (weight > 0.0) ? 255 : 0; + mosaic_mask_->SetPixel(ix, mask_pixel); + } + } + } + } + + + // data members facilitating concurrent mosaic assembly: + unsigned int thread_offset_; + unsigned int thread_stride_; + + // output mosaic: + typename IMG::Pointer & mosaic_; + mask_t * mosaic_mask_; + + // number of tiles used for this mosaic (may be fewer than + // what's available from the tile vector): + unsigned int num_tiles_; + + // omitted tile flags: + const std::vector omit_; + + // tile tint: + std::vector tint_; + + // tile trasforms: + std::vector transform_; + + // tiles and tile masks + std::vector tile_; + std::vector mask_; + + + // tile and tile mask interpolators: + std::vector itile_; + std::vector imask_; + + // image space tile bounding boxes (for feathering): + std::vector bbox_min_; + std::vector bbox_max_; + + // mosaic space tile bounding boxes: + std::vector min_; + std::vector max_; + + // overlap region feathering method: + feathering_t feathering_; + + // default mosaic pixel value: + pxl_t background_; + + ~assemble_mosaic_t() + { + itile_.clear(); + imask_.clear(); + tile_.clear(); + mask_.clear(); + transform_.clear(); + } +}; + +//---------------------------------------------------------------- +// make_mosaic_mt +// +// Assemble a portion of the mosaic positioned at mosaic_min. +// Each tile may be individually tinted with a grayscale color. +// Individual tiles may be omitted. +// Background color (outside the mask) may be specified. +// Tile masks are optional and may be NULL. +// +template +typename IMG::Pointer +make_mosaic_mt( + unsigned int num_threads, + bool assemble_mosaic_mask, + mask_t::Pointer & mosaic_mask, + const typename IMG::SpacingType & mosaic_sp, + const typename IMG::PointType & mosaic_min, + const typename IMG::SizeType & mosaic_sz, + const unsigned int num_images, + const std::vector & omit, + const std::vector & tint, + const std::vector & transform, + const std::vector & image, + + // optional image masks: + const std::vector & image_mask = std::vector(0), + + // feathering to reduce image blurring is optional: + const feathering_t feathering = FEATHER_NONE_E, + typename IMG::PixelType background = 255.0, + + bool dont_allocate = false) +{ + typedef typename IMG::PixelType pixel_t; + typedef typename IMG::Pointer image_pointer_t; + typedef typename transform_t::Pointer transform_pointer_t; + + if (num_threads == 1) + { + // use the old single-threaded code: + return make_mosaic_st(assemble_mosaic_mask, + mosaic_mask, + mosaic_sp, + mosaic_min, + mosaic_sz, + num_images, + omit, + tint, + transform, + image, + image_mask, + feathering, + background, + dont_allocate); + } + + // WRAP(itk_terminator_t terminator("make_mosaic_mt")); + + typedef typename IMG::PointType pnt_t; + + // setup the image interpolators: + std::vector img(num_images); + + for (unsigned int i = 0; i < num_images; i++) + { + img[i] = img_interpolator_t::New(); + img[i]->SetInputImage(image[i]); + } + + // setup the image mask interpolators: + typedef itk::NearestNeighborInterpolateImageFunction msk_interpolator_t; + std::vector msk(num_images); + msk.assign(num_images, typename msk_interpolator_t::Pointer(NULL)); + + for (unsigned int i = 0; i < image_mask.size() && i < num_images; i++) + { + if (image_mask[i].GetPointer() == NULL) + continue; + + msk[i] = msk_interpolator_t::New(); + msk[i]->SetInputImage(image_mask[i]); + } + + // image space bounding boxes (for feathering): + std::vector bbox_min(num_images); + std::vector bbox_max(num_images); + calc_image_bboxes(image, bbox_min, bbox_max); + + // mosaic space bounding boxes: + std::vector MIN(num_images); + std::vector MAX(num_images); + calc_mosaic_bboxes(transform, bbox_min, bbox_max, MIN, MAX); + + // setup the mosaic image: + image_pointer_t mosaic = IMG::New(); + mosaic->SetOrigin(mosaic_min); + mosaic->SetRegions(mosaic_sz); + mosaic->SetSpacing(mosaic_sp); + + mosaic_mask = NULL; + if (assemble_mosaic_mask) + { + mosaic_mask = mask_t::New(); + mosaic_mask->SetOrigin(mosaic_min); + mosaic_mask->SetRegions(mosaic_sz); + mosaic_mask->SetSpacing(mosaic_sp); + } + + if (dont_allocate) + { + // this is useful for estimating the mosaic size: + return mosaic; + } + + // allocate the mosaic: + mosaic->Allocate(); + + if (assemble_mosaic_mask) + { + mosaic_mask->Allocate(); + } + + // setup transactions for multi-threaded mosaic assembly: + std::list schedule; + for (unsigned int i = 0; i < num_threads; i++) + { + assemble_mosaic_t * t = + new assemble_mosaic_t(i, + num_threads, + mosaic, + mosaic_mask, + num_images, + omit, + tint, + transform, + image, + image_mask, + img, + msk, + bbox_min, + bbox_max, + MIN, + MAX, + feathering, + background); + + schedule.push_back(t); + } + + // setup the thread pool: + the_thread_pool_t thread_pool(num_threads); + thread_pool.set_idle_sleep_duration(50); // 50 usec + thread_pool.push_back(schedule); + thread_pool.pre_distribute_work(); + + // execute mosaic assembly transactions: + suspend_itk_multithreading_t suspend_itk_mt; + thread_pool.start(); + thread_pool.wait(); + + img.clear(); + msk.clear(); + + // done: + return mosaic; +} + +//---------------------------------------------------------------- +// make_mosaic +// +// Assemble a portion of the mosaic positioned at mosaic_min. +// Each tile may be individually tinted with a grayscale color. +// Individual tiles may be omitted. +// Background color (outside the mask) may be specified. +// Tile masks are optional and may be NULL. +// +// Use all possible processors/cores available for multi-threading: +// +template +typename IMG::Pointer +make_mosaic(const typename IMG::SpacingType & mosaic_sp, + const typename IMG::PointType & mosaic_min, + const typename IMG::SizeType & mosaic_sz, + const unsigned int num_images, + const std::vector & omit, + const std::vector & tint, + const std::vector & transform, + const std::vector & image, + + // optional image masks: + const std::vector & image_mask = std::vector(0), + + // feathering to reduce image blurring is optional: + const feathering_t feathering = FEATHER_NONE_E, + typename IMG::PixelType background = 255.0, + + bool dont_allocate = false, + const int num_threads = std::thread::hardware_concurrency()) +{ + // NOTE: for backwards compatibility we do not assemble the mosaic mask: + const bool assemble_mosaic_mask = false; + mask_t::Pointer mosaic_mask; + + return make_mosaic_mt(num_threads, + assemble_mosaic_mask, + mosaic_mask, + mosaic_sp, + mosaic_min, + mosaic_sz, + num_images, + omit, + tint, + transform, + image, + image_mask, + feathering, + background, + dont_allocate); +} + + +//---------------------------------------------------------------- +// make_mosaic +// +// Assemble a portion of the mosaic positioned at mosaic_min. +// Tiles are not tinted. +// +template +typename IMG::Pointer +make_mosaic(const typename IMG::SpacingType & mosaic_sp, + const typename IMG::PointType & mosaic_min, + const typename IMG::SizeType & mosaic_sz, + const unsigned int num_images, + const std::vector & omit, + const std::vector & transform, + const std::vector & image, + + // optional image masks: + const std::vector & image_mask = std::vector(0), + + // feathering to reduce image blurring is optional: + const feathering_t feathering = FEATHER_NONE_E, + typename IMG::PixelType background = 255.0, + + bool dont_allocate = false) +{ + const std::vector tint(num_images, 1.0); + return make_mosaic(mosaic_sp, + mosaic_min, + mosaic_sz, + num_images, + omit, + tint, + transform, + image, + image_mask, + feathering, + background, + dont_allocate); +} + +//---------------------------------------------------------------- +// make_mosaic +// +// Assemble a portion of the mosaic bounded by +// mosaic_min and mosaic_max. +// +template +typename IMG::Pointer +make_mosaic(const typename IMG::SpacingType & mosaic_sp, + const typename IMG::PointType & mosaic_min, + const typename IMG::PointType & mosaic_max, + const unsigned int num_images, + const std::vector & omit, + const std::vector & transform, + const std::vector & image, + + // optional image masks: + const std::vector & image_mask = std::vector(0), + + // feathering to reduce image blurring is optional: + const feathering_t feathering = FEATHER_NONE_E, + + typename IMG::PixelType background = 255.0, + + bool dont_allocate = false) +{ + typename IMG::SizeType mosaic_sz; + mosaic_sz[0] = (unsigned int)((mosaic_max[0] - mosaic_min[0]) / mosaic_sp[0]); + mosaic_sz[1] = (unsigned int)((mosaic_max[1] - mosaic_min[1]) / mosaic_sp[1]); + + return make_mosaic(mosaic_sp, + mosaic_min, + mosaic_sz, + num_images, + omit, + transform, + image, + image_mask, + feathering, + background, + dont_allocate); +} + +//---------------------------------------------------------------- +// make_mosaic +// +// Assemble a portion of the mosaic bounded by +// mosaic_min and mosaic_max. +// +template +typename IMG::Pointer +make_mosaic(const typename IMG::SpacingType & mosaic_sp, + const unsigned int num_images, + const std::vector & omit, + const std::vector & transform, + const std::vector & image, + + // optional image masks: + const std::vector & image_mask = std::vector(0), + + // feathering to reduce image blurring is optional: + const feathering_t feathering = FEATHER_NONE_E, + + typename IMG::PixelType background = 255.0, + + bool dont_allocate = false) +{ + // mosaic bounding box: + typename IMG::PointType mosaic_min; + typename IMG::PointType mosaic_max; + calc_mosaic_bbox(transform, image, mosaic_min, mosaic_max); + + return make_mosaic(mosaic_sp, + mosaic_min, + mosaic_max, + num_images, + omit, + transform, + image, + image_mask, + feathering, + background, + dont_allocate); +} + +//---------------------------------------------------------------- +// make_mosaic +// +// Assemble the entire mosaic from a set of tiles and transforms. +// Individual tiles may be omitted. +// +template +typename IMG::Pointer +make_mosaic( + const std::vector & image, + const std::vector & transform, + const feathering_t feathering = FEATHER_NONE_E, + const unsigned int omit = std::numeric_limits::max(), + unsigned int num_images = std::numeric_limits::max(), + // the masks are optional: + const std::vector & image_mask = std::vector(0)) +{ + if (num_images == std::numeric_limits::max()) + { + num_images = image.size(); + } + + std::vector omit_vec(num_images); + omit_vec.assign(num_images, false); + if (omit != std::numeric_limits::max()) + { + omit_vec[omit] = true; + } + + return make_mosaic(image[0]->GetSpacing(), + num_images, + omit_vec, + transform, + image, + image_mask, + feathering, + 0.0); // background +} + +#define NUM_THREADS_FOR_ASYNC_SAVE 24 + +enum IMAGE_SAVE_FORMAT +{ + IMAGE_SAVE_FORMAT_DEFAULT = 0x00, + IMAGE_SAVE_FORMAT_INT16 = 0x01, + IMAGE_SAVE_FORMAT_UINT16 = 0x0 +}; + +//---------------------------------------------------------------- +// save_mosaic +// +// Assemble the mosaic and save the mosaic image +// Individual mosaic tiles may be omitted. +// +template +void +save_mosaic(const std::vector & image, + const std::vector & transform, + const char * filename, + const unsigned int omit = std::numeric_limits::max(), + unsigned int num_images = std::numeric_limits::max()) +{ + typedef typename image_pointer_t::ObjectType::Pointer::ObjectType IMG; + + typename IMG::Pointer mosaic = + make_mosaic(image, transform, false, omit, num_images); + save(mosaic, filename); +} + +//---------------------------------------------------------------- +// save_mosaic_t +// +template +class save_mosaic_t : public the_transaction_t +{ + +public: + save_mosaic_t(const typename T::ConstPointer image, const the_text_t filename, bool blab = true) + : m_image(image) + , m_filename(filename) + , m_blab(blab) + { + m_image->Register(); + } + + // virtual: + void + execute(the_thread_interface_t * thread) + { + + typedef typename itk::ImageFileWriter writer_t; + typename writer_t::Pointer writer = writer_t::New(); + writer->SetInput(m_image); + + // if (m_blab) + // cout << "saving " << m_filename.text() << endl; + + try + { + writer->SetFileName(m_filename); + writer->Write(); + + m_image->UnRegister(); + } + catch (itk::ExceptionObject & exception) + { + cerr << "Error saving " << m_filename.text() << endl << exception << endl; + } + } + +private: + // image: + typename T::ConstPointer m_image; + + // filename: + const the_text_t m_filename; + + bool m_blab; +}; + +class save_mosaic +{ + static the_thread_pool_t * _pthread_pool; + + save_mosaic() + { + if (_pthread_pool == NULL) + { + _pthread_pool = new the_thread_pool_t(NUM_THREADS_FOR_ASYNC_SAVE); + _pthread_pool->set_idle_sleep_duration(50); // 50 usec + _pthread_pool->start(); + } + } + + ~save_mosaic() + { + if (_pthread_pool != NULL) + { + delete _pthread_pool; + _pthread_pool = NULL; + } + } + +public: + template + static void + async(typename T::ConstPointer image, + const the_text_t filename, + IMAGE_SAVE_FORMAT format = IMAGE_SAVE_FORMAT_DEFAULT, + bool blab = true) + { + typename T::ConstPointer pT = image; + if (_pthread_pool == NULL) + { + _pthread_pool = new the_thread_pool_t(NUM_THREADS_FOR_ASYNC_SAVE); + _pthread_pool->set_idle_sleep_duration(50); // 50 usec + //_pthread_pool->start(); + } + + if (format == IMAGE_SAVE_FORMAT_DEFAULT) + { + typedef typename itk::CastImageFilter cast_t; + typename cast_t::Pointer filter = cast_t::New(); + + filter->SetInput(pT); + + // put a terminator on the filter: + filter->Update(); + + native_image_t::ConstPointer native_image = filter->GetOutput(); + + save_mosaic_t * saveObj = new save_mosaic_t(native_image, filename, blab); + _pthread_pool->start(saveObj); + } + /* + else if(format == IMAGE_SAVE_FORMAT_UINT16) + { + //image->Register(); + typedef itk::Image uint16_image_t; + uint16_image_t::Pointer native_image = cast< image_t, uint16_image_t>(&image); + save_mosaic_t *t = new save_mosaic_t(native_image, filename, blab); + _pthread_pool->push_back(t, false); + + } + else if(format == IMAGE_SAVE_FORMAT_INT16) + { + //image->Register(); + typedef itk::Image int16_image_t; + int16_image_t::Pointer native_image = cast< image_t, int16_image_t>(&image); + save_mosaic_t *t = new save_mosaic_t(native_image, filename, blab); + _pthread_pool->push_back(t, false); + } +*/ + // setup the thread pool: + } + + static void + WaitForAsync() + { + if (_pthread_pool == NULL) + { + return; + } + else + { + _pthread_pool->wait(); + _pthread_pool = NULL; + } + } +}; + +#undef NUM_THREADS_FOR_ASYNC_SAVE + +template +class assemble_mosaic_transaction_t : public the_transaction_t +{ + + +private: + // const typedef transform_t::Pointer transform_pointer_t; + // const typedef IMG::Pointer image_pointer_t; + // const typedef IMG::PointType pnt_t; + + const typename IMG::SpacingType m_mosaic_sp; + const typename IMG::PointType m_mosaic_min; + const typename IMG::SizeType m_mosaic_sz; + const std::vector m_omit; + const std::vector m_tint; + const std::vector m_transform; + const std::vector m_image; + const std::vector m_image_mask; + const feathering_t m_feathering; + const typename IMG::PixelType m_background; + const unsigned int m_num_threads; + +public: + bool m_assemble_mosaic_mask; + mask_t::Pointer m_mosaic_mask; + + struct OUTPUT + { + public: + typename IMG::ConstPointer mosaic; + typename mask_t::Pointer mask; + the_text_t SaveFileName; + }; + + OUTPUT * m_pOutput; + the_text_t m_SaveFileName; + + assemble_mosaic_transaction_t( + int num_threads, // Number of threads to use for this transaction, sometimes we may have fewer tiles than threads + bool assemble_mosaic_mask, + typename mask_t::Pointer & mosaic_mask, + const typename IMG::SpacingType & mosaic_sp, + const typename IMG::PointType & mosaic_min, + const typename IMG::SizeType & mosaic_sz, + const std::vector & omit, + const std::vector & tint, + const std::vector & transform, + const std::vector & image, + + // optional image masks: + const std::vector & image_mask = std::vector(0), + + // feathering to reduce image blurring is optional: + const feathering_t feathering = FEATHER_NONE_E, + typename IMG::PixelType background = 255.0, + OUTPUT * output = NULL, + the_text_t & saveFileName = "") + : // If we should save the image to disk, use this filename + m_num_threads(num_threads) + , m_assemble_mosaic_mask(assemble_mosaic_mask) + , m_mosaic_mask(mosaic_mask) + , m_mosaic_sp(mosaic_sp) + , m_mosaic_min(mosaic_min) + , m_mosaic_sz(mosaic_sz) + , m_omit(omit) + , m_tint(tint) + , m_transform(transform) + , m_image(image) + , m_image_mask(image_mask) + , m_feathering(feathering) + , m_background(background) + , m_pOutput(output) + { + m_SaveFileName = saveFileName.text(); + if (m_pOutput != NULL) + { + m_pOutput->SaveFileName = saveFileName.text(); + } + } + + void + execute(the_thread_interface_t * thread) + { + // cout << "Building " << m_SaveFileName << endl; + typename IMG::ConstPointer mosaic; + if (m_num_threads == 1) + { + mosaic = + make_mosaic_st>(m_assemble_mosaic_mask, + m_mosaic_mask, + m_mosaic_sp, + m_mosaic_min, + m_mosaic_sz, + m_image.size(), + m_omit, + m_tint, + m_transform, + m_image, + m_image_mask, + m_feathering, + m_background); + } + else + { + mosaic = + make_mosaic_mt>(m_num_threads, + m_assemble_mosaic_mask, + m_mosaic_mask, + m_mosaic_sp, + m_mosaic_min, + m_mosaic_sz, + m_image.size(), + m_omit, + m_tint, + m_transform, + m_image, + m_image_mask, + m_feathering, + m_background); + } + + + if (m_pOutput != NULL) + { + m_pOutput->mosaic = mosaic; + + if (m_assemble_mosaic_mask) + { + m_pOutput->mask = m_mosaic_mask; + } + } + + if (m_SaveFileName.size() > 0) + { + // Send someone to save the mosaic + save_mosaic::async(mosaic, m_SaveFileName); + } + } +}; + + +//---------------------------------------------------------------- +// update_mosaic +// +// Expand the mosaic by adding another tile to it. This may be +// used to assemble the mosaic incrementally. +// +template +typename T::Pointer +update_mosaic(const T * mosaic, + const T * tile, + const base_transform_t * tile_transform, + const mask_t * mask_mosaic = NULL, + const mask_t * mask_tile = NULL) +{ + std::vector image(2); + image[0] = mosaic; + image[1] = tile; + + std::vector transform(2); + transform[0] = identity_transform_t::New(); + transform[1] = tile_transform; + + std::vector image_mask(2); + image_mask[0] = mask_mosaic; + image_mask[1] = mask_tile; + + std::vector omit(2); + omit.assign(2, false); + + return make_mosaic( + mosaic->GetSpacing(), 2, omit, transform, image, image_mask, FEATHER_NONE_E); +} + +//---------------------------------------------------------------- +// make_mask_st +// +// Assemble a mask for the entire mosaic. +// Individual tiles may be omitted. +// +template +mask_t::Pointer +make_mask_st(const mask_t::SpacingType & mosaic_sp, + const unsigned int num_images, + const std::vector & omit, + const std::vector & transform, + const std::vector & image_mask) +{ + WRAP(itk_terminator_t terminator("make_mask_st")); + + typedef mask_t::IndexType index_t; + typedef mask_t::PointType point_t; + typedef mask_t::PixelType pixel_t; + typedef mask_t::SpacingType spacing_t; + typedef mask_t::RegionType::SizeType imagesz_t; + typedef itk::ImageRegionIteratorWithIndex itex_t; + + // setup the image mask interpolators: + typedef itk::NearestNeighborInterpolateImageFunction msk_interpolator_t; + std::vector msk(num_images); + msk.assign(num_images, typename msk_interpolator_t::Pointer(NULL)); + + for (unsigned int i = 0; i < image_mask.size() && i < num_images; i++) + { + if (image_mask[i].GetPointer() == NULL) + continue; + + msk[i] = msk_interpolator_t::New(); + msk[i]->SetInputImage(image_mask[i]); + } + + // image space bounding boxes (for feathering): + std::vector bbox_min(num_images); + std::vector bbox_max(num_images); + calc_image_bboxes(image_mask, bbox_min, bbox_max); + + // mosaic space bounding boxes: + std::vector MIN(num_images); + std::vector MAX(num_images); + calc_mosaic_bboxes(transform, bbox_min, bbox_max, MIN, MAX); + + // mosiac bounding box: + point_t mosaic_min; + point_t mosaic_max; + calc_mosaic_bbox(MIN, MAX, mosaic_min, mosaic_max); + + // setup the mosaic image: + mask_t::Pointer mosaic = mask_t::New(); + imagesz_t mosaic_sz; + + mosaic_sz[0] = (unsigned int)((mosaic_max[0] - mosaic_min[0]) / mosaic_sp[0]); + mosaic_sz[1] = (unsigned int)((mosaic_max[1] - mosaic_min[1]) / mosaic_sp[1]); + mosaic->SetOrigin(pnt2d(mosaic_min[0], mosaic_min[1])); + mosaic->SetRegions(mosaic_sz); + mosaic->SetSpacing(mosaic_sp); + + // make sure there hasn't been an interrupt: + WRAP(terminator.terminate_on_request()); + + mosaic->Allocate(); + mosaic->FillBuffer(itk::NumericTraits::Zero); + + itex_t itex(mosaic, mosaic->GetLargestPossibleRegion()); + for (itex.GoToBegin(); !itex.IsAtEnd(); ++itex) + { + // make sure there hasn't been an interrupt: + WRAP(terminator.terminate_on_request()); + + point_t point; + mosaic->TransformIndexToPhysicalPoint(itex.GetIndex(), point); + + for (unsigned int k = 0; k < num_images; k++) + { + // don't try to add missing or omitted images to the mosaic: + if (omit[k] || (image_mask[k].GetPointer() == NULL)) + continue; + + // avoid undesirable radial distortion artifacts for R >> Rmax: + if (!inside_bbox(MIN[k], MAX[k], point)) + continue; + + point_t pt_k = transform[k]->TransformPoint(point); + + // make sure the pixel maps into the image: + if (!msk[k]->IsInsideBuffer(pt_k)) + continue; + + // make sure the pixel maps into the image mask: + if (msk[k]->Evaluate(pt_k) == 0) + continue; + + itex.Set(255); + break; + } + } + + return mosaic; +} + + +//---------------------------------------------------------------- +// assemble_mask_t +// +// Parallelized mosaic mask assembly mechanism +// +template +class assemble_mask_t : public the_transaction_t +{ +public: + typedef mask_t::PointType pnt_t; + typedef mask_t::PixelType pxl_t; + typedef mask_t::RegionType rn_t; + typedef mask_t::RegionType::SizeType sz_t; + typedef mask_t::RegionType::IndexType ix_t; + typedef itk::NearestNeighborInterpolateImageFunction imask_t; + + assemble_mask_t( // thread index and number of threads, used to avoid + // concurrent access to the same point in the mosaic: + unsigned int thread_offset, + unsigned int thread_stride, + + // mosaic being assembled: + mask_t::Pointer & mosaic, + + // number of tiles to use for this mosaic (may be fewer + // then the number of tiles available): + unsigned int num_tiles, + + // omitted tile flags: + const std::vector & omit, + + // tile trasforms: + const std::vector & transform, + + // tiles and tile masks + const std::vector & mask, + + // tile and tile mask interpolators: + const std::vector & imask, + + // mosaic space tile bounding boxes: + const std::vector & MIN, + const std::vector & MAX) + : + + thread_offset_(thread_offset) + , thread_stride_(thread_stride) + , mosaic_(mosaic) + , num_tiles_(num_tiles) + , omit_(omit) + , transform_(transform) + , mask_(mask) + , imask_(imask) + , min_(MIN) + , max_(MAX) + { + mask_.reserve(num_tiles); + imask_.reserve(num_tiles); + min_.reserve(num_tiles); + max_.reserve(num_tiles); + transform_.reserve(num_tiles); + + std::vector SortedTiles = AssembleUtil::GetSortedIndicies(MIN, omit); + + for (unsigned int k = 0; k < num_tiles_; k++) + { + unsigned int j = SortedTiles[k]; + + if (omit[j]) + { + continue; + } + + transform_.push_back(transform[j]); + mask_.push_back(mask[j]); + imask_.push_back(imask[j]); + min_.push_back(MIN[j]); + max_.push_back(MAX[j]); + } + } + + void + execute(the_thread_interface_t * thread) + { + WRAP(itk_terminator_t terminator("assemble_mask_t::execute")); + + rn_t region = mosaic_->GetLargestPossibleRegion(); + ix_t origin = region.GetIndex(); + sz_t extent = region.GetSize(); + ix_t::IndexValueType x_end = origin[0] + extent[0]; + ix_t::IndexValueType y_end = origin[1] + extent[1]; + + std::vector InvalidTiles(num_tiles_, false); + // std::vector ValidTilesIndicies = + // AssembleUtil::PruneInvalidTiles(omit_, mask_); + + unsigned int iStartTile = 0; + unsigned int iEndTile = num_tiles_; + + ix_t ix = origin; + for (ix[1] = origin[1]; ix[1] < y_end; ++ix[1]) + { + // check whether termination was requested: + WRAP(terminator.terminate_on_request()); + + pnt_t pointColumn; + mosaic_->TransformIndexToPhysicalPoint(ix, pointColumn); + + AssembleUtil::GetEligibleIndiciesForSortedColumn(pointColumn, min_, max_, iStartTile, iEndTile); + + if (iStartTile > iEndTile) + { + continue; + } + + for (ix[0] = origin[0] + thread_offset_; ix[0] < x_end; ix[0] += thread_stride_) + { + pnt_t point; + mosaic_->TransformIndexToPhysicalPoint(ix, point); + + mask_t::PixelType mask_pixel = 0; + for (unsigned int iTile = iStartTile; iTile <= iEndTile; iTile++) + { + unsigned int k = iTile; + + // avoid undesirable distortion artifacts: + if (!inside_bbox(min_[k], max_[k], point)) + { + continue; + } + + pnt_t pt_k = transform_[k]->TransformPoint(point); + + typename imask_t::ContinuousIndexType index; + imask_[k]->ConvertPointToContinuousIndex(pt_k, index); + + // make sure the pixel maps into the image: + if (!imask_[k]->IsInsideBuffer(index)) + { + continue; + } + + // make sure the pixel maps into the image mask: + if (imask_[k]->Evaluate(index) == 0) + { + continue; + } + + mask_pixel = 255; //(1 << sizeof(typename mask_t::PixelType)) - 1; + break; + } + + mosaic_->SetPixel(ix, mask_pixel); + } + } + } + + // data members facilitating concurrent mosaic assembly: + unsigned int thread_offset_; + unsigned int thread_stride_; + + // output mosaic: + mask_t::Pointer & mosaic_; + + // number of tiles used for this mosaic (may be fewer than + // what's available from the tile vector): + unsigned int num_tiles_; + + // omitted tile flags: + const std::vector & omit_; + + // tile trasforms: + std::vector transform_; + + // tile masks + std::vector mask_; + + // tile mask interpolators: + std::vector imask_; + + // mosaic space tile bounding boxes: + std::vector min_; + std::vector max_; +}; + + +//---------------------------------------------------------------- +// make_mask_mt +// +// Assemble a mask for the entire mosaic. +// Individual tiles may be omitted. +// +template +mask_t::Pointer +make_mask_mt(unsigned int num_threads, + const mask_t::SpacingType & mosaic_sp, + const unsigned int num_images, + const std::vector & omit, + const std::vector & transform, + const std::vector & image_mask) +{ + if (num_threads == 1) + { + // use the old single-threaded code: + return make_mask_st(mosaic_sp, num_images, omit, transform, image_mask); + } + + // FIXME: make it multi-threaded: + WRAP(itk_terminator_t terminator("make_mask_mt")); + + // setup the image mask interpolators: + typedef itk::NearestNeighborInterpolateImageFunction imask_t; + std::vector imask(num_images); + imask.assign(num_images, typename imask_t::Pointer(NULL)); + + for (unsigned int i = 0; i < image_mask.size() && i < num_images; i++) + { + if (image_mask[i].GetPointer() == NULL) + continue; + + imask[i] = imask_t::New(); + imask[i]->SetInputImage(image_mask[i]); + } + + // image space bounding boxes: + typedef mask_t::PointType pnt_t; + std::vector bbox_min(num_images); + std::vector bbox_max(num_images); + calc_image_bboxes(image_mask, bbox_min, bbox_max); + + // mosaic space bounding boxes: + std::vector MIN(num_images); + std::vector MAX(num_images); + calc_mosaic_bboxes(transform, bbox_min, bbox_max, MIN, MAX); + + // mosiac bounding box: + pnt_t mosaic_min; + pnt_t mosaic_max; + calc_mosaic_bbox(MIN, MAX, mosaic_min, mosaic_max); + + // setup the mosaic image: + mask_t::Pointer mosaic = mask_t::New(); + mask_t::RegionType::SizeType mosaic_sz; + mosaic_sz[0] = (unsigned int)((mosaic_max[0] - mosaic_min[0]) / mosaic_sp[0]); + mosaic_sz[1] = (unsigned int)((mosaic_max[1] - mosaic_min[1]) / mosaic_sp[1]); + mosaic->SetOrigin(pnt2d(mosaic_min[0], mosaic_min[1])); + mosaic->SetRegions(mosaic_sz); + mosaic->SetSpacing(mosaic_sp); + mosaic->Allocate(); + + // setup transactions for multi-threaded mosaic assembly: + std::list schedule; + for (unsigned int i = 0; i < num_threads; i++) + { + assemble_mask_t * t = new assemble_mask_t( + i, num_threads, mosaic, num_images, omit, transform, image_mask, imask, MIN, MAX); + + schedule.push_back(t); + } + + // setup the thread pool: + the_thread_pool_t thread_pool(num_threads); + thread_pool.set_idle_sleep_duration(50); // 50 usec + thread_pool.push_back(schedule); + thread_pool.pre_distribute_work(); + + // execute mosaic assembly transactions: + suspend_itk_multithreading_t suspend_itk_mt; + thread_pool.start(); + thread_pool.wait(); + + // done: + return mosaic; +} + +//---------------------------------------------------------------- +// make_mask +// +template +mask_t::Pointer +make_mask(const mask_t::SpacingType & mosaic_sp, + const unsigned int num_images, + const std::vector & omit, + const std::vector & transform, + const std::vector & image_mask, + unsigned int num_threads = std::thread::hardware_concurrency()) +{ + return make_mask_mt(num_threads, mosaic_sp, num_images, omit, transform, image_mask); +} + +//---------------------------------------------------------------- +// make_mask +// +// Assemble a mask for the entire mosaic. +// +template +mask_t::Pointer +make_mask(const std::vector & transform, + const std::vector & image_mask, + unsigned int num_threads = std::thread::hardware_concurrency()) +{ + const unsigned int num_images = transform.size(); + return make_mask( + image_mask[0]->GetSpacing(), num_images, std::vector(num_images, false), transform, image_mask, num_threads); +} + +//---------------------------------------------------------------- +// make_mosaic +// +// Assemble the entire mosaic. +// +template +typename IMG::Pointer +make_mosaic( + const feathering_t feathering, + const std::vector & transform, + const std::vector & image, + const std::vector & image_mask = std::vector(0), + bool dont_allocate = false) +{ + const unsigned int num_images = transform.size(); + return make_mosaic(image[0]->GetSpacing(), + num_images, + std::vector(num_images, false), // omit + transform, + image, + + // optional image masks: + image_mask, + + // feathering to reduce image blurring is optional: + feathering, + 0.0, + dont_allocate); +} + +//---------------------------------------------------------------- +// warp +// +// Return a copy of a given image warped according to +// a given transform. +// +template +typename T::Pointer +warp(typename T::ConstPointer a, base_transform_t::ConstPointer t) +{ + std::vector image(1); + image[0] = a; + + std::vector transform(1); + transform[0] = t; + + return make_mosaic>( + image, transform, FEATHER_NONE_E); +} + +//---------------------------------------------------------------- +// resize +// +// scale = 0.5 ---> reduce image in half along each dimension +// scale = 2.0 ---> double the image size along each dimension +// +template +typename T::Pointer +resize(typename T::ConstPointer in, double scale) +{ + typedef itk::ScaleTransform scale_t; + scale_t::Pointer transform = scale_t::New(); + scale_t::ScaleType s = transform->GetScale(); + s[0] = 1.0 / scale; + s[1] = 1.0 / scale; + transform->SetScale(s); + return warp(in, (base_transform_t::ConstPointer)transform); +} + +//---------------------------------------------------------------- +// resize +// +// Calculate a scale factor for a given image, such that +// the largest dimension of the image is equal to max_edge_size. +// Return a scaled copy of the image. +// +template +typename T::Pointer +resize(typename T::ConstPointer in, unsigned int max_edge_size, double & scale) +{ + typename T::RegionType::SizeType sz = in->GetLargestPossibleRegion().GetSize(); + + double xScale = double(max_edge_size) / double(sz[0]); + double yScale = double(max_edge_size) / double(sz[1]); + scale = xScale < yScale ? xScale : yScale; + return resize(in, scale); +} + +//---------------------------------------------------------------- +// resize +// +// Scale a given image such that the largest dimension +// of the image is equal to max_edge_size. +// Return a scaled copy of the image. +// +template +typename T::Pointer +resize(const T * in, unsigned int max_edge_size) +{ + double scale; + return resize(in, max_edge_size, scale); +} + + +//---------------------------------------------------------------- +// align_fi_mi +// +// Warp images fi and mi. Both warped images will have the same +// dimensions as a mosaic assembled from fi and mi. +// +template +void +align_fi_mi(const T * fi, + const T * mi, + const base_transform_t * mi_transform, + typename T::Pointer & fi_aligned, + typename T::Pointer & mi_aligned) +{ + std::vector image(2); + image[0] = fi; + image[1] = mi; + + std::vector transform(2); + transform[0] = identity_transform_t::New(); + transform[1] = mi_transform; + + fi_aligned = + make_mosaic(image, transform, FEATHER_NONE_E, 1); + + mi_aligned = + make_mosaic(image, transform, FEATHER_NONE_E, 0); +} + +//---------------------------------------------------------------- +// save_composite +// +// Save an RGB image of images fi and mi registered via +// a given transform. The mi image will be saved in the red channel, +// and fi will be saved in the green and blue channels. +// +template +void +save_composite(const char * filename, const T * fi, const T * mi, const base_transform_t * xform, bool blab = true) +{ + typename T::Pointer fi_aligned; + typename T::Pointer mi_aligned; + align_fi_mi(fi, mi, xform, fi_aligned, mi_aligned); + + typedef itk::CastImageFilter cast_to_native_t; + typename cast_to_native_t::Pointer fi_native = cast_to_native_t::New(); + typename cast_to_native_t::Pointer mi_native = cast_to_native_t::New(); + fi_native->SetInput(fi_aligned); + mi_native->SetInput(mi_aligned); + + typedef itk::RGBPixel composite_pixel_t; + typedef itk::Image composite_image_t; + typedef itk::ComposeImageFilter compose_filter_t; + + compose_filter_t::Pointer composer = compose_filter_t::New(); + composer->SetInput1(mi_native->GetOutput()); + composer->SetInput2(fi_native->GetOutput()); + composer->SetInput3(fi_native->GetOutput()); + + typedef itk::ImageFileWriter composite_writer_t; + composite_writer_t::Pointer writer = composite_writer_t::New(); + writer->SetFileName(filename); + writer->SetInput(composer->GetOutput()); + + if (blab) + cout << "saving " << filename << endl; + writer->Update(); +} + +//---------------------------------------------------------------- +// make_mosaic_rgb +// +// Assemble a portion of the mosaic positioned at mosaic_min. +// Each tile may be individually tinted with an RGB color. +// Individual tiles may be omitted. +// Background color (outside the mask) may be specified. +// Tile masks are optional and may be NULL. +// +template +void +make_mosaic_rgb(typename IMG::Pointer * mosaic, + const typename IMG::SpacingType & mosaic_sp, + const typename IMG::PointType & mosaic_min, + const typename IMG::SizeType & mosaic_sz, + const xyz_t & background_color, + const std::vector & color, + const std::vector & omit, + const std::vector & transform, + const std::vector & image, + + // optional image masks: + const std::vector & image_mask = std::vector(0), + + // feathering to reduce image blurring is optional: + const feathering_t feathering = FEATHER_NONE_E) +{ + WRAP(itk_terminator_t terminator("make_mosaic_rgb")); + unsigned int num_images = image.size(); + + for (unsigned int i = 0; i < 3; i++) + { + std::vector tint(num_images); + for (unsigned int j = 0; j < num_images; j++) + { + tint[j] = color[j][i] / 255.0; + } + + mosaic[i] = + make_mosaic>(mosaic_sp, + mosaic_min, + mosaic_sz, + num_images, + omit, + tint, + transform, + image, + image_mask, + feathering, + background_color[i]); + } +} + +//---------------------------------------------------------------- +// make_mosaic_rgb +// +// Assemble a portion of the mosaic bounded by +// mosaic_min and mosaic_max. +// +// Each tile may be individually tinted with an RGB color. +// Individual tiles may be omitted. +// Background color (outside the mask) may be specified. +// Tile masks are optional and may be NULL. +// +template +void +make_mosaic_rgb(typename IMG::Pointer * mosaic, + const typename IMG::SpacingType & mosaic_sp, + const typename IMG::PointType & mosaic_min, + const typename IMG::PointType & mosaic_max, + const xyz_t & background_color, + const std::vector & color, + const std::vector & omit, + const std::vector & transform, + const std::vector & image, + + // optional image masks: + const std::vector & image_mask = std::vector(0), + + // feathering to reduce image blurring is optional: + const feathering_t feathering = FEATHER_NONE_E) +{ + typename IMG::SizeType mosaic_sz; + mosaic_sz[0] = (unsigned int)((mosaic_max[0] - mosaic_min[0]) / mosaic_sp[0]); + mosaic_sz[1] = (unsigned int)((mosaic_max[1] - mosaic_min[1]) / mosaic_sp[1]); + + make_mosaic_rgb( + mosaic, mosaic_sp, mosaic_min, mosaic_sz, background_color, color, omit, transform, image, image_mask, feathering); +} + +//---------------------------------------------------------------- +// make_mosaic_rgb +// +// Assemble the entire mosaic. +// Each tile may be individually tinted with an RGB color. +// Individual tiles may be omitted. +// Background color (outside the mask) may be specified. +// Tile masks are optional and may be NULL. +// +template +void +make_mosaic_rgb(typename IMG::Pointer * mosaic, + const xyz_t background_color, + const std::vector color, + const std::vector & omit, + const std::vector & transform, + const std::vector & image, + + // optional image masks: + const std::vector & image_mask = std::vector(0), + + // feathering to reduce image blurring is optional: + const feathering_t feathering = FEATHER_NONE_E) +{ + // mosaic pixel spacing will be the same as for the first mosaic tile: + typename IMG::SpacingType mosaic_sp = image[0]->GetSpacing(); + + // mosaic bounding box: + typename IMG::PointType mosaic_min; + typename IMG::PointType mosaic_max; + calc_mosaic_bbox(transform, image, mosaic_min, mosaic_max); + + make_mosaic_rgb( + mosaic, mosaic_sp, mosaic_min, mosaic_max, background_color, color, omit, transform, image, image_mask, feathering); +} + +//---------------------------------------------------------------- +// make_colors +// +// Generate a scrambled rainbow colormap. +// +extern void +make_colors(const unsigned int & num_color, std::vector & color); + +//---------------------------------------------------------------- +// make_mosaic_rgb +// +// Assemble the entire mosaic. +// Each tile will be tinted with an RGB color +// from a scrambled rainbox colormap. +// Individual tiles may be omitted. +// Background color (outside the mask) may be specified. +// Tile masks are optional and may be NULL. +// +template +void +make_mosaic_rgb(typename IMG::Pointer * mosaic, + const std::vector & omit, + const std::vector & transform, + const std::vector & image, + + // optional image masks: + const std::vector & image_mask = std::vector(0), + + // feathering to reduce image blurring is optional: + const feathering_t feathering = FEATHER_NONE_E, + + // background color: + double background = 255.0, + + // should the first tile be white? + bool first_tile_white = false) +{ + static const xyz_t EAST = xyz(1, 0, 0); + static const xyz_t NORTH = xyz(0, 1, 0); + static const xyz_t WEST = xyz(0, 0, 1); + static const xyz_t SOUTH = xyz(0, 0, 0); + + unsigned int num_images = image.size(); + xyz_t background_color = xyz(background, background, background); + std::vector color; + + if (first_tile_white) + { + std::vector tmp; + make_colors(num_images - 1, tmp); + color.resize(num_images); + color[0] = xyz(255, 255, 255); + for (unsigned int i = 1; i < num_images; i++) + { + color[i] = tmp[i - 1]; + } + } + else + { + make_colors(num_images, color); + } + + make_mosaic_rgb(mosaic, background_color, color, omit, transform, image, image_mask, feathering); +} + +//---------------------------------------------------------------- +// make_mosaic_rgb +// +// Assemble the entire mosaic. +// Each tile will be tinted with an RGB color +// from a scrambled rainbox colormap. +// Background color (outside the mask) may be specified. +// Tile masks are optional and may be NULL. +// +template +void +make_mosaic_rgb( + typename IMG::Pointer * mosaic, + const feathering_t feathering, + const std::vector & transform, + const std::vector & image, + const std::vector & image_mask = std::vector(0), + const double background = 255.0, + bool first_tile_white = false) +{ + make_mosaic_rgb(mosaic, + std::vector(transform.size(), false), + transform, + image, + image_mask, + feathering, + background, + first_tile_white); +} + +//---------------------------------------------------------------- +// to_rgb +// +// Make an RGB image from a given grayscale image +// +template +void +to_rgb(const T * image, native_image_t::Pointer * rgb) +{ + rgb[0] = cast(remap_min_max(image, 0.0, 255.0)); + rgb[1] = cast(rgb[0]); + rgb[2] = cast(rgb[0]); +} + +//---------------------------------------------------------------- +// save_rgb +// +// Save and RGB image specified by the individual color channel images. +// +template +void +save_rgb(const image_ptr_t * image, const char * filename, bool blab = true) +{ + typedef typename image_ptr_t::ObjectType::Pointer::ObjectType IMG; + + typedef itk::RGBPixel composite_pixel_t; + typedef itk::Image composite_image_t; + typedef itk::ComposeImageFilter compose_filter_t; + + typename compose_filter_t::Pointer composer = compose_filter_t::New(); + composer->SetInput1(image[0]); + composer->SetInput2(image[1]); + composer->SetInput3(image[2]); + + typedef itk::ImageFileWriter composite_writer_t; + typename composite_writer_t::Pointer writer = composite_writer_t::New(); + writer->SetFileName(filename); + writer->SetInput(composer->GetOutput()); + + if (blab) + cout << "saving " << filename << endl; + writer->Update(); +} + +//---------------------------------------------------------------- +// save_mosaic_rgb +// +// Assemble the entire mosaic. +// Each tile will be tinted with an RGB color +// from a scrambled rainbox colormap. +// Background color (outside the mask) may be specified. +// Tile masks are optional and may be NULL. +// +// Save the resulting mosaic +// +template +void +save_mosaic_rgb(const char * filename, + const feathering_t feathering, + const std::vector & transform, + const std::vector & image, + const std::vector & image_mask = std::vector(0), + const double background = 255.0, + bool first_tile_white = false, + bool blab = true) +{ + typedef typename image_pointer_t::ObjectType::Pointer::ObjectType IMG; + typename IMG::Pointer mosaic[3]; + make_mosaic_rgb( + mosaic, feathering, transform, image, image_mask, background, first_tile_white); + save_rgb(mosaic, filename, blab); +} + +//---------------------------------------------------------------- +// save_rgb +// +// Save an RGB image of images fi and mi registered via +// a given transform. +// +template +void +save_rgb(const char * fn_save, + const T * fi, + const T * mi, + const base_transform_t * xform, + const mask_t * fi_mask = 0, + const mask_t * mi_mask = 0, + bool blab = true) +{ + std::vector image(2); + std::vector mask(2); + std::vector transform(2); + + image[0] = fi; + image[1] = mi; + + mask[0] = fi_mask; + mask[1] = mi_mask; + + transform[0] = identity_transform_t::New(); + transform[1] = xform; + + typename T::Pointer mosaic[3]; + make_mosaic_rgb( + mosaic, std::vector(2, false), transform, image, mask, FEATHER_NONE_E, 255.0, false); + + save_rgb(mosaic, fn_save, blab); +} + + +//---------------------------------------------------------------- +// inverse +// +// Return an inverse of a given translation transform. +// +inline static translate_transform_t::Pointer +inverse(const translate_transform_t * transform) +{ + translate_transform_t::Pointer inv; + if (transform != nullptr) + { + inv = translate_transform_t::New(); + inv->SetOffset(neg(transform->GetOffset())); + } + + return inv; +} + + +//---------------------------------------------------------------- +// remap +// +// Re-arrange a data vector according to a given mapping +// +template +void +remap(const std::list & mapping, const std::vector & in, std::vector & out) +{ + unsigned int size = mapping.size(); + out.resize(size); + + typename std::list::const_iterator iter = mapping.begin(); + for (unsigned int i = 0; i < size; i++, ++iter) + { + out[i] = in[*iter]; + } +} + +//---------------------------------------------------------------- +// remap +// +// Re-arrange a data list according to a given mapping +// +template +void +remap(const std::list & mapping, const std::list & in, std::list & out) +{ + unsigned int size = in.size(); + + // setup rapid access to the list elements: + std::vector rapid(size); + { + typename std::list::const_iterator iter = in.begin(); + for (unsigned int i = 0; i < size; i++, ++iter) + { + rapid[i] = &(*iter); + } + } + + // swap the list elements: + out.clear(); + typename std::list::const_iterator iter = mapping.begin(); + for (; iter != mapping.end(); ++iter) + { + out.push_back(*(rapid[*iter])); + } +} + +//---------------------------------------------------------------- +// remap +// +// Return a re-arranged data container according to a given mapping. +// +template +container_t +remap(const std::list & mapping, const container_t & container) +{ + container_t out; + remap(mapping, container, out); + return out; +} + + +//---------------------------------------------------------------- +// mark +// +// Draw a cross mark in the given image. +// +template +void +mark(typename T::Pointer & image, + const typename T::IndexType & index, + const typename T::PixelType & mark_value, + const int arm_length = 2, + const char symbol = '+') +{ + typedef typename T::SpacingType spacing_t; + typedef typename T::RegionType::SizeType image_size_t; + typedef typename T::IndexType index_t; + + image_size_t sz = image->GetLargestPossibleRegion().GetSize(); + index_t xy; + + for (int j = -arm_length; j <= arm_length; j++) + { + int x = index[0] + j; + int y = index[1] + j; + + if (symbol == '+') + { + // draw a cross: + xy[0] = x; + xy[1] = index[1]; + if (xy[0] >= 0 && image_size_value_t(xy[0]) < sz[0] && xy[1] >= 0 && image_size_value_t(xy[1]) < sz[1]) + { + image->SetPixel(xy, mark_value); + } + + xy[0] = index[0]; + xy[1] = y; + if (xy[0] >= 0 && image_size_value_t(xy[0]) < sz[0] && xy[1] >= 0 && image_size_value_t(xy[1]) < sz[1]) + { + image->SetPixel(xy, mark_value); + } + } + else + { + // draw a diagonal cross: + xy[0] = x; + xy[1] = y; + if (xy[0] >= 0 && image_size_value_t(xy[0]) < sz[0] && xy[1] >= 0 && image_size_value_t(xy[1]) < sz[1]) + { + image->SetPixel(xy, mark_value); + } + + xy[1] = index[1] - j; + if (xy[0] >= 0 && image_size_value_t(xy[0]) < sz[0] && xy[1] >= 0 && image_size_value_t(xy[1]) < sz[1]) + { + image->SetPixel(xy, mark_value); + } + } + } +} + +//---------------------------------------------------------------- +// mark +// +// Draw a cross mark in the given image. +// +template +void +mark(typename T::Pointer & image, + const pnt2d_t & mark_coords, + const typename T::PixelType & mark_value, + const int arm_length = 2, + const char symbol = '+') +{ + typedef typename T::IndexType index_t; + + index_t index; + if (!image->TransformPhysicalPointToIndex(mark_coords, index)) + { + // the mark lays outside of the image: + return; + } + + mark(image, index, mark_value, arm_length, symbol); +} + +//---------------------------------------------------------------- +// fill +// +// Fill a portion of the image with a constant value. +// +template +void +fill(typename T::Pointer & a, + unsigned int x, + unsigned int y, + unsigned int w, + unsigned int h, + const typename T::PixelType & fill_value = itk::NumericTraits::Zero) +{ + WRAP(itk_terminator_t terminator("fill")); + + // shortcuts: + typedef typename T::IndexType ix_t; + typedef typename T::RegionType rn_t; + typedef typename T::SizeType sz_t; + + sz_t sz = a->GetLargestPossibleRegion().GetSize(); + + ix_t origin; + origin[0] = x; + origin[1] = y; + + sz_t extent; + extent[0] = w; + extent[1] = h; + + rn_t region; + region.SetIndex(origin); + region.SetSize(extent); + + typedef typename itk::ImageRegionIterator iter_t; + iter_t iter(a, region); + for (iter.GoToBegin(); !iter.IsAtEnd(); ++iter) + { + // make sure there hasn't been an interrupt: + WRAP(terminator.terminate_on_request()); + + ix_t ix = iter.GetIndex(); + if (image_size_value_t(ix[0]) >= sz[0] || image_size_value_t(ix[1]) >= sz[1]) + { + continue; + } + + iter.Set(fill_value); + } +} + +//---------------------------------------------------------------- +// save_transform +// +// Save an ITK transform to a stream. +// +extern void +save_transform(std::ostream & so, const itk::TransformBase * t); + +//---------------------------------------------------------------- +// load_transform +// +// Load an ITK transform of specified type from a stream. +// +extern itk::TransformBase::Pointer +load_transform(std::istream & si, const std::string & transform_type); + +//---------------------------------------------------------------- +// load_transform +// +// Load transform type from the stream, then load the transform +// of that type. +// +extern itk::TransformBase::Pointer +load_transform(std::istream & si); + + +//---------------------------------------------------------------- +// save_mosaic +// +// Save image filenames and associated ITK transforms to a stream. +// +extern void +save_mosaic(std::ostream & so, + const unsigned int & num_images, + const double & pixel_spacing, + const bool & use_std_mask, + const the_text_t * image, + const itk::TransformBase ** transform); + +//---------------------------------------------------------------- +// load_mosaic +// +// Load image filenames and associated ITK transforms from a stream. +// +extern void +load_mosaic(std::istream & si, + double & pixel_spacing, + bool & use_std_mask, + std::vector & image, + std::vector & transform); + + +//---------------------------------------------------------------- +// save_mosaic +// +// Save tile image names and associated ITK transforms to a stream. +// +template +void +save_mosaic(std::ostream & so, + const double & pixel_spacing, + const bool & use_std_mask, + const std::list & images, + const std::vector & transforms) +{ + unsigned int num_images = transforms.size(); + std::vector image(num_images); + std::vector tbase(num_images); + + std::vector & ttmp = + const_cast &>(transforms); + + std::list::const_iterator iter = images.begin(); + for (unsigned int i = 0; i < images.size(); i++, ++iter) + { + image[i] = *iter; + tbase[i] = ttmp[i].GetPointer(); + } + + save_mosaic(so, image.size(), pixel_spacing, use_std_mask, &(image[0]), &(tbase[0])); +} + +//---------------------------------------------------------------- +// load_mosaic +// +// Load tile image names and associated ITK transforms from a stream. +// +template +void +load_mosaic(std::istream & si, + double & pixel_spacing, + bool & use_std_mask, + std::list & images, + std::vector & transforms, + const the_text_t & image_path) +{ + std::vector image; + std::vector tbase; + + load_mosaic(si, pixel_spacing, use_std_mask, image, tbase); + transforms.resize(tbase.size()); + for (unsigned int i = 0; i < tbase.size(); i++) + { + images.push_back(image[i]); + transforms[i] = dynamic_cast(tbase[i].GetPointer()); + } + + if (image_path.is_empty()) + return; + + // Replace global paths for backwards compatibility. + for (std::list::iterator iter = images.begin(); iter != images.end(); ++iter) + { + IRPath::CleanSlashes((*iter)); + (*iter) = IRPath::CleanPath(image_path) + IRPath::FilenameFromPath(*iter); + } +} + +//---------------------------------------------------------------- +// histogram_equalization +// +// Histogram Equalization. +// Transformed image will be mapped into the new MIN/MAX pixel +// range. If the new range is not specified, pixels will be +// remapped into the original range. +// +template +typename T::Pointer +histogram_equalization(const T * in, + const unsigned int bins = 256, + typename T::PixelType new_min = std::numeric_limits::max(), + typename T::PixelType new_max = -std::numeric_limits::max(), + const mask_t * mask = NULL) +{ + // local typedefs: + typedef typename T::RegionType rn_t; + typedef typename T::IndexType ix_t; + typedef typename T::SizeType sz_t; + typedef typename T::PixelType pixel_t; + + // range of the image intensities: + pixel_t p_min; + pixel_t p_max; + image_min_max(in, p_min, p_max, mask); + pixel_t p_rng = p_max - p_min; + + typename T::Pointer image = cast(in); + + if (p_rng == pixel_t(0)) + { + // nothing to do: + return image; + } + + if (new_min == std::numeric_limits::max()) + new_min = p_min; + if (new_max == -std::numeric_limits::max()) + new_max = p_max; + pixel_t new_rng = new_max - new_min; + + sz_t sz = in->GetLargestPossibleRegion().GetSize(); + + // initialize the histogram: + std::vector pdf(bins); + std::vector clipped_pdf(bins); + std::vector cdf(bins); + + for (unsigned int i = 0; i < bins; i++) + { + pdf[i] = 0; + clipped_pdf[i] = 0.0; + cdf[i] = 0.0; + } + + typedef typename itk::ImageRegionConstIteratorWithIndex iter_t; + iter_t iter(in, in->GetLargestPossibleRegion()); + for (iter.GoToBegin(); !iter.IsAtEnd(); ++iter) + { + ix_t ix = iter.GetIndex(); + + if (pixel_in_mask(in, mask, ix)) + { + pixel_t p = iter.Get(); + unsigned int bin = (unsigned int)(double(p - p_min) / double(p_rng) * double(bins - 1)); + pdf[bin]++; + } + } + + // build a cumulative distribution function (CDF): + cdf[0] = double(pdf[0]); + for (unsigned int i = 1; i < bins; i++) + { + cdf[i] = cdf[i - 1] + double(pdf[i]); + } + + // update the image: + iter = iter_t(in, in->GetLargestPossibleRegion()); + for (iter.GoToBegin(); !iter.IsAtEnd(); ++iter) + { + ix_t ix = iter.GetIndex(); + + // generate the output pixel: + pixel_t p_out = new_min; + if (pixel_in_mask(in, mask, ix)) + { + pixel_t p_in = iter.Get(); + unsigned int bin = (unsigned int)((double(p_in - p_min) / double(p_rng)) * double(bins - 1)); + p_out = pixel_t(double(new_min) + double(new_rng) * double(cdf[bin]) / double(cdf[bins - 1])); + } + + image->SetPixel(ix, p_out); + } + + return image; +} + + +//---------------------------------------------------------------- +// clip_histogram +// +// This is used by CLAHE to limit the contrast ratio slope. +// +extern void +clip_histogram(const double & clipping_height, + const unsigned int & pdf_size, + const unsigned int * pdf, + double * clipped_pdf); + +//---------------------------------------------------------------- +// CLAHE +// +// Perform Contrast-Limited Adaptive Histogram Equalization +// Transformed image will be mapped into the new MIN/MAX pixel +// range. If the new range is not specified, pixels will be +// remapped into the original range. +// +template +typename T::Pointer +CLAHE(const T * in, + int nx, + int ny, + double max_slope, + const unsigned int bins = 256, + typename T::PixelType new_min = std::numeric_limits::max(), + typename T::PixelType new_max = -std::numeric_limits::max(), + const mask_t * mask = NULL) +{ + // local typedefs: + typedef typename T::RegionType rn_t; + typedef typename T::IndexType ix_t; + typedef typename T::SizeType sz_t; + typedef typename T::PixelType pixel_t; + + // sanity check: + assert(nx > 1 && ny > 1); + assert(max_slope >= 1.0 || max_slope == 0.0); + + // range of the image intensities: + pixel_t p_min; + pixel_t p_max; + image_min_max(in, p_min, p_max, mask); + pixel_t p_rng = p_max - p_min; + + typename T::Pointer image = cast(in); + + if (p_rng == pixel_t(0)) + { + // nothing to do: + return image; + } + + if (new_min == std::numeric_limits::max()) + new_min = p_min; + if (new_max == -std::numeric_limits::max()) + new_max = p_max; + pixel_t new_rng = new_max - new_min; + + sz_t sz = in->GetLargestPossibleRegion().GetSize(); + const int image_w = sz[0]; + const int image_h = sz[1]; + + // make sure the histogram window doesn't exceed the image: + nx = std::min(nx, image_w); + ny = std::min(ny, image_h); + + // calculate the histogram window center: + const int cx = nx / 2; + const int cy = ny / 2; + + // initialize the histogram: + std::vector pdf(bins); + std::vector clipped_pdf(bins); + std::vector cdf(bins); + + for (unsigned int i = 0; i < bins; i++) + { + pdf[i] = 0; + clipped_pdf[i] = 0.0; + cdf[i] = 0.0; + } + + for (int x = 0; x < nx; x++) + { + for (int y = 0; y < ny; y++) + { + ix_t ix; + ix[0] = x; + ix[1] = y; + + if (pixel_in_mask(in, mask, ix)) + { + pixel_t p = in->GetPixel(ix); + unsigned int bin = (unsigned int)(double(p - p_min) / double(p_rng) * double(bins - 1)); + pdf[bin]++; + } + } + } + + // clip the histogram: + if (max_slope != 0.0) + { + double clipping_height = (double(nx * ny) / double(bins)) * max_slope; + clip_histogram(clipping_height, bins, &(pdf[0]), &(clipped_pdf[0])); + } + else + { + for (unsigned int i = 0; i < bins; i++) + { + clipped_pdf[i] = double(pdf[i]); + } + } + + // build a cumulative distribution function (CDF): + cdf[0] = double(clipped_pdf[0]); + for (unsigned int i = 1; i < bins; i++) + { + cdf[i] = cdf[i - 1] + double(clipped_pdf[i]); + } + + // start at the origin: + int hist_x = cx; + int hist_y = cy; +#if 1 + for (int x = 0; x < image_w; x++) + { + // alternate the direction of the scanline traversal: + int y0 = (x % 2 == 0) ? 0 : image_h - 1; + int y1 = (x % 2 == 0) ? image_h - 1 : 0; + int dy = (x % 2 == 0) ? 1 : -1; + + for (int y = y0; y != (y1 + dy); y += dy) + { +#else + /* + for (int y = 0; y < image_h; y++) + { + // alternate the direction of the scanline traversal: + int x0 = (y % 2 == 0) ? 0 : image_w - 1; + int x1 = (y % 2 == 0) ? image_w - 1 : 0; + int dx = (y % 2 == 0) ? 1 : -1; + + for (int x = x0; x != (x1 + dx); x += dx) + { + */ +#endif + int spill_x0 = std::max(0, cx - x); + int spill_y0 = std::max(0, cy - y); + int spill_x1 = std::max(0, x - (image_w - nx + cx)); + int spill_y1 = std::max(0, y - (image_h - ny + cy)); + + int new_hx = x + spill_x0 - spill_x1; + int new_hy = y + spill_y0 - spill_y1; + + int shift_x = new_hx - hist_x; + int shift_y = new_hy - hist_y; + + if (shift_x != 0) + { + // update the histogram (add-remove columns): + assert(shift_x == 1 || shift_x == -1); + + for (int ty = 0; ty < ny; ty++) + { + ix_t ix; + ix[1] = hist_y + ty - cy; + + // add: + ix[0] = (shift_x > 0) ? new_hx - cx + nx - 1 : new_hx - cx; + + if (pixel_in_mask(in, mask, ix)) + { + pixel_t a = in->GetPixel(ix); + unsigned int bin = (unsigned int)((double(a - p_min) / double(p_rng)) * double(bins - 1)); + pdf[bin]++; + } + + // remove: + ix[0] = (shift_x > 0) ? hist_x - cx : hist_x - cx + nx - 1; + if (pixel_in_mask(in, mask, ix)) + { + pixel_t d = in->GetPixel(ix); + unsigned int bin = (unsigned int)((double(d - p_min) / double(p_rng)) * double(bins - 1)); + pdf[bin]--; + } + } + + hist_x = new_hx; + } + + if (shift_y != 0) + { + // update the histogram (add-remove rows): + assert(shift_y == 1 || shift_y == -1); + + for (int tx = 0; tx < nx; tx++) + { + ix_t ix; + ix[0] = hist_x + tx - cx; + + // add: + ix[1] = (shift_y > 0) ? new_hy - cy + ny - 1 : new_hy - cy; + if (pixel_in_mask(in, mask, ix)) + { + pixel_t a = in->GetPixel(ix); + unsigned int bin = (unsigned int)((double(a - p_min) / double(p_rng)) * double(bins - 1)); + pdf[bin]++; + } + + // remove: + ix[1] = (shift_y > 0) ? hist_y - cy : hist_y - cy + ny - 1; + if (pixel_in_mask(in, mask, ix)) + { + pixel_t d = in->GetPixel(ix); + unsigned int bin = (unsigned int)((double(d - p_min) / double(p_rng)) * double(bins - 1)); + pdf[bin]--; + } + } + + hist_y = new_hy; + } + + if (shift_x != 0 || shift_y != 0) + { + // clip the histogram: + if (max_slope != 0.0) + { + double clipping_height = (double(nx * ny) / double(bins)) * max_slope; + clip_histogram(clipping_height, bins, &(pdf[0]), &(clipped_pdf[0])); + } + else + { + for (unsigned int i = 0; i < bins; i++) + { + clipped_pdf[i] = double(pdf[i]); + } + } + + // build a cumulative distribution function (CDF): + cdf[0] = double(clipped_pdf[0]); + for (unsigned int i = 1; i < bins; i++) + { + cdf[i] = cdf[i - 1] + double(clipped_pdf[i]); + } + } + + // generate the output pixel: + ix_t ix; + ix[0] = x; + ix[1] = y; + + pixel_t p_out = new_min; + if (pixel_in_mask(in, mask, ix)) + { + pixel_t p_in = in->GetPixel(ix); + unsigned int bin = (unsigned int)((double(p_in - p_min) / double(p_rng)) * double(bins - 1)); + p_out = pixel_t(double(new_min) + double(new_rng) * double(cdf[bin]) / double(cdf[bins - 1])); + } + + image->SetPixel(ix, p_out); + } + } + + return image; +} + + +//---------------------------------------------------------------- +// median +// +// Return a copy of a given image de-noised with a median filter. +// +template +typename T::Pointer +median(const T * a, const unsigned int & median_radius) +{ + typedef itk::MedianImageFilter filter_t; + typename filter_t::Pointer filter = filter_t::New(); + typename filter_t::InputSizeType radius; + radius[0] = median_radius; + radius[1] = median_radius; + filter->SetInput(a); + filter->SetRadius(radius); + + // FIXME: + // itk::SimpleFilterWatcher w(filter.GetPointer(), "median"); + + WRAP(terminator_t terminator(filter)); + filter->Update(); + return filter->GetOutput(); +} + + +//---------------------------------------------------------------- +// normalize +// +// Return a copy of the image normalized (pixels shifted and +// rescaled) using the +// +template +typename T::Pointer +normalize(const T * a, const mask_t * ma) +{ + typedef itk::NormalizeImageFilterWithMask filter_t; + typename filter_t::Pointer filter = filter_t::New(); + filter->SetInput(a); + filter->SetImageMask(mask_so(ma)); + + WRAP(terminator_t terminator(filter)); + filter->Update(); + return filter->GetOutput(); +} + +//---------------------------------------------------------------- +// calc_pixel_weight +// +// Calculate a pixel feathering weight used to blend mosaic tiles. +// +inline double +pixel_weight(const image_t::IndexType & MIN, const image_t::IndexType & MAX, const image_t::IndexType & ix) +{ + double w = double(MAX[0]) - double(MIN[0]); + double h = double(MAX[1]) - double(MIN[1]); + double r = 0.5 * ((w > h) ? h : w); + double sx = std::min(double(ix[0]) - double(MIN[0]), double(MAX[0]) - double(ix[0])); + double sy = std::min(double(ix[1]) - double(MIN[1]), double(MAX[1]) - double(ix[1])); + double s = (1.0 + std::min(sx, sy)) / (r + 1.0); + return s; +} + +//---------------------------------------------------------------- +// normalize +// +// Tiled image normalization -- mean shift and rescale +// pixel intensities to match a normal distribution. +// Tiles overlap and the overlap regions are blended together. +// Clip the pixels to the [-3, 3] range +// and remap into the new [MIN, MAX] range. +// +template +typename T::Pointer +normalize(const T * image, + const unsigned int cols, + const unsigned int rows, + typename T::PixelType new_min = std::numeric_limits::max(), + typename T::PixelType new_max = -std::numeric_limits::max(), + const mask_t * mask = NULL) +{ + WRAP(itk_terminator_t terminator("normalize")); + + if (new_min == std::numeric_limits::max() || + new_max == -std::numeric_limits::max()) + { + // range of the image intensities: + typename T::PixelType p_min; + typename T::PixelType p_max; + image_min_max(image, p_min, p_max, mask); + + if (new_min == std::numeric_limits::max()) + { + new_min = p_min; + } + + if (new_max == -std::numeric_limits::max()) + { + new_max = p_max; + } + } + + // split the image into a set of overlapping tiles: + typename T::SizeType sz = image->GetLargestPossibleRegion().GetSize(); + + double half_tw = double(sz[0]) / double(cols + 1); + double half_th = double(sz[1]) / double(rows + 1); + + typename T::Pointer out = cast(image); + + array2d(typename T::Pointer) tile; + resize(tile, cols, rows); + + array2d(mask_t::Pointer) tile_mask; + resize(tile_mask, cols, rows); + + array2d(typename T::IndexType) tile_min; + resize(tile_min, cols, rows); + + array2d(typename T::IndexType) tile_max; + resize(tile_max, cols, rows); + + for (unsigned int i = 0; i < cols; i++) + { + double x0 = double(i) * half_tw; + unsigned int ix0 = (unsigned int)(floor(x0)); + double x1 = double(ix0) + (x0 - double(ix0)) + 2.0 * half_tw; + unsigned int ix1 = std::min((unsigned int)(sz[0] - 1), (unsigned int)(ceil(x1))); + + for (unsigned int j = 0; j < rows; j++) + { + double y0 = double(j) * half_th; + unsigned int iy0 = (unsigned int)(floor(y0)); + double y1 = double(iy0) + (y0 - double(iy0)) + 2.0 * half_th; + unsigned int iy1 = std::min((unsigned int)(sz[1] - 1), (unsigned int)(ceil(y1))); + + tile_min[i][j][0] = ix0; + tile_min[i][j][1] = iy0; + tile_max[i][j][0] = ix1; + tile_max[i][j][1] = iy1; + + tile[i][j] = crop(image, tile_min[i][j], tile_max[i][j]); + tile_mask[i][j] = crop(mask, tile_min[i][j], tile_max[i][j]); + + typename T::Pointer v1 = normalize(tile[i][j], tile_mask[i][j]); + + typename T::Pointer v2 = threshold(v1, -3, 3, -3, 3); + + tile[i][j] = v2; + } + } + + // blend the tiles together: + typedef itk::ImageRegionIteratorWithIndex itex_t; + itex_t itex(out, out->GetLargestPossibleRegion()); + for (itex.GoToBegin(); !itex.IsAtEnd(); ++itex) + { + // make sure there hasn't been an interrupt: + WRAP(terminator.terminate_on_request()); + + typename T::IndexType ix = itex.GetIndex(); + + double wsum = 0.0; + double mass = 0.0; + for (unsigned int i = 0; i < cols; i++) + { + for (unsigned int j = 0; j < rows; j++) + { + if (ix[0] >= tile_min[i][j][0] && ix[0] <= tile_max[i][j][0] && ix[1] >= tile_min[i][j][1] && + ix[1] <= tile_max[i][j][1]) + { + typename T::IndexType index; + index[0] = ix[0] - tile_min[i][j][0]; + index[1] = ix[1] - tile_min[i][j][1]; + + double weight = pixel_weight(tile_min[i][j], tile_max[i][j], ix); + wsum += weight * tile[i][j]->GetPixel(index); + mass += weight; + } + } + } + + if (mass != 0.0) + wsum /= mass; + out->SetPixel(ix, wsum); + } + + remap_min_max_inplace(out, new_min, new_max); + return out; +} + + +//---------------------------------------------------------------- +// forward_transform +// +// Cascaded forward transform. This function assumes +// that it is given a vector of forward transforms. +// +template +typename T::PointType +forward_transform(const transform_vec_t & t, const unsigned int t_size, const typename T::PointType & in) +{ + typename T::PointType out = in; + for (unsigned int i = 0; i < t_size; i++) + { + out = t[i]->TransformPoint(out); + } + return out; +} + +//---------------------------------------------------------------- +// inverse_transform +// +// Cascaded inverse transform. This function assumes +// that it is given a vector of inverse transforms t_inverse, +// it will not call GetInverse on any of them. +// +template +typename T::PointType +inverse_transform(const transform_vec_t & t_inverse, const unsigned int t_size, const typename T::PointType & in) +{ + typename T::PointType out = in; + for (int i = t_size - 1; i >= 0; i--) + { + out = t_inverse[i]->TransformPoint(out); + } + return out; +} + + +//---------------------------------------------------------------- +// flip +// +// Flip an image around vertical and/or horizontal axis. +// +// flip_x -- flip around the vertical axis +// flip_y -- flip around the horizontal axis. +// +template +typename T::Pointer +flip(const T * in, const bool flip_x = true, const bool flip_y = false) +{ + WRAP(itk_terminator_t terminator("flip")); + + // local typedefs: + typedef typename T::RegionType rn_t; + typedef typename T::IndexType ix_t; + typedef typename T::SizeType sz_t; + + typename T::Pointer out = cast(in); + + if (flip_x || flip_y) + { + rn_t rn = in->GetLargestPossibleRegion(); + sz_t sz = rn.GetSize(); + + typedef itk::ImageRegionIteratorWithIndex itex_t; + itex_t itex(out, rn); + + for (itex.GoToBegin(); !itex.IsAtEnd(); ++itex) + { + // make sure there hasn't been an interrupt: + WRAP(terminator.terminate_on_request()); + + ix_t ix = itex.GetIndex(); + if (flip_x) + ix[0] = sz[0] - ix[0] - 1; + if (flip_y) + ix[1] = sz[1] - ix[1] - 1; + + itex.Set(in->GetPixel(ix)); + } + } + + return out; +} + + +//---------------------------------------------------------------- +// overlap_ratio +// +// Calculate the ratio of the overlap region area to the area +// of the smaller image. +// +template +double +overlap_ratio(const T * fi, const T * mi, const base_transform_t * fi_to_mi) +{ + if (fi_to_mi == NULL) + return 0.0; + + typename T::PointType fi_min = fi->GetOrigin(); + typename T::PointType mi_min = mi->GetOrigin(); + + typename T::SpacingType fi_sp = fi->GetSpacing(); + typename T::SpacingType mi_sp = mi->GetSpacing(); + + typename T::SizeType fi_sz = fi->GetLargestPossibleRegion().GetSize(); + typename T::SizeType mi_sz = mi->GetLargestPossibleRegion().GetSize(); + + typename T::PointType fi_max = fi_min + vec2d((fi_sz[0]) * fi_sp[0], (fi_sz[1]) * fi_sp[1]); + + typename T::PointType mi_max = mi_min + vec2d((mi_sz[0]) * mi_sp[0], (mi_sz[1]) * mi_sp[1]); + + const double w0 = fi_max[0] - fi_min[0]; + const double h0 = fi_max[1] - fi_min[1]; + + const double w1 = mi_max[0] - mi_min[0]; + const double h1 = mi_max[1] - mi_min[1]; + + const double smaller_area = std::min(w0, w1) * std::min(h0, h1); + + typename T::PointType ul = fi_to_mi->TransformPoint(fi_min); + ul[0] = std::max(0.0, std::min(w1, ul[0] - mi_min[0])); + ul[1] = std::max(0.0, std::min(h1, ul[1] - mi_min[1])); + + typename T::PointType lr = fi_to_mi->TransformPoint(fi_max); + lr[0] = std::max(0.0, std::min(w1, lr[0] - mi_min[0])); + lr[1] = std::max(0.0, std::min(h1, lr[1] - mi_min[1])); + + const double dx = lr[0] - ul[0]; + const double dy = lr[1] - ul[1]; + const double area_ratio = (dx * dy) / smaller_area; + + return area_ratio; +} + +//---------------------------------------------------------------- +// find_inverse +// +// Given a forward transform and image space coordinates, +// find mosaic space coordinates. +// +extern bool +find_inverse(const pnt2d_t & tile_min, // tile space + const pnt2d_t & tile_max, // tile space + const base_transform_t * mosaic_to_tile, // forward transform + const pnt2d_t & xy, // tile space + pnt2d_t & uv, // mosaic space + const unsigned int max_iterations = 100, + const double min_step_scale = 1e-12, + const double min_error_sqrd = 1e-16, + const unsigned int pick_up_pace_steps = 5); + +//---------------------------------------------------------------- +// find_inverse +// +// Given a forward transform, an approximate inverse transform, +// and image space coordinates, find mosaic space coordinates. +// +extern bool +find_inverse(const pnt2d_t & tile_min, // tile space + const pnt2d_t & tile_max, // tile space + const base_transform_t * mosaic_to_tile, // forward transform + const base_transform_t * tile_to_mosaic, // inverse transform + const pnt2d_t & xy, // tile space + pnt2d_t & uv, // mosaic space + const unsigned int max_iterations = 100, + const double min_step_scale = 1e-12, + const double min_error_sqrd = 1e-16, + const unsigned int pick_up_pace_steps = 5); + +//---------------------------------------------------------------- +// find_inverse +// +// Given a forward transform, an approximate inverse transform, +// and image space coordinates, find mosaic space coordinates. +// +extern bool +find_inverse(const base_transform_t * mosaic_to_tile, // forward transform + const base_transform_t * tile_to_mosaic, // inverse transform + const pnt2d_t & xy, // tile space + pnt2d_t & uv, // mosaic space + const unsigned int max_iterations = 100, + const double min_step_scale = 1e-12, + const double min_error_sqrd = 1e-16, + const unsigned int pick_up_pace_steps = 5); + +//---------------------------------------------------------------- +// generate_landmarks +// +// Given a forward transform, generate a set of image space +// coordinates and find their matching mosaic space coordinates. +// +// version 1 -- uniform jittered sampling over the tile +// version 2 -- non-uniform sampling skewed towards the center +// of the tile radially. This may be useful when +// the transform is ill-behaved at the tile edges. +// +extern bool +generate_landmarks(const pnt2d_t & tile_min, + const pnt2d_t & tile_max, + const mask_t * tile_mask, + const base_transform_t * mosaic_to_tile, + const unsigned int samples, + std::vector & xy, + std::vector & uv, + const unsigned int version, + const bool refine); + +//---------------------------------------------------------------- +// generate_landmarks +// +// Given a forward transform, generate a set of image space +// coordinates and find their matching mosaic space coordinates. +// +// version 1 -- uniform jittered sampling over the tile +// version 2 -- non-uniform sampling skewed towards the center +// of the tile radially. This may be useful when +// the transform is ill-behaved at the tile edges. +// +extern bool +generate_landmarks(const image_t * tile, + const mask_t * mask, + const base_transform_t * mosaic_to_tile, + const unsigned int samples, + std::vector & xy, + std::vector & uv, + const unsigned int version = 1, + const bool refine = false); + +//---------------------------------------------------------------- +// approx_transform +// +// Given a forward Legendre polynomial transform, solve for +// transform parameters of the inverse Legendre polynomial transform. +// +// version 1 -- uniform jittered sampling over the tile +// version 2 -- non-uniform sampling skewed towards the center +// of the tile radially. This may be useful when +// the transform is ill-behaved at the tile edges. +// +template +typename legendre_transform_t::Pointer +approx_transform(const pnt2d_t & tile_min, + const pnt2d_t & tile_max, + const mask_t * tile_mask, + const base_transform_t * forward, + const unsigned int samples = 16, + const unsigned int version = 1, + const bool refine = false) +{ + base_transform_t::Pointer inverse; + forward->GetInverse(inverse); + assert(inverse.GetPointer() != nullptr); + + // calculate the shift: + pnt2d_t center = + pnt2d(tile_min[0] + (tile_max[0] - tile_min[0]) / 2.0, tile_min[1] + (tile_max[1] - tile_min[1]) / 2.0); + vec2d_t shift = center - inverse->TransformPoint(center); + + typename legendre_transform_t::Pointer approx = setup_transform(tile_min, tile_max); + approx->setup_translation(shift[0], shift[1]); + + std::vector xy; + std::vector uv; + generate_landmarks(tile_min, tile_max, tile_mask, forward, samples, xy, uv, version, refine); + + unsigned int order = legendre_transform_t::Degree + 1; + unsigned int loworder = std::min(2u, order); + +#if 1 + approx->solve_for_parameters(0, loworder, uv, xy); + approx->solve_for_parameters(loworder, order - loworder, uv, xy); +#else + approx->solve_for_parameters(0, order, uv, xy); +#endif + + return approx; +} + +//---------------------------------------------------------------- +// solve_for_transform +// +// Given a forward transform and a gross translation vector for +// the inverse mapping, solve for transform parameters of the +// inverse Legendre polynomial transform. +// +// version 1 -- uniform jittered sampling over the tile +// version 2 -- non-uniform sampling skewed towards the center +// of the tile radially. This may be useful when +// the transform is ill-behaved at the tile edges. +// +template +void +solve_for_transform(const pnt2d_t & tile_min, + const pnt2d_t & tile_max, + const mask_t * tile_mask, + const base_transform_t * mosaic_to_tile_0, + typename legendre_transform_t::Pointer & mosaic_to_tile_1, + const vec2d_t & shift, + const unsigned int samples = 16, + const unsigned int version = 1, + const bool refine = false) +{ + mosaic_to_tile_1->setup_translation(shift[0], shift[1]); + +#if 0 + // FIXME: + cerr << "SHIFT: " << shift << endl + << "ROUGH: " << mosaic_to_tile_1->GetParameters() << endl; +#endif + + std::vector xy; + std::vector uv; + generate_landmarks(tile_min, tile_max, tile_mask, mosaic_to_tile_0, samples, xy, uv, version, refine); + + unsigned int order = legendre_transform_t::Degree + 1; + unsigned int loworder = std::min(2u, order); + +#if 1 + mosaic_to_tile_1->solve_for_parameters(0, loworder, uv, xy); + mosaic_to_tile_1->solve_for_parameters(loworder, order - loworder, uv, xy); +#else + mosaic_to_tile_1->solve_for_parameters(0, order, uv, xy); +#endif + +#if 0 + // FIXME: + cerr << "FINAL: " << mosaic_to_tile_1->GetParameters() << endl; +#endif +} + +//---------------------------------------------------------------- +// solve_for_transform +// +// Given a forward transform and a gross translation vector for +// the inverse mapping, solve for transform parameters of the +// inverse Legendre polynomial transform. +// +// version 1 -- uniform jittered sampling over the tile +// version 2 -- non-uniform sampling skewed towards the center +// of the tile radially. This may be useful when +// the transform is ill-behaved at the tile edges. +// +template +void +solve_for_transform(const T * tile, + const mask_t * mask, + const base_transform_t * mosaic_to_tile_0, + typename legendre_transform_t::Pointer & mosaic_to_tile_1, + const vec2d_t & shift, + const unsigned int samples = 16, + const unsigned int version = 1, + const bool refine = false) +{ + typename T::SpacingType sp = tile->GetSpacing(); + typename T::SizeType sz = tile->GetLargestPossibleRegion().GetSize(); + const pnt2d_t MIN = tile->GetOrigin(); + const pnt2d_t MAX = MIN + vec2d(sp[0] * double(sz[0]), sp[1] * double(sz[1])); + + solve_for_transform( + MIN, MAX, mask, mosaic_to_tile_0, mosaic_to_tile_1, shift, samples, version, refine); +} + +//---------------------------------------------------------------- +// solve_for_transform +// +// Given a forward transform, solve for transform parameters of +// the inverse Legendre polynomial transform. +// +// version 1 -- uniform jittered sampling over the tile +// version 2 -- non-uniform sampling skewed towards the center +// of the tile radially. This may be useful when +// the transform is ill-behaved at the tile edges. +// +template +void +solve_for_transform(const pnt2d_t & tile_min, + const pnt2d_t & tile_max, + const mask_t * tile_mask, + const base_transform_t * mosaic_to_tile_0, + typename legendre_transform_t::Pointer & mosaic_to_tile_1, + const unsigned int samples = 16, + const unsigned int version = 1, + const bool refine = false) +{ + base_transform_t::Pointer tile_to_mosaic; + mosaic_to_tile_0->GetInverse(tile_to_mosaic); + assert(tile_to_mosaic.GetPointer() != nullptr); + + // calculate the shift: + pnt2d_t center = + pnt2d(tile_min[0] + (tile_max[0] - tile_min[0]) / 2.0, tile_min[1] + (tile_max[1] - tile_min[1]) / 2.0); + vec2d_t shift = center - tile_to_mosaic->TransformPoint(center); + + solve_for_transform( + tile_min, tile_max, tile_mask, mosaic_to_tile_0, mosaic_to_tile_1, shift, samples, version, refine); +} + +//---------------------------------------------------------------- +// solve_for_transform +// +// Given a forward transform, solve for transform parameters of +// the inverse Legendre polynomial transform. +// +// version 1 -- uniform jittered sampling over the tile +// version 2 -- non-uniform sampling skewed towards the center +// of the tile radially. This may be useful when +// the transform is ill-behaved at the tile edges. +// +template +void +solve_for_transform(const T * tile, + const mask_t * mask, + const base_transform_t * mosaic_to_tile_0, + typename legendre_transform_t::Pointer & mosaic_to_tile_1, + const unsigned int samples = 16, + const unsigned int version = 1, + const bool refine = false) +{ + base_transform_t::Pointer tile_to_mosaic; + mosaic_to_tile_0->GetInverse(tile_to_mosaic); + assert(tile_to_mosaic.GetPointer() != NULL); + + typename T::SizeType sz = tile->GetLargestPossibleRegion().GetSize(); + typename T::SpacingType sp = tile->GetSpacing(); + pnt2d_t tile_min = tile->GetOrigin(); + pnt2d_t tile_max; + tile_max[0] = tile_min[0] + sp[0] * double(sz[0]); + tile_max[1] = tile_min[1] + sp[1] * double(sz[1]); + + return solve_for_transform( + tile_min, tile_max, mask, mosaic_to_tile_0, mosaic_to_tile_1, samples, version, refine); +} + + +//---------------------------------------------------------------- +// std_mask +// +// Given image dimensions, generate a mask image. +// use_std_mask -- the standard mask for the Robert Marc Lab data. +// +extern mask_t::Pointer +std_mask(const itk::Point & origin, + const itk::Vector & spacing, + const itk::Size<2> & sz, + bool use_std_mask = true); + +//---------------------------------------------------------------- +// std_mask +// +// Given an image, generate a matching mask image. +// use_std_mask -- the standard mask for the Robert Marc Lab data. +// +template +mask_t::Pointer +std_mask(const T * tile, bool use_std_mask = true) +{ + return std_mask(tile->GetOrigin(), tile->GetSpacing(), tile->GetLargestPossibleRegion().GetSize(), use_std_mask); +} + +//---------------------------------------------------------------- +// std_tile +// +// Load a mosaic tile image, reset the origin to zero, set pixel +// spacing as specified. Downscale the image according to the +// shrink factor. If clahe_slope is greater than 1 then process +// the image with the CLAHE algorithm. +// +template +typename T::Pointer +std_tile(const char * fn_image, + const unsigned int & shrink_factor, + const double & pixel_spacing, + const double & clahe_slope, + const unsigned int & clahe_window, + const bool & blab) +{ + typename T::Pointer image = load(fn_image, blab); + + // reset the tile image origin and spacing: + typename T::PointType origin = image->GetOrigin(); + origin[0] = 0; + origin[1] = 0; + image->SetOrigin(origin); + + typename T::SpacingType spacing = image->GetSpacing(); + spacing[0] = 1; + spacing[1] = 1; + image->SetSpacing(spacing); + + // don't blur the images unnecessarily: + if (shrink_factor > 1) + { + image = shrink(image, shrink_factor); + } + + typename T::SpacingType sp = image->GetSpacing(); + + sp[0] *= pixel_spacing; + sp[1] *= pixel_spacing; + image->SetSpacing(sp); + + if (clahe_slope > 1.0) + { + image = CLAHE(image, + clahe_window, + clahe_window, + clahe_slope, + 256, // histogram bins + 0, // new MIN + 1); // new MAX + } + + return image; +} + +//---------------------------------------------------------------- +// std_tile +// +// Load a mosaic tile image, reset the origin to zero, set pixel +// spacing as specified. Downscale the image according to the +// shrink factor. +// +template +typename T::Pointer +std_tile(const char * fn_image, const unsigned int & shrink_factor, const double & pixel_spacing, bool blab) +{ + return std_tile(fn_image, + shrink_factor, + pixel_spacing, + 1.0, // clahe slope + 0, // clahe window size + blab); +} + +//---------------------------------------------------------------- +// save_volume_slice +// +// Save a volume slice transform. This is used by ir-stom-approx +// to generate the .stom files. +// +template +void +save_volume_slice(std::ostream & so, + const unsigned int & index, + const the_text_t & image, + const double & sp_x, + const double & sp_y, + const transform_t * transform, + const pnt2d_t & overall_min, + const pnt2d_t & overall_max, + const pnt2d_t & slice_min, + const pnt2d_t & slice_max, + const bool & flip) +{ + ios::fmtflags old_flags = so.setf(ios::scientific); + int old_precision = so.precision(); + so.precision(12); + + so << "index: " << index << endl + << "overall_min: " << overall_min[0] << ' ' << overall_min[1] << endl + << "overall_max: " << overall_max[0] << ' ' << overall_max[1] << endl + << "slice_min: " << slice_min[0] << ' ' << slice_min[1] << endl + << "slice_max: " << slice_max[0] << ' ' << slice_max[1] << endl + << "flip: " << flip << endl + << "sp: " << sp_x << ' ' << sp_y << endl + << "image:" << endl + << image << endl; + save_transform(so, transform); + so << endl; + + so.setf(old_flags); + so.precision(old_precision); +} + +//---------------------------------------------------------------- +// load_volume_slice +// +// Load a volume slice transform. This is used by ir-slice +// to assemble a volume slice image from a given .stom file. +// +template +void +load_volume_slice(std::istream & si, + unsigned int & index, + the_text_t & image, + double & sp_x, + double & sp_y, + typename transform_t::Pointer & transform, + pnt2d_t & overall_min, + pnt2d_t & overall_max, + pnt2d_t & slice_min, + pnt2d_t & slice_max, + bool & flip) +{ + while (true) + { + std::string token; + si >> token; + if (si.eof() && token.size() == 0) + break; + + if (token == "index:") + { + si >> index; + } + else if (token == "overall_min:") + { + si >> overall_min[0] >> overall_min[1]; + } + else if (token == "overall_max:") + { + si >> overall_max[0] >> overall_max[1]; + } + else if (token == "slice_min:") + { + si >> slice_min[0] >> slice_min[1]; + } + else if (token == "slice_max:") + { + si >> slice_max[0] >> slice_max[1]; + } + else if (token == "flip:") + { + si >> flip; + } + else if (token == "sp:") + { + si >> sp_x >> sp_y; + } + else if (token == "image:") + { + image.clear(); + while (image.is_empty()) + { + getline(si, image); + } + + itk::TransformBase::Pointer t_base = load_transform(si); + transform = dynamic_cast(t_base.GetPointer()); + assert(transform.GetPointer() != NULL); + } + else + { + cerr << "WARNING: unknown token: '" << token << "', ignoring ..." << endl; + } + } +} + +//---------------------------------------------------------------- +// calc_size +// +// Given a bounding box expressed in the image space and +// pixel spacing, calculate corresponding image size. +// +extern image_t::SizeType +calc_size(const pnt2d_t & MIN, const pnt2d_t & MAX, const double & spacing); + +//---------------------------------------------------------------- +// track_progress +// +// Sets the current progress for determining the overall percentage +// +inline static void +track_progress(bool track) +{ + if (track) + cout << "Tracking progress..." << endl; + else + cout << "Progress tracking disabled..." << endl; +} + +//---------------------------------------------------------------- +// set_major_progress +// +// Sets the current progress for determining the overall percentage +// +inline static void +set_major_progress(double major_percent) +{ + // TODO: Draw out a nifty bar instead. This would be easier to read, and + // better understood. + cout << "Tool Percentage: " << major_percent << endl; +} + +//---------------------------------------------------------------- +// set_minor_progress +// +// Sets the current progress for determining the percentage of this +// particular task. +// +inline static void +set_minor_progress(double minor_percent, double major_percent) +{ + // TODO: Draw out a nifty bar instead. This would be easier to read, and + // better understood. + cout << "Task Percentage: " << minor_percent << " until " << major_percent << endl; +} + +//---------------------------------------------------------------- +// set_the_total_ +// +// Set the total expected progress value so multiple threads can increment the same value +// +// +static double s_TotalExpectedMajorProgess = 100; +static double s_TotalExpectedMinorProgess = 100; +static double s_CurrentMajorProgess = 0; +static double s_CurrentMinorProgess = 0; + +inline static void +set_expected_major_progress(double ExpectedMajorTotal = 100) +{ + s_TotalExpectedMajorProgess = ExpectedMajorTotal; + s_CurrentMajorProgess = 0; + cout << "Tool Percentage: 0"; +} + +inline static void +set_expected_minor_progress(double ExpectedMinorTotal = 100) +{ + s_TotalExpectedMinorProgess = ExpectedMinorTotal; + s_CurrentMinorProgess = 0; + cout << "Tool Percentage: 0"; +} + +inline static void +increment_major_progress(double amount = 1) +{ + s_CurrentMajorProgess = s_CurrentMajorProgess + amount; + cout << "Tool Percentage: " << s_CurrentMajorProgess / s_TotalExpectedMajorProgess << endl; +} + +inline static void +increment_minor_progress(double amount = 1) +{ + std::ios_base::fmtflags original_flags = std::cout.flags(); + + s_CurrentMinorProgess = s_CurrentMinorProgess + amount; + cout.precision(2); + + cout << "Task Percentage: " << (s_CurrentMinorProgess / s_TotalExpectedMinorProgess) * 100 << " until 100" << endl; + + cout.setf(original_flags); +} + + +#endif // itkIRCommon_h diff --git a/include/itkIRDynamicArray.h b/include/itkIRDynamicArray.h new file mode 100644 index 0000000..62be2a0 --- /dev/null +++ b/include/itkIRDynamicArray.h @@ -0,0 +1,350 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_dynamic_array.hxx +// Author : Pavel A. Koshevoy +// Created : Fri Oct 31 17:16:25 MDT 2003 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : Implementation of a dynamically resizable array +// that grows automatically. + +#ifndef THE_DYNAMIC_ARRAY_HXX_ +#define THE_DYNAMIC_ARRAY_HXX_ + +// system includes: +#include +#include +#include + +// forward declarations: +template class the_dynamic_array_t; + +#undef min +#undef max + + +//---------------------------------------------------------------- +// the_dynamic_array_ref_t +// +template +class the_dynamic_array_ref_t +{ +public: + the_dynamic_array_ref_t(the_dynamic_array_t & array, + size_t index = 0): + array_(array), + index_(index) + {} + + inline the_dynamic_array_ref_t & operator << (const T & elem) + { + array_[index_++] = elem; + return *this; + } + +private: + // reference to the array: + the_dynamic_array_t & array_; + + // current index into the array: + size_t index_; +}; + + +//---------------------------------------------------------------- +// the_dynamic_array_t +// +template +class the_dynamic_array_t +{ +public: + the_dynamic_array_t(): + array_(NULL), + page_size_(16), + size_(0), + init_value_() + {} + + the_dynamic_array_t(const size_t & init_size): + array_(NULL), + page_size_(init_size), + size_(0), + init_value_() + {} + + the_dynamic_array_t(const size_t & init_size, + const size_t & page_size, + const T & init_value): + array_(NULL), + page_size_(page_size), + size_(0), + init_value_(init_value) + { + resize(init_size); + } + + // copy constructor: + the_dynamic_array_t(const the_dynamic_array_t & a): + array_(NULL), + page_size_(0), + size_(0), + init_value_(a.init_value_) + { + (*this) = a; + } + + // destructor: + ~the_dynamic_array_t() + { + clear(); + } + + // remove all contents of this array: + void clear() + { + size_t num = num_pages(); + for (size_t i = 0; i < num; i++) + { + delete (*array_)[i]; + } + + delete array_; + array_ = NULL; + + size_ = 0; + } + + // the assignment operator: + the_dynamic_array_t & operator = (const the_dynamic_array_t & array) + { + clear(); + + page_size_ = array.page_size_; + init_value_ = array.init_value_; + + resize(array.size_); + for (size_t i = 0; i < size_; i++) + { + (*this)[i] = array[i]; + } + + return *this; + } + + // resize the array, all contents will be preserved: + void resize(const size_t & new_size) + { + // bump the current size value: + size_ = new_size; + + // do nothing if resizing is unnecessary: + if (size_ <= max_size()) return; + + // we'll have to do something about the existing data: + size_t old_num_pages = num_pages(); + size_t new_num_pages = + std::max((size_t)(2 * old_num_pages), + (size_t)(1 + size_ / page_size_)); + + // create a new array: + std::vector< std::vector * > * new_array = + new std::vector< std::vector * >(new_num_pages); + + // shallow-copy the old content: + for (size_t i = 0; i < old_num_pages; i++) + { + (*new_array)[i] = (*array_)[i]; + } + + // initialize the new pages: + for (size_t i = old_num_pages; i < new_num_pages; i++) + { + (*new_array)[i] = new std::vector(page_size_); + for (size_t j = 0; j < page_size_; j++) + { + (*(*new_array)[i])[j] = init_value_; + } + } + + // get rid of the old array: + delete array_; + + // put the new array in place of the old array: + array_ = new_array; + } + + // the size of this array: + inline const size_t & size() const + { return size_; } + + inline const size_t & page_size() const + { return page_size_; } + + // maximum usable size of the array that does not require resizing the array: + inline size_t max_size() const + { return num_pages() * page_size_; } + + // number of pages currently allocated: + inline size_t num_pages() const + { return (array_ == NULL) ? 0 : array_->size(); } + + inline const T * page(const size_t & page_index) const + { return &((*(*array_)[page_index])[0]); } + + inline T * page(const size_t & page_index) + { return &((*(*array_)[page_index])[0]); } + + // return either first or last index into the array: + inline size_t end_index(bool last) const + { + if (last == false) return 0; + return size_ - 1; + } + + // return either first or last element in the array: + inline const T & end_elem(bool last) const + { return elem(end_index(last)); } + + inline T & end_elem(bool last) + { return elem(end_index(last)); } + + inline const T & front() const + { return end_elem(false); } + + inline T & front() + { return end_elem(false); } + + inline const T & back() const + { return end_elem(true); } + + inline T & back() + { return end_elem(true); } + + // non-const accessors: + inline T & elem(const size_t i) + { + if (i >= size_) resize(i + 1); + return (*(*array_)[i / page_size_])[i % page_size_]; + } + + inline T & operator [] (const size_t & i) + { return elem(i); } + + // const accessors: + inline const T & elem(const size_t & i) const + { return (*((*array_)[i / page_size_]))[i % page_size_]; } + + inline const T & operator [] (const size_t & i) const + { return elem(i); } + + // this is usefull for filling-in the array: + the_dynamic_array_ref_t operator << (const T & elem) + { + (*this)[0] = elem; + return the_dynamic_array_ref_t(*this, 1); + } + + // grow the array by one and insert a new element at the tail: + inline void push_back(const T & elem) + { (*this)[size_] = elem; } + + inline void append(const T & elem) + { push_back(elem); } + + // return the index of the first occurrence of a given element in the array: + size_t index_of(const T & element) const + { + for (size_t i = 0; i < size_; i++) + { + if (!(elem(i) == element)) continue; + return i; + } + + return ~0u; + } + + // check whether this array contains a given element: + inline bool has(const T & element) const + { return index_of(element) != ~0u; } + + // remove an element from the array: + bool remove(const T & element) + { + size_t idx = index_of(element); + if (idx == ~0u) return false; + + for (size_t i = idx + 1; i < size_; i++) + { + elem(i - 1) = elem(i); + } + + size_--; + return true; + } + + void assign(const size_t & size, const T & element) + { + resize(size); + for (size_t i = 0; i < size; i++) + { + elem(i) = element; + } + } + + // for debugging, dumps this list into a stream: + void dump(std::ostream & strm) const + { + strm << "the_dynamic_array_t(" << (void *)this << ") {\n"; + for (size_t i = 0; i < size_; i++) + { + strm << elem(i) << std::endl; + } + strm << '}'; + } + +protected: + // an array of pointers to arrays (pages) of data: + std::vector< std::vector *> * array_; + + // page size: + size_t page_size_; + + // current array size: + size_t size_; + + // init value used when resizing the array: + T init_value_; +}; + +//---------------------------------------------------------------- +// operator << +// +template +std::ostream & +operator << (std::ostream & s, const the_dynamic_array_t & a) +{ + a.dump(s); + return s; +} + + +#endif // THE_DYNAMIC_ARRAY_HXX_ diff --git a/include/itkIRTerminator.h b/include/itkIRTerminator.h new file mode 100644 index 0000000..a88a0d5 --- /dev/null +++ b/include/itkIRTerminator.h @@ -0,0 +1,64 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : itk_terminator.hxx +// Author : Pavel A. Koshevoy +// Created : Sun Sep 24 18:16:00 MDT 2006 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : a ITK filter terminator convenience class + +#ifndef ITK_TERMINATOR_HXX_ +#define ITK_TERMINATOR_HXX_ + +// local includes: +#include "IRTerminator.h" + +#ifdef USE_ITK_TERMINATORS +#define itk_terminator_t the_terminator_t + + +//---------------------------------------------------------------- +// terminator_t +// +template +class terminator_t : public the_terminator_t +{ +public: + terminator_t(process_t * proc): + itk_terminator_t(proc->GetNameOfClass()), + process_(proc) + {} + + // virtual: + void terminate() + { + process_->AbortGenerateDataOn(); + itk_terminator_t::terminate(); + } + +private: + typename process_t::Pointer process_; +}; + + +#endif // USE_ITK_TERMINATORS +#endif // ITK_TERMINATOR_HXX_ diff --git a/include/itkIRText.h b/include/itkIRText.h new file mode 100644 index 0000000..3a03a01 --- /dev/null +++ b/include/itkIRText.h @@ -0,0 +1,324 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_text.hxx +// Author : Pavel A. Koshevoy +// Created : Sun Aug 29 14:53:00 MDT 2004 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : text convenience class. + +#ifndef itkIRText_h +#define itkIRText_h + +// system includes: +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +//---------------------------------------------------------------- +// the_text_t +// +class the_text_t +{ +public: + the_text_t(const char * text = ""); + the_text_t(const char * text, const size_t & size); + the_text_t(const the_text_t & text); + the_text_t(const std::list & text); + ~the_text_t(); + + // assignment operator: + inline the_text_t & operator = (const the_text_t & text) + { + if (this != &text) + { + assign(text.text_, text.size_); + } + + return *this; + } + + // clear the string: + inline void clear() + { + delete [] text_; + text_ = NULL; + size_ = 0; + } + + // shorthand: + inline bool is_empty() const + { return size_ == 0; } + + // assign a new string to this text: + inline void assign(const char * text) + { assign(text, strlen(text)); } + + void assign(const char * text, const size_t & text_size); + + // append a new string to this text: + inline void append(const char * text) + { append(text, strlen(text)); } + + void append(const char * text, const size_t & text_size); + + // replace + void replace(const char find, const char replace); + + // equality/inequality tests: + inline bool operator == (const the_text_t & text) const + { return ((size_ == text.size_) && (strcmp(text_, text.text_) == 0)); } + + inline bool operator != (const the_text_t & text) const + { return !(*this == text); } + + inline bool operator == (const char * text) const + { return (*this == the_text_t(text)); } + + inline bool operator != (const char * text) const + { return !(*this == text); } + + inline bool operator < (const the_text_t & text) const + { return (strcmp(text_, text.text_) < 0); } + + inline bool operator > (const the_text_t & text) const + { return (strcmp(text_, text.text_) > 0); } + + // arithmetic: + inline the_text_t & operator += (const the_text_t & text) + { + append(text.text_, text.size_); + return *this; + } + + inline the_text_t operator + (const the_text_t & text) const + { + the_text_t text_sum(*this); + text_sum += text; + return text_sum; + } + + // access operators: + template + inline const char & operator [] (const index_t & index) const + { return text_[index]; } + + template + inline char & operator [] (const index_t & index) + { return text_[index]; } + + // accessors: + inline const char * text() const + { return text_; } + + inline const size_t & size() const + { return size_; } + + // conversion operator: + inline operator const char * () const + { return text_; } + + inline static the_text_t pad(const char * str, + const size_t width = 0, + const char pad_char = ' ', + const bool pad_left = true) + { + the_text_t txt(str); + + if (width > txt.size()) + { + the_text_t padding; + padding.fill(pad_char, width - txt.size()); + txt = pad_left ? padding + txt : txt + padding; + } + + return txt; + } + + // helpers: + template + static the_text_t number(const number_t & number, + const size_t width = 0, + const char pad_char = ' ', + const bool pad_left = true) + { + std::ostringstream os; + os << number; + + std::string str = os.str(); + return pad(str.c_str(), width, pad_char, pad_left); + } + + inline static the_text_t number(const size_t & number, + const size_t width = 0, + const char pad_char = ' ', + const bool pad_left = true) + { +#ifdef _WIN32 +#ifndef snprintf +#define snprintf _snprintf_s +#endif +#endif + + static char buffer[256]; + snprintf(buffer, sizeof(buffer), "%llu", (long long unsigned int)(number)); + return pad(buffer, width, pad_char, pad_left); + } + + short int toShort(bool * ok = 0, int base = 10) const; + unsigned short int toUShort(bool * ok = NULL, int base = 10) const; + + int toInt(bool * ok = NULL, int base = 10) const; + unsigned int toUInt(bool * ok = NULL, int base = 10) const; + + long int toLong(bool * ok = NULL, int base = 10) const; + unsigned long int toULong(bool * ok = NULL, int base = 10) const; + + float toFloat(bool * ok = NULL) const; + double toDouble(bool * ok = NULL) const; + + void to_ascii(); + void to_lower(); + void to_upper(); + void fill(const char & c, const size_t size); + + void fill(const char & c) + { fill(c, size_); } + + // return true if the given text matches the head/tail of this text: + bool match_head(const the_text_t & t, bool ignore_case = false) const; + bool match_tail(const the_text_t & t, bool ignore_case = false) const; + + bool match_text(const the_text_t & t, + const size_t & index, + bool ignore_case = false) const; + + // remove leading/tailing white space, replace internal white space + // with a single space: + the_text_t simplify_ws() const; + + // split the text into a set of tokens, return the number of tokens: + size_t split(std::vector & tokens, + const char & separator, + const bool & empty_ok = false) const; + + // splitAt splits string into two parts upon a character. + std::vector splitAt(const char split_char, + unsigned int n) const; + + // count the number of occurrences of a given symbol in the text: + size_t contains(const char & symbol) const; + + // extract a portion of the string: + void extract(the_text_t & to, + const size_t & from, + const size_t & size) const + { + assert(from + size < size_ + 1); + to.assign(&text_[from], size); + } + + inline the_text_t extract(const size_t & from, + const size_t & size) const + { + the_text_t to; + extract(to, from, size); + return to; + } + + inline the_text_t reverse() const + { + the_text_t rev(*this); + for (size_t i = 0; i < size_; i++) + { + rev.text_[i] = text_[size_ - i - 1]; + } + + return rev; + } + + inline the_text_t cut(const char & separator, + size_t f0, + size_t f1 = 0) const + { + const char sep_str[2] = { separator, '\0' }; + + std::vector fields; + split(fields, separator, true); + size_t num_fields = fields.size(); + + if (f1 < f0) + { + f1 = f0; + } + else if (f1 >= num_fields) + { + f1 = num_fields - 1; + } + + the_text_t out; + for (size_t f = f0; f <= f1; f++) + { + out += fields[f]; + if (f + 1 <= f1) + { + out += sep_str; + } + } + + return out; + } + +private: + // the text itself: + char * text_; + + // the length of the text: + size_t size_; +}; + +extern std::ostream & +operator << (std::ostream & out, const the_text_t & text); + +extern std::istream & +operator >> (std::istream & in, the_text_t & text); + +extern std::istream & +getline(std::istream & in, the_text_t & text); + +//---------------------------------------------------------------- +// to_binary +// +// return a 0 and 1 string representation of a byte +// +extern the_text_t +to_binary(const unsigned char & byte, bool lsb_first = true); + + +#endif // itkIRText_h diff --git a/include/itkIRUtils.h b/include/itkIRUtils.h new file mode 100644 index 0000000..a72f678 --- /dev/null +++ b/include/itkIRUtils.h @@ -0,0 +1,936 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_utils.hxx +// Author : Pavel A. Koshevoy +// Created : 2006/04/15 11:25:00 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : utility functions for working with arrays, +// lists, numbers, angles, etc... + +#ifndef THE_UTILS_HXX_ +#define THE_UTILS_HXX_ + +// system includes: +#ifndef _USE_MATH_DEFINES +#define _USE_MATH_DEFINES +#endif + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// local includes: +#include "itkIRDynamicArray.h" + + +//---------------------------------------------------------------- +// array2d +// +#define array2d( T ) std::vector > + +//---------------------------------------------------------------- +// array3d +// +#define array3d( T ) std::vector > > + +//---------------------------------------------------------------- +// TWO_PI +// +static const double TWO_PI = 2.0 * M_PI; + + +//---------------------------------------------------------------- +// clamp_angle +// +inline double +clamp_angle(const double & absolute_angle) +{ + double a = fmod(absolute_angle + TWO_PI, TWO_PI); + assert(a >= 0.0 && a < TWO_PI); + return a; +} + +//---------------------------------------------------------------- +// calc_angle +// +inline double +calc_angle(const double & x, + const double & y, + const double & reference_angle = 0.0) +{ + return clamp_angle(fmod(atan2(y, x) + TWO_PI, TWO_PI) - + fmod(reference_angle, TWO_PI)); +} + +//---------------------------------------------------------------- +// sleep_msec +// +extern void sleep_msec(size_t msec); + +//---------------------------------------------------------------- +// drand +// +inline static double drand() +{ return double(rand()) / double(RAND_MAX); } + +//---------------------------------------------------------------- +// integer_power +// +template +inline scalar_t +integer_power(scalar_t x, size_t p) +{ + scalar_t result = scalar_t(1); + while (p != 0u) + { + if (p & 1) result *= x; + x *= x; + p >>= 1; + } + + return result; +} + +//---------------------------------------------------------------- +// closest_power_of_two_larger_than_given +// +template +inline scalar_t +closest_power_of_two_larger_than_given(const scalar_t & given) +{ + size_t n = sizeof(given) * 8; + scalar_t closest = scalar_t(1); + for (size_t i = 0; + (i < n) && (closest < given); + i++, closest *= scalar_t(2)) {} + + return closest; +} + + +//---------------------------------------------------------------- +// divide +// +template +data_t +divide(const data_t & numerator, const data_t & denominator) +{ + static data_t zero = data_t(0); + return (denominator != zero) ? (numerator / denominator) : zero; +} + +//---------------------------------------------------------------- +// clear_stack +// +template +void +clear_stack(std::stack & s) +{ + while (!s.empty()) + { + s.pop(); + } +} + +//---------------------------------------------------------------- +// resize +// +template +void +resize(array2d(data_t) & array, + const size_t & rows, + const size_t & cols) +{ + array.resize(rows); + for (size_t j = 0; j < rows; j++) + { + array[j].resize(cols); + } +} + +//---------------------------------------------------------------- +// assign +// +template +void +assign(array2d(data_t) & array, + const size_t & rows, + const size_t & cols, + const data_t & value) +{ + array.resize(rows); + for (size_t j = 0; j < rows; j++) + { + array[j].assign(cols, value); + } +} + +//---------------------------------------------------------------- +// resize +// +template +void +resize(array3d(data_t) & array, + const size_t & slices, + const size_t & rows, + const size_t & cols) +{ + array.resize(slices); + for (size_t i = 0; i < slices; i++) + { + array[i].resize(rows); + for (size_t j = 0; j < rows; j++) + { + array[i][j].resize(cols); + } + } +} + + +//---------------------------------------------------------------- +// push_back_unique +// +template +void +push_back_unique(container_t & container, + const data_t & data) +{ + typename container_t::const_iterator where = + std::find(container.begin(), container.end(), data); + if (where != container.end()) return; + + container.push_back(data); +} + +//---------------------------------------------------------------- +// push_front_unique +// +template +void +push_front_unique(container_t & container, + const data_t & data) +{ + typename container_t::const_iterator where = + std::find(container.begin(), container.end(), data); + if (where != container.end()) return; + + container.push_front(data); +} + +//---------------------------------------------------------------- +// remove_head +// +template +data_t +remove_head(std::list & container) +{ + data_t head = container.front(); + container.pop_front(); + return head; +} + +//---------------------------------------------------------------- +// remove_tail +// +template +data_t +remove_tail(std::list & container) +{ + data_t tail = container.back(); + container.pop_back(); + return tail; +} + +//---------------------------------------------------------------- +// remove_head +// +template +data_t +remove_head(std::vector & container) +{ + data_t head = container.front(); + container.erase(container.begin()); + return head; +} + +//---------------------------------------------------------------- +// remove_tail +// +template +data_t +remove_tail(std::vector & container) +{ + data_t tail = container.back(); + container.pop_back(); + return tail; +} + +//---------------------------------------------------------------- +// is_size_two_or_larger +// +template +inline bool +is_size_two_or_larger(const container_t & c) +{ + typename container_t::const_iterator i = c.begin(); + typename container_t::const_iterator e = c.end(); + return (i != e) && (++i != e); +} + +//---------------------------------------------------------------- +// is_size_three_or_larger +// +template +inline bool +is_size_three_or_larger(const container_t & c) +{ + typename container_t::const_iterator i = c.begin(); + typename container_t::const_iterator e = c.end(); + return (i != e) && (++i != e) && (++i != e); +} + +//---------------------------------------------------------------- +// is_size_one +// +template +inline bool +is_size_one(const container_t & c) +{ + typename container_t::const_iterator i = c.begin(); + typename container_t::const_iterator e = c.end(); + return (i != e) && (++i == e); +} + + +//---------------------------------------------------------------- +// replace +// +template +bool +replace(container_t & container, const data_t & a, const data_t & b) +{ + typename container_t::iterator end = container.end(); + typename container_t::iterator i = std::find(container.begin(), end, a); + if (i == end) return false; + + *i = b; + return true; +} + + +//---------------------------------------------------------------- +// iterator_at_index +// +template +typename std::list::const_iterator +iterator_at_index(const std::list & container, + const size_t & index) +{ + typename std::list::const_iterator iter = container.begin(); + for (size_t i = 0; i < index && iter != container.end(); i++, ++iter) ; + return iter; +} + +//---------------------------------------------------------------- +// iterator_at_index +// +template +typename std::list::iterator +iterator_at_index(std::list & container, + const size_t & index) +{ + typename std::list::iterator iter = container.begin(); + for (size_t i = 0; i < index && iter != container.end(); i++, ++iter) ; + return iter; +} + +//---------------------------------------------------------------- +// index_of +// +template +size_t +index_of(const std::list & container, const data_t & data) +{ + typename std::list::const_iterator iter = container.begin(); + for (size_t i = 0; iter != container.end(); i++, ++iter) + { + if (data == *iter) return i; + } + + return ~0; +} + +//---------------------------------------------------------------- +// has +// +template +bool +has(const std::list & container, const data_t & data) +{ + typename std::list::const_iterator iter = + std::find(container.begin(), container.end(), data); + + return iter != container.end(); +} + +//---------------------------------------------------------------- +// expand +// +template +container_t & +expand(container_t & a, + const container_t & b, + const bool unique = false) +{ + for (typename container_t::const_iterator i = b.begin(); i != b.end(); ++i) + { + if (unique) + { + push_back_unique(a, *i); + } + else + { + a.push_back(*i); + } + } + + return a; +} + +//---------------------------------------------------------------- +// next +// +template +iterator_t +next(const iterator_t & curr) +{ + iterator_t tmp(curr); + return ++tmp; +} + +//---------------------------------------------------------------- +// prev +// +template +iterator_t +prev(const iterator_t & curr) +{ + iterator_t tmp(curr); + return --tmp; +} + +//---------------------------------------------------------------- +// dump +// +template +stream_t & +dump(stream_t & so, const std::list & c) +{ + for (typename std::list::const_iterator + i = c.begin(); i != c.end(); ++i) + { + so << *i << ' '; + } + + return so; +} + + +//---------------------------------------------------------------- +// operator << +// +template +std::ostream & +operator << (stream_t & so, const std::list & c) +{ + return dump(so, c); +} + + +//---------------------------------------------------------------- +// operator + +// +// Construct an on-the-fly linked list containing two elements: +// +template +inline std::list +operator + (const T & a, const T & b) +{ + std::list ab; + ab.push_back(a); + ab.push_back(b); + return ab; +} + +//---------------------------------------------------------------- +// operator + +// +// Construct an on-the-fly linked list containing list a with item b appended: +template +inline std::list +operator + (const std::list & a, const T & b) +{ + std::list ab(a); + ab.push_back(b); + return ab; +} + +//---------------------------------------------------------------- +// inserter_t +// +template +class inserter_t +{ +public: + inserter_t(container_t & container, + const typename container_t::iterator & iter, + const bool & expand): + container_(container), + iter_(iter), + expand_(expand) + {} + + inline inserter_t & operator << (const data_t & data) + { + if (iter_ == container_.end()) + { + if (expand_) + { + iter_ = container_.insert(iter_, data); + } + else + { + assert(0); + } + } + else + { + *iter_ = data; + ++iter_; + } + + return *this; + } + +private: + // reference to the container: + container_t & container_; + + // current index into the container: + typename container_t::iterator iter_; + + // a flag indicating whether the container should be expanded to + // accomodate the insertions: + bool expand_; +}; + +//---------------------------------------------------------------- +// operator << +// +template +inserter_t, data_t> +operator << (std::vector & container, const data_t & data) +{ + inserter_t, data_t> + inserter(container, container.begin(), false); + return inserter << data; +} + +//---------------------------------------------------------------- +// operator << +// +template +inserter_t, data_t> +operator << (std::list & container, const data_t & data) +{ + inserter_t, data_t> + inserter(container, container.begin(), true); + return inserter << data; +} + + +//---------------------------------------------------------------- +// calc_euclidian_distance_sqrd +// +template +data_t +calc_euclidian_distance_sqrd(const std::vector & a, + const std::vector & b) +{ + data_t distance_sqrd = data_t(0); + for (size_t i = 0; i < dimensions; i++) + { + data_t d = a[i] - b[i]; + distance_sqrd += d * d; + } + + return distance_sqrd; +} + +//---------------------------------------------------------------- +// calc_euclidian_distance +// +template +data_t +calc_euclidian_distance(const std::vector & a, + const std::vector & b) +{ + data_t dist_sqrd = calc_euclidian_distance_sqrd(a, b); + double dist = sqrt(double(dist_sqrd)); + return data_t(dist); +} + +//---------------------------------------------------------------- +// calc_frobenius_norm_sqrd +// +template +data_t +calc_frobenius_norm_sqrd(const std::vector & vec) +{ + data_t L2_norm_sqrd = data_t(0); + + const size_t len = vec.size(); + for (size_t i = 0; i < len; i++) + { + L2_norm_sqrd += vec[i] * vec[i]; + } + + return L2_norm_sqrd; +} + +//---------------------------------------------------------------- +// calc_frobenius_norm +// +template +data_t +calc_frobenius_norm(const std::vector & vec) +{ + data_t norm_sqrd = calc_frobenius_norm_sqrd(vec); + double norm = sqrt(double(norm_sqrd)); + return data_t(norm); +} + +//---------------------------------------------------------------- +// normalize +// +template +void +normalize(std::vector & vec) +{ + data_t norm = calc_frobenius_norm(vec); + + const size_t len = vec.size(); + for (size_t i = 0; i < len; i++) + { + vec[i] /= norm; + } +} + + +//---------------------------------------------------------------- +// the_sign +// +template +inline T +the_sign(const T & a) +{ + if (a < 0) return -1; + if (a > 0) return 1; + return 0; +} + + +//---------------------------------------------------------------- +// copy_a_to_b +// +// list -> array: +// +template +void +copy_a_to_b(const std::list & container_a, + std::vector & container_b) +{ + container_b.assign(container_a.begin(), container_a.end()); +} + +//---------------------------------------------------------------- +// copy_a_to_b +// +// list -> array: +// +template +void +copy_a_to_b(const std::list & container_a, + the_dynamic_array_t & container_b) +{ + container_b.resize(container_a.size()); + + const size_t size = container_a.size(); + typename std::list::const_iterator iter = container_a.begin(); + for (size_t i = 0; i < size; i++, ++iter) + { + container_b[i] = *iter; + } +} + +//---------------------------------------------------------------- +// copy_a_to_b +// +// dynamic_array -> array +// +template +void +copy_a_to_b(const the_dynamic_array_t & container_a, + std::vector & container_b) +{ + container_b.resize(container_a.size()); + + const size_t & size = container_a.size(); + for (size_t i = 0; i < size; i++) + { + container_b[i] = container_a[i]; + } +} + + +//---------------------------------------------------------------- +// the_lock_t +// +template +class the_lock_t +{ +public: + the_lock_t(T * lock, bool lock_immediately = true): + lock_(lock), + armed_(false) + { if (lock_immediately) arm(); } + + the_lock_t(T & lock, bool lock_immediately = true): + lock_(&lock), + armed_(false) + { if (lock_immediately) arm(); } + + ~the_lock_t() + { disarm(); } + + inline void arm() + { + if (!armed_ && lock_ != NULL) + { + lock_->lock(); + armed_ = true; + } + } + + inline void disarm() + { + if (armed_ && lock_ != NULL) + { + lock_->unlock(); + armed_ = false; + } + } + +private: + the_lock_t(); + the_lock_t(const the_lock_t &); + the_lock_t & operator = (const the_lock_t &); + + T * lock_; + bool armed_; +}; + +//---------------------------------------------------------------- +// the_unlock_t +// +template +class the_unlock_t +{ +public: + the_unlock_t(T * lock): + lock_(lock) + { + if (lock_ != NULL) + { + assert(lock_->try_lock() == false); + } + } + + the_unlock_t(T & lock): + lock_(&lock) + { + if (lock_ != NULL) + { + assert(lock_->try_lock() == false); + } + } + + ~the_unlock_t() + { + if (lock_ != NULL) + { + lock_->unlock(); + } + } + +private: + the_unlock_t(); + the_unlock_t(const the_unlock_t &); + the_unlock_t & operator = (const the_unlock_t &); + + T * lock_; +}; + +//---------------------------------------------------------------- +// the_scoped_variable_t +// +template +class the_scoped_variable_t +{ +public: + the_scoped_variable_t(T & variable, + const T & in_scope_value, + const T & out_of_scope_value): + var_(variable), + in_(in_scope_value), + out_(out_of_scope_value) + { + var_ = in_; + } + + ~the_scoped_variable_t() + { + var_ = out_; + } + +private: + the_scoped_variable_t(const the_scoped_variable_t &); + the_scoped_variable_t & operator = (const the_scoped_variable_t &); + + T & var_; + const T in_; + const T out_; +}; + +//---------------------------------------------------------------- +// the_scoped_increment_t +// +template +class the_scoped_increment_t +{ +public: + the_scoped_increment_t(T & variable): + var_(variable) + { + var_++; + } + + ~the_scoped_increment_t() + { + var_--; + } + +private: + the_scoped_increment_t(const the_scoped_increment_t &); + the_scoped_increment_t & operator = (const the_scoped_increment_t &); + + T & var_; +}; + +//---------------------------------------------------------------- +// THROW_ARG2_IF_FALSE +// +#ifndef THROW_ARG2_IF_FALSE +#define THROW_ARG2_IF_FALSE(predicate, arg2) \ +if (predicate) {} else throw arg2 +#endif + +//---------------------------------------------------------------- +// restore_console_stdio +// +// Reopen stdin, stdout, stderr on windows, no-op everywhere else +// +extern bool +restore_console_stdio(); + + +//---------------------------------------------------------------- +// off_t +// +#ifdef _WIN32 +#define off_t __int64 +#endif + +namespace the +{ + extern int open_utf8(const char * filename_utf8, + int oflag, + int pmode); + + extern void open_utf8(std::fstream & fstream_to_open, + const char * filename_utf8, + std::ios_base::openmode mode); + + extern FILE * fopen_utf8(const char * filename_utf8, + const char * mode); + + extern int rename_utf8(const char * old_utf8, const char * new_utf8); + extern int remove_utf8(const char * filename_utf8); + + extern int rmdir_utf8(const char * path_utf8); + extern int mkdir_utf8(const char * path_utf8); + + extern int fseek64(FILE * file, off_t offset, int whence); + extern off_t ftell64(const FILE * file); + + inline static bool + close_enough(const float & ref, + const float & given, + const float tolerance = 1e-6f) + { + float err = fabsf(given - ref); + return err < tolerance; + } + + inline static bool + close_enough(const double & ref, + const double & given, + const double tolerance = 1e-6) + { + double err = fabs(given - ref); + return err < tolerance; + } +} + + +#endif // THE_UTILS_HXX_ diff --git a/include/itkImageMosaicVarianceMetric.h b/include/itkImageMosaicVarianceMetric.h new file mode 100644 index 0000000..2e6f8f7 --- /dev/null +++ b/include/itkImageMosaicVarianceMetric.h @@ -0,0 +1,316 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : itkImageMosaicVarianceMetric.h +// Author : Pavel A. Koshevoy +// Created : 2005/06/22 17:19 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : A metric class measuring mean pixel variance within +// the mosaic tile overlap regions. The metric derivative + +#ifndef __itkImageMosaicVarianceMetric_h +#define __itkImageMosaicVarianceMetric_h + +#include +#include +#include +#include +#include + +/** . + This class computes the mean pixel variance across several images + within the overlapping regions of the mosaic. Each image is warped + by a corresponding transform. All of the transforms must be of the + same type (and therefore have the same number of parameters). Some + of the transforms parameters may be shared across transforms. The + shared/unique parameters are specified by a bit vector + (true - shared, false - unique). This is usefull for radial + distortion transforms where the translation parameters are unique + for each image but the distortion parameters are the same across + all images (all images are assumed to have been distorted by + the same lens). + */ + +namespace itk +{ + +/** \class ImageMosaicVarianceMetric + * \brief Computes mean pixel variance within the overlapping regions + * of a mosaic. + * + */ +template +class ITK_EXPORT ImageMosaicVarianceMetric : public SingleValuedCostFunction +{ +public: + /** Standard class typedefs. */ + typedef ImageMosaicVarianceMetric Self; + typedef SingleValuedCostFunction Superclass; + typedef SmartPointer Pointer; + typedef SmartPointer ConstPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self); + + /** Run-time type information (and related methods). */ + itkTypeMacro(ImageMosaicVarianceMetric, SingleValuedCostFunction); + + /** Type of the moving Image. */ + typedef TInterpolator interpolator_t; + typedef TImage image_t; + typedef typename TImage::PixelType pixel_t; + + /** Constant for the image dimension */ + itkStaticConstMacro(ImageDimension, unsigned int, TImage::ImageDimension); + + typedef itk::Image mask_t; + + /** Type of the transform base class */ + typedef Transform + transform_t; + + typedef typename transform_t::InputPointType point_t; + typedef typename transform_t::ParametersType params_t; + typedef typename transform_t::JacobianType jacobian_t; + + /** Gaussian filter to compute the gradient of the mosaic images */ + // typedef typename NumericTraits::RealType + typedef float scalar_t; + + typedef CovariantVector gradient_pixel_t; + + typedef Image gradient_image_t; + + typedef GradientRecursiveGaussianImageFilter gradient_filter_t; + + /** Type of the measure. */ + typedef Superclass::MeasureType measure_t; + + /** Type of the derivative. */ + typedef Superclass::DerivativeType derivative_t; + + /** Get the number of pixels considered in the computation. */ + itkGetConstReferenceMacro(NumberOfPixelsCounted, unsigned long); + + /** Set/Get the region over which the metric will be computed */ + itkSetMacro(MosaicRegion, typename image_t::RegionType); + itkGetConstReferenceMacro(MosaicRegion, typename image_t::RegionType); + + /** Initialize transform parameter offsets. + + Setup the mapping from individual transform parameter indices to + the corresponding indices in the concatenated parameter vector. + + The radial distortion transforms typically share the same parameters + across all images in the mosaic, therefore it is slow and unnecessary + to duplicate the same parameters parameters in the concatenated + parameters vector. Instead, the parameters are store only once. + This also allows a significant speedup of the optimization. On the + other hand, translation parameters are typically unique for each + image. + + params_shared is a vector of boolean flags indicating which of + the transform parameters are shared (true) or unique (false). + + NOTE: this function will fail if the transform_ has not been + initialized. + */ + void + setup_param_map(const std::vector & params_shared, const std::vector & params_active); + + /** Concatenate parameters of each transform into one big vector. + + NOTE: this function will fail if SetupTransformParameterOffsets + has not been called yet. + */ + params_t + GetTransformParameters() const; + + /** virtual: Set the parameters defining the transforms: + + NOTE: this function will fail if SetupTransformParameterOffsets + has not been called yet. + */ + void + SetTransformParameters(const params_t & parameters) const; + + /** virtual: Return the number of parameters required by the transforms. + + NOTE: this function will fail if SetupTransformParameterOffsets + has not been called yet. + */ + unsigned int + GetNumberOfParameters() const; + + /** Initialize the Metric by making sure that all the components + are present and plugged together correctly. + */ + virtual void + Initialize(); + + /** virtual: Get the value for single valued optimizers. */ + measure_t + GetValue(const params_t & parameters) const + { + measure_t measure; + derivative_t derivative; + GetValueAndDerivative(parameters, measure, derivative); + return measure; + } + + /** virtual: Get the derivatives of the match measure. */ + void + GetDerivative(const params_t & parameters, derivative_t & derivative) const + { + measure_t measure; + GetValueAndDerivative(parameters, measure, derivative); + } + + /** virtual: Get value and derivatives for multiple valued optimizers. */ + void + GetValueAndDerivative(const params_t & parameters, measure_t & value, derivative_t & derivative) const; + + //---------------------------------------------------------------- + // image_data_t + // + // This datastructure is used to speed up access to relevant + // information when evaluating GetValueAndDerivative: + // + class image_data_t + { + public: + image_data_t() + : id_(~0) + , P_(measure_t(0)) + , dPdx_(measure_t(0)) + , dPdy_(measure_t(0)) + {} + + // image id: + unsigned int id_; + + // image value: + measure_t P_; + + // partial derivative with respect to x: + measure_t dPdx_; + + // partial derivative with respect to y: + measure_t dPdy_; + + // a pointer to the Jacobian of the transform: + const jacobian_t * J_; + }; + + // calculate the bounding box for a given set of image bounding boxes: + void + CalcMosaicBBox(point_t & mosaic_min, + point_t & mosaic_max, + std::vector & image_min, + std::vector & image_max) const; + + // calculate the mosaic variance image as well as maximum and mean variance: + typename image_t::Pointer + variance(measure_t & max_var, measure_t & avg_var) const; + + typename image_t::Pointer + variance(const typename TImage::SpacingType & sp, + const pnt2d_t & mosaic_min, + const pnt2d_t & mosaic_max, + measure_t & max_var, + measure_t & avg_var) const; + + typename image_t::Pointer + variance(const typename TImage::SpacingType & sp, + const pnt2d_t & mosaic_min, + const typename TImage::SizeType & sz, + measure_t & max_var, + measure_t & avg_var) const; + + typename image_t::Pointer + variance() const + { + measure_t max_var; + measure_t avg_var; + return variance(max_var, avg_var); + } + + // mosaic images: + std::vector image_; + std::vector mask_; + + // image transforms (cascaded): + mutable std::vector transform_; + + // image interpolators: + std::vector interpolator_; + + // image gradients: + std::vector gradient_; + + // adresses of the individual transform parameter indices within the + // concatenated parameter vector: + std::vector> address_; + + // number of shared/unique parameters per transform: + unsigned int n_shared_; + unsigned int n_unique_; + + // this mask defines which of the individual transform parameters are + // active and will be included into the metric parameter vector: + std::vector param_active_; + +protected: + ImageMosaicVarianceMetric() + : n_shared_(0) + , n_unique_(0) + , param_active_(0) + , m_NumberOfPixelsCounted(0) + {} + + virtual ~ImageMosaicVarianceMetric() {} + + // standard ITK debugging helper: + void + PrintSelf(std::ostream & os, Indent indent) const; + + // number of pixels in the overlapping regions of the mosaic: + mutable unsigned long m_NumberOfPixelsCounted; + +private: + // unimplemented on purpose: + ImageMosaicVarianceMetric(const Self &); + void + operator=(const Self &); + + typename image_t::RegionType m_MosaicRegion; +}; + +} // end namespace itk + +#ifndef ITK_MANUAL_INSTANTIATION +# include "itkImageMosaicVarianceMetric.hxx" +#endif // ITK_MANUAL_INSTANTIATION + +#endif // __itkImageMosaicVarianceMetric_h diff --git a/include/itkImageMosaicVarianceMetric.hxx b/include/itkImageMosaicVarianceMetric.hxx new file mode 100644 index 0000000..35e1c88 --- /dev/null +++ b/include/itkImageMosaicVarianceMetric.hxx @@ -0,0 +1,751 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : itkImageMosaicVarianceMetric.txx +// Author : Pavel A. Koshevoy +// Created : 2005/06/22 17:19 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : A metric class measuring mean pixel variance within +// the mosaic tile overlap regions. The metric derivative + +#ifndef _itkImageMosaicVarianceMetric_hxx +#define _itkImageMosaicVarianceMetric_hxx + +// local includes: +#include "itkIRCommon.h" +#include "itkIRUtils.h" +#include "IRTerminator.h" + +// system includes: +#include + +// ITK includes: +#include +#include + + +namespace itk +{ + +//---------------------------------------------------------------- +// setup_param_map +// +template +void +ImageMosaicVarianceMetric::setup_param_map(const std::vector & param_shared, + const std::vector & param_active) +{ + const unsigned int num_transforms = transform_.size(); + assert(num_transforms > 0); + + const unsigned int n_params = param_shared.size(); + assert(n_params > 0 && n_params == param_active.size() && n_params == transform_[0]->GetNumberOfParameters()); + + // save the active parameters mask: + param_active_ = param_active; + + // count the number of shared/unique params: + n_shared_ = 0; + n_unique_ = 0; + for (unsigned int i = 0; i < n_params; i++) + { + n_shared_ += param_active[i] && param_shared[i]; + n_unique_ += param_active[i] && !param_shared[i]; + } + + // build a mapping into the concatenated parameters vector, + // shared parameters will be stored at the head of the vector + // (to help with debugging) followed by the unique parameters: + resize(address_, num_transforms, n_params); + + unsigned int u_off = n_shared_; + for (unsigned int i = 0; i < num_transforms; i++) + { + unsigned int s_off = 0; + for (unsigned int j = 0; j < n_params; j++) + { + if (!param_active[j]) + continue; + + if (param_shared[j]) + { + address_[i][j] = s_off; + s_off++; + } + else + { + address_[i][j] = u_off; + u_off++; + } + + // FIXME: + // cout << "address[" << i << "][" << j << "] = " << address_[i][j] + // << endl; + } + } +} + +//---------------------------------------------------------------- +// GetTransformParameters +// +template +typename ImageMosaicVarianceMetric::params_t +ImageMosaicVarianceMetric::GetTransformParameters() const +{ + const unsigned int num_transforms = transform_.size(); + assert(num_transforms > 0); + + const unsigned int n_params = param_active_.size(); + assert(n_params > 0); + + params_t parameters(GetNumberOfParameters()); + for (unsigned int i = 0; i < num_transforms; i++) + { + params_t params = transform_[i]->GetParameters(); + for (unsigned int k = 0; k < n_params; k++) + { + if (!param_active_[k]) + continue; + parameters[address_[i][k]] = params[k]; + } + } + + return parameters; +} + +//---------------------------------------------------------------- +// SetTransformParameters +// +template +void +ImageMosaicVarianceMetric::SetTransformParameters(const params_t & parameters) const +{ + const unsigned int num_transforms = transform_.size(); + assert(num_transforms > 0); + + const unsigned int n_params = param_active_.size(); + assert(n_params > 0); + + // extract individual transform parameter values: + for (unsigned int i = 0; i < num_transforms; i++) + { + params_t params = transform_[i]->GetParameters(); + for (unsigned int k = 0; k < n_params; k++) + { + if (!param_active_[k]) + continue; + params[k] = parameters[address_[i][k]]; + } + transform_[i]->SetParameters(params); + } +} + +//---------------------------------------------------------------- +// GetNumberOfParameters +// +template +unsigned int +ImageMosaicVarianceMetric::GetNumberOfParameters() const +{ + const unsigned int num_transforms = transform_.size(); + return n_shared_ + n_unique_ * num_transforms; +} + +//---------------------------------------------------------------- +// Initialize +// +template +void +ImageMosaicVarianceMetric::Initialize() +{ +#if 0 + cout << "sizeof(pixel_t) = " << sizeof(pixel_t) << endl + << "sizeof(scalar_t) = " << sizeof(scalar_t) << endl + << "sizeof(measure_t) = " << sizeof(measure_t) << endl + << "sizeof(gradient_pixel_t) = " << sizeof(gradient_pixel_t) << endl + << endl; +#endif + + const unsigned int num_transforms = address_.size(); + + if (num_transforms == 0) + { + itkExceptionMacro(<< "call setup_param_map first"); + } + + for (unsigned int i = 0; i < num_transforms; i++) + { + if (!transform_[i]) + { + itkExceptionMacro(<< "One of the transforms is missing"); + } + + if (!image_[i]) + { + itkExceptionMacro(<< "One of the images is missing"); + } + else if (image_[i]->GetSource()) + { + // if the image is provided by a source, update the source: + image_[i]->GetSource()->Update(); + } + } + + // setup the interpolators, calculate the gradient images: + interpolator_.resize(num_transforms); + gradient_.resize(num_transforms); + for (unsigned int i = 0; i < num_transforms; i++) + { + interpolator_[i] = interpolator_t::New(); + interpolator_[i]->SetInputImage(image_[i]); + + // calculate the image gradient: + typename gradient_filter_t::Pointer gradient_filter = gradient_filter_t::New(); + gradient_filter->SetInput(image_[i]); + + const typename image_t::SpacingType & spacing = image_[i]->GetSpacing(); + double maximum_spacing = 0.0; + for (unsigned int j = 0; j < ImageDimension; j++) + { + maximum_spacing = std::max(maximum_spacing, spacing[j]); + } + + gradient_filter->SetSigma(maximum_spacing); + gradient_filter->SetNormalizeAcrossScale(true); + + try + { + gradient_filter->Update(); + } + catch (itk::ExceptionObject & exception) + { + // oops: + cerr << "gradient filter threw an exception:" << endl << exception << endl; + throw exception; + } + + gradient_[i] = gradient_filter->GetOutput(); + } + + // If there are any observers on the metric, call them to give the + // user code a chance to set parameters on the metric: + this->InvokeEvent(InitializeEvent()); +} + +//---------------------------------------------------------------- +// GetValueAndDerivative +// +// FIXME: the following code assumes a 2D mosaic: +// +template +void +ImageMosaicVarianceMetric::GetValueAndDerivative(const params_t & parameters, + measure_t & measure, + derivative_t & derivative) const +{ + WRAP(itk_terminator_t terminator("ImageMosaicVarianceMetric::GetValueAndDerivative")); + + typedef typename image_t::IndexType index_t; + typedef typename image_t::SpacingType spacing_t; + typedef typename image_t::RegionType::SizeType imagesz_t; + typedef typename image_t::RegionType region_t; + + itkDebugMacro("GetValueAndDerivative( " << parameters << " ) "); + + // shortcuts: + const unsigned int num_images = interpolator_.size(); + if (num_images == 0) + { + itkExceptionMacro(<< "You forgot to call Initialize()"); + } + + // number of parameters per transform: + const unsigned int n_params = param_active_.size(); + if (n_params == 0) + { + itkExceptionMacro(<< "You forgot to call setup_param_map()"); + } + + // the derivative vector size: + const unsigned int n_concat = GetNumberOfParameters(); + + // compare the previous parameters to the next set of parameters: +#if 0 + { + params_t prev = GetTransformParameters(); + params_t diff = parameters; + cout << "0 diff: "; + for (unsigned int i = 0; i < n_concat; i++) + { + diff[i] -= prev[i]; + cout << setw(8 + 3) << diff[i]; + if (i == (n_concat - 1) / 2) cout << endl << "1 diff: "; + else if (i == (n_concat - 1)) cout << endl; + } + cout << endl; + } +#endif + +#if 0 + { + cout << "new parameters:\n" << parameters << endl; + for (unsigned int i = 0; i < transform_.size(); i++) + { + cout << i << ". " << transform_[i] << endl; + } + } +#endif + + // update the transforms: + SetTransformParameters(parameters); + + // calculate image bounding boxes in the mosaic space, as well as the + // mosaic bounding box: + pnt2d_t mosaic_min = pnt2d(std::numeric_limits::max(), std::numeric_limits::max()); + pnt2d_t mosaic_max = pnt2d(-mosaic_min[0], -mosaic_min[1]); + std::vector min(num_images); + std::vector max(num_images); + CalcMosaicBBox(mosaic_min, mosaic_max, min, max); + + // mosaic will have the same spacing as the first image in the mosaic: + spacing_t spacing = image_[0]->GetSpacing(); + imagesz_t mosaic_sz; + { + double nx = (mosaic_max[0] - mosaic_min[0]) / spacing[0]; + double ny = (mosaic_max[1] - mosaic_min[1]) / spacing[1]; + + mosaic_sz[0] = (unsigned int)(nx + 0.5); + mosaic_sz[1] = (unsigned int)(ny + 0.5); + } + + // update the region of interest if necessary: + region_t ROI = m_MosaicRegion; + if (ROI.GetNumberOfPixels() == 0) + { + // use largest possible region: + index_t index; + index[0] = 0; + index[1] = 0; + ROI.SetIndex(index); + ROI.SetSize(mosaic_sz); + } + + // setup the mosaic image, but don't allocate any buffers for it: + typename image_t::Pointer mosaic = image_t::New(); + mosaic->SetRegions(mosaic_sz); + mosaic->SetSpacing(spacing); + mosaic->SetOrigin(pnt2d(mosaic_min[0], mosaic_min[1])); + + // reset the accumulators: + derivative = derivative_t(n_concat); + derivative.Fill(NumericTraits::Zero); + measure = NumericTraits::Zero; + m_NumberOfPixelsCounted = 0; + + // pre-allocate and initialize image data for each image: + std::vector image_data(num_images); + for (unsigned int i = 0; i < num_images; i++) + { + image_data[i].id_ = i; + } + + // preallocate derivatives of the mean and variance: + std::vector dMu(n_concat); + std::vector dV(n_concat); + + // iterate over the mosaic, evaluate the metric in the overlapping regions: + typedef itk::ImageRegionConstIteratorWithIndex itex_t; + // bool FIXME_FIRST_RUN = true; + itex_t itex(mosaic, ROI); + for (itex.GoToBegin(); !itex.IsAtEnd(); ++itex) + { + // make sure there hasn't been an interrupt: + WRAP(terminator.terminate_on_request()); + + pnt2d_t point; + mosaic->TransformIndexToPhysicalPoint(itex.GetIndex(), point); + + // find pixels in overlapping regions of the mosaic: + std::list overlap; + for (unsigned int k = 0; k < num_images; k++) + { + if (point[0] < min[k][0] || point[0] > max[k][0] || point[1] < min[k][1] || point[1] > max[k][1]) + continue; + + typename transform_t::Pointer & t = transform_[k]; + pnt2d_t pt_k = t->TransformPoint(point); + index_t index; + // make sure the pixel maps into the image: + image_[k]->TransformPhysicalPointToIndex(pt_k, index); + if (!interpolator_[k]->IsInsideBuffer(pt_k)) + continue; + + // make sure the pixel maps into the mask: + if (mask_[k].GetPointer() && mask_[k]->GetPixel(index) == 0) + continue; + + // shortcut: + image_data_t & data = image_data[k]; + + // get the image value: + // data.P_ = image_[k]->GetPixel(index); // faster + data.P_ = interpolator_[k]->Evaluate(pt_k); // slower + + // get the image gradient: + const gradient_pixel_t & gradient = gradient_[k]->GetPixel(index); + data.dPdx_ = gradient[0]; + data.dPdy_ = gradient[1]; + + // get the transform Jacobian: + data.J_ = &(t->GetJacobian(point)); + + // add to the list: + overlap.push_back(&data); + } + + // skip over the regions that do not overlap: + if (overlap.size() < 2) + continue; + + // found a pixel in an overlapping region, increment the counter: + m_NumberOfPixelsCounted++; + + // shortcut: + measure_t normalization_factor = measure_t(1) / measure_t(overlap.size()); + + // calculate the mean and derivative of the mean: + measure_t Mu = measure_t(0); + dMu.assign(n_concat, measure_t(0)); + for (typename std::list::const_iterator i = overlap.begin(); i != overlap.end(); ++i) + { + const image_data_t & data = *(*i); + const unsigned int * addr = &(address_[data.id_][0]); + const jacobian_t & J = *(data.J_); + + Mu += data.P_; + + for (unsigned int j = 0; j < n_params; j++) + { + dMu[addr[j]] += (J[0][j] * data.dPdx_ + J[1][j] * data.dPdy_) * normalization_factor; + } + } + Mu *= normalization_factor; +#if 1 + // normalize the shared portion of dMu: + for (unsigned int i = 0; i < n_shared_; i++) + { + dMu[i] *= normalization_factor; + } +#endif + + // calculate the variance: + measure_t V = measure_t(0); + dV.assign(n_concat, measure_t(0)); + for (typename std::list::const_iterator i = overlap.begin(); i != overlap.end(); ++i) + { + const image_data_t & data = *(*i); + const unsigned int * addr = &(address_[data.id_][0]); + const jacobian_t & J = *(data.J_); + + measure_t d = data.P_ - Mu; + V += d * d; + + for (unsigned int j = 0; j < n_params; j++) + { + dV[addr[j]] += + measure_t(2) * d * (J[0][j] * data.dPdx_ + J[1][j] * data.dPdy_ - dMu[addr[j]]) * normalization_factor; + } + } + V *= normalization_factor; +#if 1 + // normalize the shared portion of dV: + for (unsigned int i = 0; i < n_shared_; i++) + { + dV[i] *= normalization_factor; + } +#endif + +#if 0 + if (FIXME_FIRST_RUN && overlap.size() > 2) + { + FIXME_FIRST_RUN = false; + for (typename std::list::const_iterator + i = overlap.begin(); i != overlap.end(); ++i) + { + const image_data_t & data = *(*i); + const unsigned int * addr = &(address_[data.id_][0]); + const jacobian_t & J = *(data.J_); + + for (unsigned int k = 0; k < 2; k++) + { + cout << "J[" << k << "] ="; + for (unsigned int j = 0; j < n_params; j++) + { + cout << ' ' << J[k][j]; + } + cout << endl; + } + cout << endl; + } + } +#endif + // update the measure: + measure += V; + + // update the derivative: + for (unsigned int i = 0; i < n_concat; i++) + { + derivative[i] += dV[i]; + } + } + + if (m_NumberOfPixelsCounted == 0) + { + itkExceptionMacro(<< "mosaic contains no overlapping images"); + return; + } + + measure_t normalization_factor = measure_t(1) / measure_t(m_NumberOfPixelsCounted); + measure *= normalization_factor; + + for (unsigned int i = 0; i < n_concat; i++) + { + derivative[i] *= normalization_factor; + } + +#if 0 + cout << "stdV: " << measure << endl; + cout << "0 dV: "; + for (unsigned int i = 0; i < n_concat; i++) + { + cout << setw(7 + 3) << derivative[i]; + if (i == (n_concat - 1) / 2) cout << endl << "1 dV: "; + else if (i == (n_concat - 1)) cout << endl; + else cout << ' '; + } + cout << endl; +#endif +} + +//---------------------------------------------------------------- +// CalcMosaicBBox +// +template +void +ImageMosaicVarianceMetric::CalcMosaicBBox(point_t & mosaic_min, + point_t & mosaic_max, + std::vector & min, + std::vector & max) const +{ + // image space bounding boxes: + std::vector image_min; + std::vector image_max; + calc_image_bboxes(image_, image_min, image_max); + + std::vector ConstTransforms; + ConstTransforms.assign(transform_.begin(), transform_.end()); + + // mosaic space bounding boxes: + calc_mosaic_bboxes(ConstTransforms, image_min, image_max, min, max); + + // mosiac bounding box: + calc_mosaic_bbox(min, max, mosaic_min, mosaic_max); +} + +//---------------------------------------------------------------- +// variance +// +template +typename TImage::Pointer +ImageMosaicVarianceMetric::variance(measure_t & max_var, measure_t & avg_var) const +{ + max_var = measure_t(0); + avg_var = measure_t(0); + + // shortcut: + const unsigned int num_images = interpolator_.size(); + if (num_images == 0) + { + itkExceptionMacro(<< "You forgot to call Initialize()"); + } + + pnt2d_t mosaic_min = pnt2d(std::numeric_limits::max(), std::numeric_limits::max()); + pnt2d_t mosaic_max = pnt2d(-mosaic_min[0], -mosaic_min[1]); + std::vector min(num_images); + std::vector max(num_images); + CalcMosaicBBox(mosaic_min, mosaic_max, min, max); + + return variance(image_[0]->GetSpacing(), mosaic_min, mosaic_max, max_var, avg_var); +} + +//---------------------------------------------------------------- +// variance +// +template +typename TImage::Pointer +ImageMosaicVarianceMetric::variance(const typename TImage::SpacingType & mosaic_sp, + const pnt2d_t & mosaic_min, + const pnt2d_t & mosaic_max, + measure_t & max_var, + measure_t & avg_var) const +{ + typename TImage::SizeType mosaic_sz; + mosaic_sz[0] = (unsigned int)((mosaic_max[0] - mosaic_min[0]) / mosaic_sp[0]); + mosaic_sz[1] = (unsigned int)((mosaic_max[1] - mosaic_min[1]) / mosaic_sp[1]); + return variance(mosaic_sp, mosaic_min, mosaic_sz, max_var, avg_var); +} + +//---------------------------------------------------------------- +// variance +// +template +typename TImage::Pointer +ImageMosaicVarianceMetric::variance(const typename TImage::SpacingType & mosaic_sp, + const pnt2d_t & mosaic_min, + const typename TImage::SizeType & mosaic_sz, + measure_t & max_var, + measure_t & avg_var) const +{ + WRAP(itk_terminator_t terminator("ImageMosaicVarianceMetric::GetValueAndDerivative")); + + typedef typename image_t::IndexType index_t; + typedef typename image_t::RegionType::SizeType imagesz_t; + + max_var = measure_t(0); + avg_var = measure_t(0); + + // shortcut: + const unsigned int num_images = interpolator_.size(); + if (num_images == 0) + { + itkExceptionMacro(<< "You forgot to call Initialize()"); + } + + // calculate image bounding boxes in the mosaic space, as well as the + // mosaic bounding box: + pnt2d_t global_min = pnt2d(std::numeric_limits::max(), std::numeric_limits::max()); + pnt2d_t global_max = pnt2d(-global_min[0], -global_min[1]); + std::vector min(num_images); + std::vector max(num_images); + CalcMosaicBBox(global_min, global_max, min, max); + + // setup the mosaic image: + typename image_t::Pointer mosaic = image_t::New(); + mosaic->SetOrigin(mosaic_min); + mosaic->SetRegions(mosaic_sz); + mosaic->SetSpacing(mosaic_sp); + mosaic->Allocate(); + mosaic->FillBuffer(NumericTraits::Zero); + + // iterate over the mosaic, evaluate the metric in the overlapping regions: + typedef itk::ImageRegionIteratorWithIndex itex_t; + unsigned int pixel_count = 0; + + itex_t itex(mosaic, mosaic->GetLargestPossibleRegion()); + for (itex.GoToBegin(); !itex.IsAtEnd(); ++itex) + { + // make sure there hasn't been an interrupt: + WRAP(terminator.terminate_on_request()); + + pnt2d_t point; + mosaic->TransformIndexToPhysicalPoint(itex.GetIndex(), point); + + // find pixels in overlapping regions of the mosaic: + std::list overlap; + for (unsigned int k = 0; k < num_images; k++) + { + if (point[0] < min[k][0] || point[0] > max[k][0] || point[1] < min[k][1] || point[1] > max[k][1]) + continue; + + const typename transform_t::Pointer & t = transform_[k]; + pnt2d_t pt_k = t->TransformPoint(point); + index_t index; + image_[k]->TransformPhysicalPointToIndex(pt_k, index); + + // make sure the pixel maps into the image: + if (!interpolator_[k]->IsInsideBuffer(pt_k)) + continue; + + // make sure the pixel maps into the mask: + if (mask_[k].GetPointer() && mask_[k]->GetPixel(index) == 0) + continue; + + // get the image value, add it to the list: + overlap.push_back(interpolator_[k]->Evaluate(pt_k)); + } + + // skip over the regions that do not overlap: + if (overlap.size() < 2) + continue; + + // found a pixel in an overlapping region, increment the counter: + pixel_count++; + + // shortcut: + measure_t normalization_factor = measure_t(1) / measure_t(overlap.size()); + + // calculate the mean and derivative of the mean: + measure_t Mu = measure_t(0); + for (std::list::const_iterator i = overlap.begin(); i != overlap.end(); ++i) + { + Mu += *i; + } + Mu *= normalization_factor; + + // calculate the variance: + measure_t V = measure_t(0); + for (std::list::const_iterator i = overlap.begin(); i != overlap.end(); ++i) + { + measure_t d = *i - Mu; + V += d * d; + } + V *= normalization_factor; + itex.Set(pixel_t(V)); + + max_var = std::max(max_var, V); + avg_var += V; + } + + measure_t normalization_factor = measure_t(1) / measure_t(pixel_count); + avg_var *= normalization_factor; + + return mosaic; +} + +//---------------------------------------------------------------- +// PrintSelf +// +template +void +ImageMosaicVarianceMetric::PrintSelf(std::ostream & os, Indent indent) const +{ + // FIXME: write me: + + Superclass::PrintSelf(os, indent); + os << indent << "MosaicRegion: " << m_MosaicRegion << std::endl + << indent << "Number of Pixels Counted: " << m_NumberOfPixelsCounted << std::endl; +} + +} // end namespace itk + + +#endif // _itkImageMosaicVarianceMetric_txx diff --git a/include/itkInverseTransform.h b/include/itkInverseTransform.h new file mode 100644 index 0000000..276b815 --- /dev/null +++ b/include/itkInverseTransform.h @@ -0,0 +1,134 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : itkInverseTransform.h +// Author : Pavel A. Koshevoy +// Created : 2005/06/03 10:16 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : A inverse transform class. + +#ifndef __itkInverseTransform_h +#define __itkInverseTransform_h + +// system includes: +#include + +// ITK includes: +#include +#include + + +//---------------------------------------------------------------- +// itk::InverseTransform +// +namespace itk +{ + //---------------------------------------------------------------- + // InverseTransform + // + template + class InverseTransform : + public Transform< typename ForwardTransform::ScalarType, + ForwardTransform::OutputSpaceDimension, + ForwardTransform::InputSpaceDimension > + { + public: + /** Standard class typedefs. */ + typedef InverseTransform Self; + + typedef Transform< typename ForwardTransform::ScalarType, + ForwardTransform::OutputSpaceDimension, + ForwardTransform::InputSpaceDimension > Superclass; + + typedef SmartPointer< Self > Pointer; + typedef SmartPointer< const Self > ConstPointer; + + /** Base inverse transform type. */ + typedef typename Superclass::InverseTransformType InverseTransformType; + typedef SmartPointer< InverseTransformType > InverseTransformPointer; + + /** New method for creating an object using a factory. */ + itkNewMacro(Self); + + /** Run-time type information (and related methods). */ + itkTypeMacro( InverseTransform, Transform ); + + /** Standard scalar type for this class. */ + typedef typename Superclass::ScalarType ScalarType; + + /** Type of the input parameters. */ + typedef typename Superclass::ParametersType ParametersType; + + /** Type of the Jacobian matrix. */ + typedef typename Superclass::JacobianType JacobianType; + + /** Standard coordinate point type for this class. */ + typedef typename Superclass::InputPointType InputPointType; + typedef typename Superclass::OutputPointType OutputPointType; + + /** Dimension of the domain space. */ + itkStaticConstMacro(InputSpaceDimension, + unsigned int, + ForwardTransform::OutputSpaceDimension); + itkStaticConstMacro(OutputSpaceDimension, + unsigned int, + ForwardTransform::InputSpaceDimension); + + /** Set the forward transform pointer. */ + void SetForwardTransform(const ForwardTransform * forward) + { forward_ = forward; } + + /** Method to transform a point. */ + virtual OutputPointType TransformPoint(const InputPointType & y) const + { + assert(forward_ != NULL); + return forward_->BackTransformPoint(y); + } + + virtual const JacobianType & GetJacobian(const InputPointType &) const + { + itkExceptionMacro(<< "GetJacobian is not implemented " + "for InverseTransform"); + return this->m_Jacobian; + }; + + virtual unsigned int GetNumberOfParameters() const + { return 0; } + + virtual InverseTransformPointer GetInverse() const + { return const_cast(forward_); } + + protected: + InverseTransform(): Superclass(0, 0) {} + + private: + // disable default copy constructor and assignment operator: + InverseTransform(const Self & other); + const Self & operator = (const Self & t); + + // the transform whose inverse we are trying to evaluate: + const ForwardTransform * forward_; + }; + +} // namespace itk + +#endif // __itkInverseTransform_h diff --git a/include/itkLegendrePolynomialTransform.h b/include/itkLegendrePolynomialTransform.h new file mode 100644 index 0000000..6fa0745 --- /dev/null +++ b/include/itkLegendrePolynomialTransform.h @@ -0,0 +1,457 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : itkLegendrePolynomialTransform.h +// Author : Pavel A. Koshevoy +// Created : 2006/02/22 15:55 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : A bivariate centered/normalized Legendre polynomial +// transform and helper functions. + +#ifndef __itkLegendrePolynomialTransform_h +#define __itkLegendrePolynomialTransform_h + +// system includes: +#include +#include + +// ITK includes: +#include +#include +#include + +// local includes: +#include "itkInverseTransform.h" + +// VXL includes: +#include + + +//---------------------------------------------------------------- +// itk::LegendrePolynomialTransform +// +// Let +// A = (u - uc) / Xmax +// B = (v - vc) / Ymax +// +// where uc, vc correspond to the center of the image expressed in +// the coordinate system of the mosaic. +// +// The transform is defined as +// x(u, v) = Xmax * Sa +// y(u, v) = Ymax * Sb +// +// where +// Sa = sum(i in [0, N], sum(j in [0, i], a_jk * Pj(A) * Qk(B))); +// Sb = sum(i in [0, N], sum(j in [0, i], b_jk * Pj(A) * Qk(B))); +// +// where k = i - j and (Pj, Qk) are Legendre polynomials +// of degree (j, k) respectively. +// +namespace itk +{ +template +class LegendrePolynomialTransform : public Transform +{ +public: + // standard typedefs: + typedef LegendrePolynomialTransform Self; + typedef SmartPointer Pointer; + typedef SmartPointer ConstPointer; + + typedef Transform Superclass; + + // Base inverse transform type: + typedef Superclass InverseTransformType; + typedef SmartPointer InverseTransformPointer; + + // static constant for the degree of the polynomial: + itkStaticConstMacro(Degree, unsigned int, N); + + // static constant for the number of a_jk (or b_jk) coefficients: + itkStaticConstMacro(CoefficientsPerDimension, unsigned int, ((N + 1) * (N + 2)) / 2); + + // static constant for the length of the parameter vector: + itkStaticConstMacro(ParameterVectorLength, unsigned int, (N + 1) * (N + 2)); + + // RTTI: + itkTypeMacro(LegendrePolynomialTransform, Transform); + + // macro for instantiation through the object factory: + itkNewMacro(Self); + + /** Standard scalar type for this class. */ + typedef typename Superclass::ScalarType ScalarType; + + /** Dimension of the domain space. */ + itkStaticConstMacro(InputSpaceDimension, unsigned int, 2); + itkStaticConstMacro(OutputSpaceDimension, unsigned int, 2); + + // shortcuts: + typedef typename Superclass::ParametersType ParametersType; + typedef typename Superclass::JacobianType JacobianType; + + typedef typename Superclass::InputPointType InputPointType; + typedef typename Superclass::OutputPointType OutputPointType; + + // virtual: + OutputPointType + TransformPoint(const InputPointType & x) const; + + // Inverse transformations: + // If y = Transform(x), then x = BackTransform(y); + // if no mapping from y to x exists, then an exception is thrown. + InputPointType + BackTransformPoint(const OutputPointType & y) const; + + using InputDiffusionTensor3DType = typename Superclass::InputDiffusionTensor3DType; + using OutputDiffusionTensor3DType = typename Superclass::OutputDiffusionTensor3DType; + OutputDiffusionTensor3DType + TransformDiffusionTensor3D(const InputDiffusionTensor3DType & inputTensor, const InputPointType & point) const override + { + itkExceptionMacro("TransformDiffusionTensor3D( const InputDiffusionTensor3DType & ) is " + "unimplemented for " + << this->GetNameOfClass()); + } + + using InputVectorPixelType = typename Superclass::InputVectorPixelType; + using OutputVectorPixelType = typename Superclass::OutputVectorPixelType; + OutputVectorPixelType + TransformDiffusionTensor3D(const InputVectorPixelType & inputTensor, const InputPointType & point) const override + { + itkExceptionMacro("TransformDiffusionTensor3D( const InputVectorPixelType & ) is " + "unimplemented for " + << this->GetNameOfClass()); + } + + // virtual: + void + SetFixedParameters(const ParametersType & params) + { + this->m_FixedParameters = params; + } + + // virtual: + const ParametersType & + GetFixedParameters() const + { + return this->m_FixedParameters; + } + + // virtual: + void + SetParameters(const ParametersType & params) + { + this->m_Parameters = params; + } + + // virtual: + const ParametersType & + GetParameters() const + { + return this->m_Parameters; + } + + // virtual: + IdentifierType + GetNumberOfParameters() const override + { + return ParameterVectorLength; + } + + void + ComputeJacobianWithRespectToParameters(const InputPointType & point, JacobianType & jacobian) const override; + + // virtual: return an inverse of this transform. + InverseTransformPointer + GetInverse() const + { + typedef InverseTransform InvTransformType; + typename InvTransformType::Pointer inv = InvTransformType::New(); + inv->SetForwardTransform(this); + return inv.GetPointer(); + } + + // setup the fixed transform parameters: + void + setup( // image bounding box expressed in the physical space: + const double x_min, + const double x_max, + const double y_min, + const double y_max, + + // normalization parameters: + const double Xmax = 0.0, + const double Ymax = 0.0) + { + double & uc_ = this->m_FixedParameters[0]; + double & vc_ = this->m_FixedParameters[1]; + double & xmax_ = this->m_FixedParameters[2]; + double & ymax_ = this->m_FixedParameters[3]; + double & a00_ = this->m_Parameters[index_a(0, 0)]; + double & b00_ = this->m_Parameters[index_b(0, 0)]; + + // center of the image: + double xc = (x_min + x_max) / 2.0; + double yc = (y_min + y_max) / 2.0; + uc_ = xc; + vc_ = yc; + + // setup the normalization parameters: + if (Xmax != 0.0 && Ymax != 0.0) + { + xmax_ = Xmax; + ymax_ = Ymax; + } + else + { + const double w = x_max - x_min; + const double h = y_max - y_min; + + // -1 : 1 + xmax_ = w / 2.0; + ymax_ = h / 2.0; + } + + // setup a00, b00 (local translation parameters): + a00_ = xc / xmax_; + b00_ = yc / ymax_; + } + + // setup the translation parameters: + void + setup_translation( // translation is expressed in the physical space: + const double tx_Xmax = 0.0, + const double ty_Ymax = 0.0) + { + // incorporate translation into the (uc, vc) fixed parameters: + double & uc_ = this->m_FixedParameters[0]; + double & vc_ = this->m_FixedParameters[1]; + + // FIXME: the signs might be wrong here (20051101): + uc_ -= tx_Xmax; + vc_ -= ty_Ymax; + + // FIXME: untested: + /* + double & a00_ = this->m_Parameters[index_a(0, 0)]; + double & b00_ = this->m_Parameters[index_b(0, 0)]; + a00_ -= tx_Xmax / GetXmax(); + b00_ -= ty_Ymax / GetYmax(); + */ + } + + // helper required for numeric inverse transform calculation; + // evaluate F = T(x), J = dT/dx (another Jacobian): + void + eval(const std::vector & x, std::vector & F, std::vector> & J) const; + + // setup a linear system to solve for the parameters of this + // transform such that it maps points uv to xy: + void + setup_linear_system(const unsigned int start_with_degree, + const unsigned int degrees_covered, + const std::vector & uv, + const std::vector & xy, + vnl_matrix & M, + vnl_vector & bx, + vnl_vector & by) const; + + // find the polynomial coefficients such that this transform + // would map uv to xy: + void + solve_for_parameters(const unsigned int start_with_degree, + const unsigned int degrees_covered, + const std::vector & uv, // mosaic + const std::vector & xy, // tile + ParametersType & params) const; + + inline void + solve_for_parameters(const unsigned int start_with_degree, + const unsigned int degrees_covered, + const std::vector & uv, + const std::vector & xy) + { + ParametersType params = GetParameters(); + solve_for_parameters(start_with_degree, degrees_covered, uv, xy, params); + SetParameters(params); + } + + // calculate the number of coefficients of a given + // degree range (per dimension): + inline static unsigned int + count_coefficients(const unsigned int start_with_degree, const unsigned int degrees_covered) + { + return index_a(0, start_with_degree + degrees_covered) - index_a(0, start_with_degree); + } + + // accessors to the warp origin expressed in the mosaic coordinate system: + inline const double & + GetUc() const + { + return this->m_FixedParameters[0]; + } + + inline const double & + GetVc() const + { + return this->m_FixedParameters[1]; + } + + // accessors to the normalization parameters Xmax, Ymax: + inline const double & + GetXmax() const + { + return this->m_FixedParameters[2]; + } + + inline const double & + GetYmax() const + { + return this->m_FixedParameters[3]; + } + + // generate a mask of shared parameters: + static void + setup_shared_params_mask(bool shared, std::vector & mask) + { + mask.assign(ParameterVectorLength, shared); + mask[index_a(0, 0)] = false; + mask[index_b(0, 0)] = false; + } + + // Convert the j, k indeces associated with a(j, k) coefficient + // into an index that can be used with the parameters array: + inline static unsigned int + index_a(const unsigned int & j, const unsigned int & k) + { + return j + ((j + k) * (j + k + 1)) / 2; + } + + inline static unsigned int + index_b(const unsigned int & j, const unsigned int & k) + { + return CoefficientsPerDimension + index_a(j, k); + } + + // virtual: Generate a platform independent name: + std::string + GetTransformTypeAsString() const + { + std::string base = Superclass::GetTransformTypeAsString(); + std::ostringstream name; + name << base << '_' << N; + return name.str(); + } + +protected: + LegendrePolynomialTransform(); + + // virtual: + void + PrintSelf(std::ostream & s, Indent indent) const; + +private: + // disable default copy constructor and assignment operator: + LegendrePolynomialTransform(const Self & other); + const Self & + operator=(const Self & t); + + // polynomial coefficient accessors: + inline const double & + a(const unsigned int & j, const unsigned int & k) const + { + return this->m_Parameters[index_a(j, k)]; + } + + inline const double & + b(const unsigned int & j, const unsigned int & k) const + { + return this->m_Parameters[index_b(j, k)]; + } + +}; // class LegendrePolynomialTransform + +} // namespace itk + + +#ifndef ITK_MANUAL_INSTANTIATION +# include "itkLegendrePolynomialTransform.hxx" +#endif + + +//---------------------------------------------------------------- +// setup_transform +// +template +typename transform_t::Pointer +setup_transform(const itk::Point & bbox_min, const itk::Point & bbox_max) +{ + double w = bbox_max[0] - bbox_min[0]; + double h = bbox_max[1] - bbox_min[1]; + double Umax = w / 2.0; + double Vmax = h / 2.0; + + typename transform_t::Pointer t = transform_t::New(); + t->setup(bbox_min[0], bbox_max[0], bbox_min[1], bbox_max[1], Umax, Vmax); + + return t; +} + + +//---------------------------------------------------------------- +// setup_transform +// +template +typename transform_t::Pointer +setup_transform(const image_t * image) +{ + typedef typename image_t::IndexType index_t; + + index_t i00; + i00[0] = 0; + i00[1] = 0; + + typename image_t::PointType origin; + image->TransformIndexToPhysicalPoint(i00, origin); + + index_t i11; + i11[0] = 1; + i11[1] = 1; + + typename image_t::PointType spacing; + image->TransformIndexToPhysicalPoint(i11, spacing); + spacing[0] -= origin[0]; + spacing[1] -= origin[1]; + + typename image_t::SizeType sz = image->GetLargestPossibleRegion().GetSize(); + + typename image_t::PointType bbox_min = origin; + typename image_t::PointType bbox_max; + bbox_max[0] = bbox_min[0] + spacing[0] * double(sz[0]); + bbox_max[1] = bbox_min[1] + spacing[1] * double(sz[1]); + + return setup_transform(bbox_min, bbox_max); +} + + +#endif // __itkLegendrePolynomialTransform_h diff --git a/include/itkLegendrePolynomialTransform.hxx b/include/itkLegendrePolynomialTransform.hxx new file mode 100644 index 0000000..00df6fd --- /dev/null +++ b/include/itkLegendrePolynomialTransform.hxx @@ -0,0 +1,560 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : itkLegendrePolynomialTransform.txx +// Author : Pavel A. Koshevoy +// Created : 2006/02/22 15:55 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : A bivariate centered/normalized Legendre polynomial +// transform and helper functions. + +#ifndef _itkLegendrePolynomialTransform_txx +#define _itkLegendrePolynomialTransform_txx + +// local includes: +#include "itkNumericInverse.h" + +// system includes: +#include +#include + +// ITK includes: +#include + + +namespace itk +{ +namespace Legendre +{ +// Legendre polynomials: +static double +P0(const double & /* x */) +{ + return 1.0; +} + +static double +P1(const double & x) +{ + return x; +} + +static double +P2(const double & x) +{ + const double xx = x * x; + return (3.0 * xx - 1.0) / 2.0; +} + +static double +P3(const double & x) +{ + const double xx = x * x; + return ((5.0 * xx - 3.0) * x) / 2.0; +} + +static double +P4(const double & x) +{ + const double xx = x * x; + return ((35.0 * xx - 30.0) * xx + 3.0) / 8.0; +} + +static double +P5(const double & x) +{ + const double xx = x * x; + return (((63.0 * xx - 70.0) * xx + 15.0) * x) / 8.0; +} + +static double +P6(const double & x) +{ + const double xx = x * x; + return (((231.0 * xx - 315.0) * xx + 105.0) * xx - 5.0) / 16.0; +} + +//---------------------------------------------------------------- +// polynomial_t +// +typedef double (*polynomial_t)(const double & x); + +//---------------------------------------------------------------- +// A partial table of the Legendre polynomials +// +static polynomial_t P[] = { P0, P1, P2, P3, P4, P5, P6 }; + +// first derivatives of the Legendre polynomials: +static double +dP0(const double & /* x */) +{ + return 0.0; +} + +static double +dP1(const double & /* x */) +{ + return 1.0; +} + +static double +dP2(const double & x) +{ + return 3.0 * x; +} + +static double +dP3(const double & x) +{ + const double xx = x * x; + return (15.0 * xx - 3.0) / 2.0; +} + +static double +dP4(const double & x) +{ + const double xx = x * x; + return ((35.0 * xx - 15.0) * x) / 2.0; +} + +static double +dP5(const double & x) +{ + const double xx = x * x; + return ((315.0 * xx - 210.0) * xx + 15.0) / 8.0; +} + +static double +dP6(const double & x) +{ + const double xx = x * x; + return (((693.0 * xx - 630.0) * xx + 105.0) * x) / 8.0; +} + +//---------------------------------------------------------------- +// A partial table of the derivatives of Legendre polynomials +// +static polynomial_t dP[] = { dP0, dP1, dP2, dP3, dP4, dP5, dP6 }; +} // namespace Legendre + +//---------------------------------------------------------------- +// LegendrePolynomialTransform +// +template +LegendrePolynomialTransform::LegendrePolynomialTransform() +{ + // by default, the parameters are initialized for an identity transform: + // initialize the parameters for an identity transform: + for (unsigned int i = 0; i < ParameterVectorLength; i++) + { + this->m_Parameters[i] = 0.0; + } + + this->m_Parameters[index_a(1, 0)] = 1.0; + this->m_Parameters[index_b(0, 1)] = 1.0; + + // allocate some space for the fixed parameters: + this->m_FixedParameters.SetSize(4); + setup(-1, 1, -1, 1); + + this->m_Parameters[index_a(0, 0)] = GetUc() / GetXmax(); + this->m_Parameters[index_b(0, 0)] = GetVc() / GetYmax(); +} + +//---------------------------------------------------------------- +// TransformPoint +// +template +typename LegendrePolynomialTransform::OutputPointType +LegendrePolynomialTransform::TransformPoint(const InputPointType & x) const +{ + const double & uc = this->GetUc(); + const double & vc = this->GetVc(); + const double & Xmax = this->GetXmax(); + const double & Ymax = this->GetYmax(); + + const ScalarType & u = x[0]; + const ScalarType & v = x[1]; + + const double A = (u - uc) / Xmax; + const double B = (v - vc) / Ymax; + + double P[N + 1]; + double Q[N + 1]; + for (unsigned int i = 0; i <= N; i++) + { + P[i] = Legendre::P[i](A); + Q[i] = Legendre::P[i](B); + } + + double Sa = 0.0; + double Sb = 0.0; + + for (unsigned int i = 0; i <= N; i++) + { + for (unsigned int j = 0; j <= i; j++) + { + unsigned int k = i - j; + double PjQk = P[j] * Q[k]; + Sa += a(j, k) * PjQk; + Sb += b(j, k) * PjQk; + } + } + + OutputPointType y; + y[0] = Xmax * Sa; + y[1] = Ymax * Sb; + + return y; +} + +//---------------------------------------------------------------- +// BackTransformPoint +// +template +typename LegendrePolynomialTransform::InputPointType +LegendrePolynomialTransform::BackTransformPoint(const OutputPointType & y) const +{ + NumericInverse> inverse(*this); + + std::vector vy(2); + std::vector vx(2); + vy[0] = y[0]; + vy[1] = y[1]; + + // initialize x: first guess - x is close to y: + vx = vy; + const double & uc = this->GetUc(); + const double & vc = this->GetVc(); + + vx[0] += uc; + vx[1] += vc; + bool ok = inverse.transform(vy, vx, true); + if (!ok) + { + itk::ExceptionObject e(__FILE__, __LINE__); + e.SetDescription("could not perform a numeric inverse transformation " + "for a Legendre polynomial transform"); + throw e; + } + + OutputPointType x; + x[0] = vx[0]; + x[1] = vx[1]; + + return x; +} + +template +void +LegendrePolynomialTransform::ComputeJacobianWithRespectToParameters(const InputPointType & x, + JacobianType & jacobian) const +{ + const double & uc = this->GetUc(); + const double & vc = this->GetVc(); + const double & Xmax = this->GetXmax(); + const double & Ymax = this->GetYmax(); + + const ScalarType & u = x[0]; + const ScalarType & v = x[1]; + + const double A = (u - uc) / Xmax; + const double B = (v - vc) / Ymax; + + double P[N + 1]; + double Q[N + 1]; + for (unsigned int i = 0; i <= N; i++) + { + P[i] = Legendre::P[i](A); + Q[i] = Legendre::P[i](B); + } + + // zero-out the Jacobian: + for (unsigned int i = 0; i < ParameterVectorLength; i++) + { + jacobian(0, i) = 0.0; + jacobian(1, i) = 0.0; + } + + // derivatives with respect to a_jk, b_jk: + for (unsigned int i = 0; i <= N; i++) + { + for (unsigned int j = 0; j <= i; j++) + { + unsigned int k = i - j; + + double PjQk = P[j] * Q[k]; + jacobian(0, index_a(j, k)) = Xmax * PjQk; + jacobian(1, index_b(j, k)) = Ymax * PjQk; + } + } +} + +//---------------------------------------------------------------- +// eval +// +template +void +LegendrePolynomialTransform::eval(const std::vector & x, + std::vector & F, + std::vector> & J) const +{ + const double & uc = this->GetUc(); + const double & vc = this->GetVc(); + const double & Xmax = this->GetXmax(); + const double & Ymax = this->GetYmax(); + + const ScalarType & u = x[0]; + const ScalarType & v = x[1]; + + const double A = (u - uc) / Xmax; + const double B = (v - vc) / Ymax; + + double P[N + 1]; + double Q[N + 1]; + for (unsigned int i = 0; i <= N; i++) + { + P[i] = Legendre::P[i](A); + Q[i] = Legendre::P[i](B); + } + + double Sa = 0.0; + double Sb = 0.0; + + // derivatives with respect to a_jk, b_jk: + for (unsigned int i = 0; i <= N; i++) + { + for (unsigned int j = 0; j <= i; j++) + { + unsigned int k = i - j; + double PjQk = P[j] * Q[k]; + Sa += a(j, k) * PjQk; + Sb += b(j, k) * PjQk; + } + } + + F[0] = Xmax * Sa; + F[1] = Ymax * Sb; + + // derivatives with respect to u: + double dSa_du = 0.0; + double dSb_du = 0.0; + + for (unsigned int i = 1; i <= N; i++) + { + for (unsigned int j = 0; j <= i; j++) + { + unsigned int k = i - j; + double dPjQk = Legendre::dP[j](A) * Q[k]; + dSa_du += a(j, k) * dPjQk; + dSb_du += b(j, k) * dPjQk; + } + } + + // derivatives with respect to v: + double dSa_dv = 0.0; + double dSb_dv = 0.0; + + for (unsigned int i = 1; i <= N; i++) + { + for (unsigned int j = 0; j <= i; j++) + { + unsigned int k = i - j; + double dQkPj = Legendre::dP[k](B) * P[j]; + dSa_dv += a(j, k) * dQkPj; + dSb_dv += b(j, k) * dQkPj; + } + } + + // dx/du: + J[0][0] = dSa_du; + + // dx/dv: + J[0][1] = Xmax / Ymax * dSa_dv; + + // dy/du: + J[1][0] = Ymax / Xmax * dSb_du; + + // dy/dv: + J[1][1] = dSb_dv; +} + +//---------------------------------------------------------------- +// setup_linear_system +// +template +void +LegendrePolynomialTransform::setup_linear_system(const unsigned int start_with_degree, + const unsigned int degrees_covered, + const std::vector & uv, + const std::vector & xy, + vnl_matrix & M, + vnl_vector & bx, + vnl_vector & by) const +{ + if (degrees_covered == 0) + return; + + const double & uc = this->GetUc(); + const double & vc = this->GetVc(); + const double & Xmax = this->GetXmax(); + const double & Ymax = this->GetYmax(); + + static double P[N + 1]; + static double Q[N + 1]; + + const unsigned int & num_points = uv.size(); + assert(num_points == xy.size()); + + const unsigned int offset = index_a(0, start_with_degree); + + const unsigned int extent = index_a(0, start_with_degree + degrees_covered) - offset; + + M.set_size(num_points, extent); + bx.set_size(num_points); + by.set_size(num_points); + + for (unsigned int row = 0; row < num_points; row++) + { + const ScalarType & u = uv[row][0]; + const ScalarType & v = uv[row][1]; + const ScalarType & x = xy[row][0]; + const ScalarType & y = xy[row][1]; + + const double A = (u - uc) / Xmax; + const double B = (v - vc) / Ymax; + + bx[row] = x / Xmax; + by[row] = y / Ymax; + + for (unsigned int i = 0; i < start_with_degree + degrees_covered; i++) + { + P[i] = Legendre::P[i](A); + Q[i] = Legendre::P[i](B); + } + + for (unsigned int i = 0; i < start_with_degree; i++) + { + for (unsigned int j = 0; j <= i; j++) + { + const unsigned int k = i - j; + double PjQk = P[j] * Q[k]; + bx[row] -= a(j, k) * PjQk; + by[row] -= b(j, k) * PjQk; + } + } + + for (unsigned int i = start_with_degree; i < start_with_degree + degrees_covered; i++) + { + for (unsigned int j = 0; j <= i; j++) + { + const unsigned int k = i - j; + const unsigned int col = index_a(j, k) - offset; + + M[row][col] = P[j] * Q[k]; + } + } + } +} + +//---------------------------------------------------------------- +// solve_for_parameters +// +template +void +LegendrePolynomialTransform::solve_for_parameters(const unsigned int start_with_degree, + const unsigned int degrees_covered, + const std::vector & uv, + const std::vector & xy, + ParametersType & params) const +{ + if (degrees_covered == 0) + return; + + vnl_matrix M; + vnl_vector bx; + vnl_vector by; + + setup_linear_system(start_with_degree, degrees_covered, uv, xy, M, bx, by); + + // use SVD to find the inverse of M: + vnl_svd svd(M); + vnl_vector xa = svd.solve(bx); + vnl_vector xb = svd.solve(by); + + unsigned int offset = index_a(0, start_with_degree); + for (unsigned int i = start_with_degree; i < start_with_degree + degrees_covered; i++) + { + for (unsigned int j = 0; j <= i; j++) + { + const unsigned int k = i - j; + const unsigned int a_jk = index_a(j, k); + const unsigned int b_jk = index_b(j, k); + + params[a_jk] = xa[a_jk - offset]; + params[b_jk] = xb[a_jk - offset]; + } + } +} + +//---------------------------------------------------------------- +// PrintSelf +// +template +void +LegendrePolynomialTransform::PrintSelf(std::ostream & os, Indent indent) const +{ + Superclass::PrintSelf(os, indent); + + for (unsigned int i = 0; i <= N; i++) + { + for (unsigned int j = 0; j <= i; j++) + { + unsigned int k = i - j; + os << indent << "a(" << j << ", " << k << ") = " << a(j, k) << std::endl; + } + } + for (unsigned int i = 0; i <= N; i++) + { + for (unsigned int j = 0; j <= i; j++) + { + unsigned int k = i - j; + os << indent << "b(" << j << ", " << k << ") = " << b(j, k) << std::endl; + } + } + os << indent << "uc = " << GetUc() << std::endl + << indent << "vc = " << GetVc() << std::endl + << indent << "Xmax = " << GetXmax() << std::endl + << indent << "Ymax = " << GetYmax() << std::endl; +#if 0 + for (unsigned int i = 0; i < ParameterVectorLength; i++) + { + os << indent << "params[" << i << "] = " << this->m_Parameters[i] << ';' + << std::endl; + } +#endif +} + +} // namespace itk + + +#endif // _itkLegendrePolynomialTransform_txx diff --git a/include/itkMeshTransform.h b/include/itkMeshTransform.h new file mode 100644 index 0000000..2351df6 --- /dev/null +++ b/include/itkMeshTransform.h @@ -0,0 +1,369 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: nil -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : itkMeshTransform.h +// Author : Pavel A. Koshevoy +// Created : 2009/05/06 20:13 +// Copyright : (C) 2009 University of Utah +// License : GPLv2 +// Description : A discontinuous transform -- a Delaunay triangle mesh +// mapped to an image. At each vertex, in addition to image +// space coordinates a second set of coordinates is stored. +// This is similar to texture mapped OpenGL triangle meshes, +// where the texture coordinates correspond to the image space +// vertex coordinates. + +#ifndef __itkMeshTransform_h +#define __itkMeshTransform_h + +// system includes: +#include +#include +#include + +// ITK includes: +#include +#include +#include +#include + +// local includes: +#include "IRGridTransform.h" + + +//---------------------------------------------------------------- +// itk::MeshTransform +// +namespace itk +{ +class MeshTransform : public Transform +{ +public: + // standard typedefs: + typedef MeshTransform Self; + typedef SmartPointer Pointer; + typedef SmartPointer ConstPointer; + + typedef Transform Superclass; + + // Base inverse transform type: + typedef Superclass InverseTransformType; + typedef SmartPointer InverseTransformPointer; + + // RTTI: + itkTypeMacro(MeshTransform, Transform); + + // macro for instantiation through the object factory: + itkNewMacro(Self); + + /** Standard scalar type for this class. */ + typedef double ScalarType; + + /** Dimension of the domain space. */ + itkStaticConstMacro(InputSpaceDimension, unsigned int, 2); + itkStaticConstMacro(OutputSpaceDimension, unsigned int, 2); + + // shortcuts: + typedef Superclass::ParametersType ParametersType; + typedef Superclass::JacobianType JacobianType; + + typedef Superclass::InputPointType InputPointType; + typedef Superclass::OutputPointType OutputPointType; + + // virtual: + OutputPointType + TransformPoint(const InputPointType & x) const + { + OutputPointType y; + if (transform_.grid_.cols_ == 0 || transform_.grid_.cols_ == 0) + { + // No grid has been setup. Use the identity. + y[0] = x[0]; + y[1] = x[1]; + } + else if (is_inverse()) + { + pnt2d_t uv; + uv[0] = (x[0] - transform_.tile_min_[0]) / transform_.tile_ext_[0]; + uv[1] = (x[1] - transform_.tile_min_[1]) / transform_.tile_ext_[1]; + transform_.transform_inv(uv, y); + } + else + { + transform_.transform(x, y); + y[0] *= transform_.tile_ext_[0]; + y[1] *= transform_.tile_ext_[1]; + y[0] += transform_.tile_min_[0]; + y[1] += transform_.tile_min_[1]; + } + + // ITK is not forgiving to NaNs: + if (y[0] != y[0]) + { + y[0] = std::numeric_limits::max(); + y[1] = y[0]; + } + + return y; + } + + // Inverse transformations: + // If y = Transform(x), then x = BackTransform(y); + // if no mapping from y to x exists, then an exception is thrown. + InputPointType + BackTransformPoint(const OutputPointType & y) const + { + InputPointType x; + if (is_inverse()) + { + transform_.transform(y, x); + x[0] *= transform_.tile_ext_[0]; + x[1] *= transform_.tile_ext_[1]; + x[0] += transform_.tile_min_[0]; + x[1] += transform_.tile_min_[1]; + } + else + { + pnt2d_t uv; + uv[0] = (y[0] - transform_.tile_min_[0]) / transform_.tile_ext_[0]; + uv[1] = (y[1] - transform_.tile_min_[1]) / transform_.tile_ext_[1]; + transform_.transform_inv(uv, x); + } + + // ITK does not handle NaNs well: + if (x[0] != x[0]) + { + x[0] = std::numeric_limits::max(); + x[1] = x[0]; + } + + return x; + } + + using InputDiffusionTensor3DType = typename Superclass::InputDiffusionTensor3DType; + using OutputDiffusionTensor3DType = typename Superclass::OutputDiffusionTensor3DType; + OutputDiffusionTensor3DType + TransformDiffusionTensor3D(const InputDiffusionTensor3DType & inputTensor, const InputPointType & point) const override + { + itkExceptionMacro("TransformDiffusionTensor3D( const InputDiffusionTensor3DType & ) is " + "unimplemented for " + << this->GetNameOfClass()); + } + + using InputVectorPixelType = typename Superclass::InputVectorPixelType; + using OutputVectorPixelType = typename Superclass::OutputVectorPixelType; + OutputVectorPixelType + TransformDiffusionTensor3D(const InputVectorPixelType & inputTensor, const InputPointType & point) const override + { + itkExceptionMacro("TransformDiffusionTensor3D( const InputVectorPixelType & ) is " + "unimplemented for " + << this->GetNameOfClass()); + } + + // virtual: + void + SetFixedParameters(const ParametersType & params) + { + this->m_FixedParameters = params; + } + + // virtual: + const ParametersType & + GetFixedParameters() const + { + ParametersType params = this->m_FixedParameters; + + // acceleration grid size: + params[1] = transform_.grid_.rows_; + params[2] = transform_.grid_.cols_; + + // bounding box if the image associated with this transform: + params[3] = transform_.tile_min_[0]; + params[4] = transform_.tile_min_[1]; + params[5] = transform_.tile_ext_[0]; + params[6] = transform_.tile_ext_[1]; + + // number of vertices in the Delaunay triangle mesh: + params[7] = transform_.grid_.mesh_.size(); + + // update the parameters vector: + MeshTransform * fake = const_cast(this); + fake->m_FixedParameters = params; + return this->m_FixedParameters; + } + + // virtual: + void + SetParameters(const ParametersType & params) + { + this->m_Parameters = params; + + std::size_t accel_grid_rows = std::size_t(this->m_FixedParameters[1]); + std::size_t accel_grid_cols = std::size_t(this->m_FixedParameters[2]); + + pnt2d_t tile_min; + tile_min[0] = this->m_FixedParameters[3]; + tile_min[1] = this->m_FixedParameters[4]; + + pnt2d_t tile_max; + tile_max[0] = tile_min[0] + this->m_FixedParameters[5]; + tile_max[1] = tile_min[1] + this->m_FixedParameters[6]; + + const std::size_t num_points = std::size_t(this->m_FixedParameters[7]); + std::vector uv(num_points); + std::vector xy(num_points); + + for (unsigned int i = 0; i < num_points; i++) + { + const std::size_t idx = i * 4; + + uv[i][0] = params[idx + 0]; + uv[i][1] = params[idx + 1]; + xy[i][0] = params[idx + 2]; + xy[i][1] = params[idx + 3]; + } + + transform_.setup(tile_min, tile_max, uv, xy, accel_grid_rows, accel_grid_cols); + } + + // virtual: + const ParametersType & + GetParameters() const + { + ParametersType params(GetNumberOfParameters()); + + const std::size_t num_points = transform_.grid_.mesh_.size(); + for (std::size_t i = 0; i < num_points; i++) + { + const vertex_t & vx = transform_.grid_.mesh_[i]; + const std::size_t idx = i * 4; + + params[idx + 0] = vx.uv_[0]; + params[idx + 1] = vx.uv_[1]; + params[idx + 2] = vx.xy_[0]; + params[idx + 3] = vx.xy_[1]; + } + + // update the parameters vector: + MeshTransform * fake = const_cast(this); + fake->m_Parameters = params; + return this->m_Parameters; + } + + IdentifierType + GetNumberOfParameters() const override + { + return 4 * transform_.grid_.mesh_.size(); + } + + // virtual: return an inverse of this transform. + InverseTransformPointer + GetInverse() const + { + MeshTransform::Pointer inv = MeshTransform::New(); + inv->setup(transform_, !is_inverse()); + return inv.GetPointer(); + } + + // setup the transform: + void + setup(const the_mesh_transform_t & transform, const bool & is_inverse = false) + { + transform_ = transform; + GetParameters(); + GetFixedParameters(); + this->m_FixedParameters[0] = is_inverse ? 1.0 : 0.0; + } + + // inverse transform flag check: + inline bool + is_inverse() const + { + return this->m_FixedParameters[0] != 0.0; + } + + void + ComputeJacobianWithRespectToParameters(const InputPointType & point, JacobianType & jacobian) const override + { + // FIXME: 20061227 -- this function was written and not tested: + + // these scales are necessary to account for the fact that + // the_mesh_transform_t expects uv in the [0,1]x[0,1] range, + // where as we remap it into the image tile physical coordinates + // according to tile_min_ and tile_ext_: + double Su = transform_.tile_ext_[0]; + double Sv = transform_.tile_ext_[1]; + + unsigned int idx[3]; + double jac[12]; + jacobian.SetSize(2, GetNumberOfParameters()); + jacobian.Fill(0.0); + if (transform_.jacobian(point, idx, jac)) + { + for (unsigned int i = 0; i < 3; i++) + { + unsigned int addr = idx[i] * 2; + jacobian(0, addr) = Su * jac[i * 2]; + jacobian(0, addr + 1) = Su * jac[i * 2 + 1]; + jacobian(1, addr) = Sv * jac[i * 2 + 6]; + jacobian(1, addr + 1) = Sv * jac[i * 2 + 7]; + } + } + } + +protected: + MeshTransform() + { + this->m_FixedParameters.SetSize(8); + + // initialize the inverse flag: + this->m_FixedParameters[0] = 0.0; + + // acceleration grid size: + this->m_FixedParameters[1] = 0.0; + this->m_FixedParameters[2] = 0.0; + + // bounding box if the image associated with this transform: + this->m_FixedParameters[3] = std::numeric_limits::max(); + this->m_FixedParameters[4] = this->m_FixedParameters[3]; + this->m_FixedParameters[5] = -(this->m_FixedParameters[3]); + this->m_FixedParameters[6] = -(this->m_FixedParameters[3]); + + // number of vertices in the Delaunay triangle mesh: + this->m_FixedParameters[7] = 0.0; + } + +private: + // disable default copy constructor and assignment operator: + MeshTransform(const Self & other); + const Self & + operator=(const Self & t); + +public: + // the actual transform: + the_mesh_transform_t transform_; + +}; // class MeshTransform + +} // namespace itk + + +#endif // __itkMeshTransform_h diff --git a/include/itkMinimalStandardRandomVariateGenerator.h b/include/itkMinimalStandardRandomVariateGenerator.h deleted file mode 100644 index 05ea580..0000000 --- a/include/itkMinimalStandardRandomVariateGenerator.h +++ /dev/null @@ -1,96 +0,0 @@ -/*========================================================================= - * - * Copyright NumFOCUS - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0.txt - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - *=========================================================================*/ -#ifndef itkMinimalStandardRandomVariateGenerator_h -#define itkMinimalStandardRandomVariateGenerator_h - -#include "itkIntTypes.h" -#include "itkObjectFactory.h" -#include "itkRandomVariateGeneratorBase.h" -#include "NornirExport.h" -#include "itkNormalVariateGenerator.h" - -namespace itk -{ -namespace Statistics -{ -/** \class MinimalStandardRandomVariateGenerator - * \brief Linear congruential random random variate generator. - * - * This is a pseudo-random number generator for unsigned integers following - * - * \f[ - * X_{n+1} = (a X_n + c) \mod m - * \f] - * - * where \f$a\f$ is the Multiplier \f$c\f$ is the Increment and \f$m\f$ is - * the Modulus. - * - * https://en.wikipedia.com/wiki/Linear_congruential_generator - * - * The random numbers generated have a period \f$m\f$. - * - * This class uses \f$a = 48271\f$, \f$c = 0\f$, \f$m = 2^31 -1 = - * 2147483647\f$, the Minimial Standard configuration recommended by Park, - * Miller and Stockmeyer in 1993. - * - * \ingroup Nornir - */ -class Nornir_EXPORT MinimalStandardRandomVariateGenerator : public RandomVariateGeneratorBase -{ -public: - ITK_DISALLOW_COPY_AND_MOVE(MinimalStandardRandomVariateGenerator); - - /** Standard class aliases. */ - using Self = MinimalStandardRandomVariateGenerator; - using Superclass = RandomVariateGeneratorBase; - using Pointer = SmartPointer; - using ConstPointer = SmartPointer; - - using IntegerType = uint32_t; - - using NormalGeneratorType = itk::Statistics::NormalVariateGenerator; - - /** Run-time type information (and related methods). */ - itkTypeMacro(MinimalStandardRandomVariateGenerator, RandomVariateGeneratorBase); - - /** Method for creation through the object factory. */ - itkNewMacro(Self); - - /** initialize with a simple IntegerType */ - void - Initialize(int randomSeed); - - /** Get a variate in the range [0, 1] */ - double - GetVariate() override; - -protected: - MinimalStandardRandomVariateGenerator(); - ~MinimalStandardRandomVariateGenerator() override = default; - - void - PrintSelf(std::ostream & os, Indent indent) const override; - -private: - NormalGeneratorType::Pointer m_NormalGenerator; -}; - -} // end of namespace Statistics -} // end of namespace itk - -#endif // itkMinimalStandardRandomVariateGenerator_h diff --git a/include/itkNormalizeImageFilterWithMask.h b/include/itkNormalizeImageFilterWithMask.h new file mode 100644 index 0000000..c0163e6 --- /dev/null +++ b/include/itkNormalizeImageFilterWithMask.h @@ -0,0 +1,111 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : itkNormalizeImageFilterWithMask.h +// Author : Pavel A. Koshevoy +// Created : 2006/06/15 17:09 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : An enhanced version of the itk::NormalizeImageFilter +// adding support for a mask image. + +#ifndef __itkNormalizeImageFilterWithMask_h +#define __itkNormalizeImageFilterWithMask_h + +// ITK includes: +#include +#include +#include +#include + +// local includes: +#include "itkStatisticsImageFilterWithMask.h" + +namespace itk { + +/** \class NormalizeImageFilterWithMask + * \brief Normalize an image by setting its mean to zero and variance to one. + * + * NormalizeImageFilterWithMask shifts and scales an image so that the pixels + * in the image have a zero mean and unit variance. This filter uses + * StatisticsImageFilterWithMask to compute the mean and variance of the input + * and then applies ShiftScaleImageFilter to shift and scale the pixels. + * + * NB: since this filter normalizes the data to lie within -1 to 1, + * integral types will produce an image that DOES NOT HAVE a unit variance. + * \ingroup MathematicalImageFilters + */ +template +class ITK_EXPORT NormalizeImageFilterWithMask : + public ImageToImageFilter +{ +public: + /** Standard Self typedef */ + typedef NormalizeImageFilterWithMask Self; + typedef ImageToImageFilter Superclass; + typedef SmartPointer Pointer; + typedef SmartPointer ConstPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self); + + /** Runtime information support. */ + itkTypeMacro(NormalizeImageFilterWithMask, ImageToImageFilter); + + /** Image related typedefs. */ + typedef typename TInputImage::Pointer InputImagePointer; + typedef typename TOutputImage::Pointer OutputImagePointer; + + /** Type for the mask of the fixed image. Only pixels that are "inside" + this mask will be considered for the computation of the metric */ + typedef SpatialObject ImageMaskType; + typedef typename ImageMaskType::Pointer ImageMaskPointer; + + /** Set/Get the image mask. */ + itkSetObjectMacro( ImageMask, ImageMaskType ); + itkGetConstObjectMacro( ImageMask, ImageMaskType ); + +protected: + NormalizeImageFilterWithMask(); + + /** GenerateData. */ + void GenerateData (); + + // Override since the filter needs all the data for the algorithm + void GenerateInputRequestedRegion(); + +private: + NormalizeImageFilterWithMask(const Self&); //purposely not implemented + void operator=(const Self&); //purposely not implemented + + typename StatisticsImageFilterWithMask::Pointer m_StatisticsFilter; + typename ShiftScaleImageFilter::Pointer m_ShiftScaleFilter; + + mutable ImageMaskPointer m_ImageMask; +} ; // end of class + +} // end namespace itk + +#ifndef ITK_MANUAL_INSTANTIATION +#include "itkNormalizeImageFilterWithMask.hxx" +#endif + +#endif diff --git a/include/itkNormalizeImageFilterWithMask.hxx b/include/itkNormalizeImageFilterWithMask.hxx new file mode 100644 index 0000000..b2eef6a --- /dev/null +++ b/include/itkNormalizeImageFilterWithMask.hxx @@ -0,0 +1,98 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : itkNormalizeImageFilterWithMask.txx +// Author : Pavel A. Koshevoy +// Created : 2006/06/15 17:09 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : An enhanced version of the itk::NormalizeImageFilter +// adding support for a mask image. + +#ifndef _itkNormalizeImageFilterWithMask_txx +#define _itkNormalizeImageFilterWithMask_txx + +// ITK includes: +#include +#include +#include +#include + +namespace itk +{ + +template +NormalizeImageFilterWithMask +::NormalizeImageFilterWithMask() +{ + m_StatisticsFilter = 0; + m_StatisticsFilter = StatisticsImageFilterWithMask::New(); + m_ShiftScaleFilter = ShiftScaleImageFilter::New(); +} + +template +void +NormalizeImageFilterWithMask +::GenerateInputRequestedRegion() +{ + Superclass::GenerateInputRequestedRegion(); + if ( this->GetInput() ) + { + InputImagePointer image = + const_cast< typename Superclass::InputImageType * >( this->GetInput() ); + image->SetRequestedRegionToLargestPossibleRegion(); + } +} + +template +void +NormalizeImageFilterWithMask +::GenerateData() +{ + ProgressAccumulator::Pointer progress = ProgressAccumulator::New(); + progress->SetMiniPipelineFilter(this); + + progress->RegisterInternalFilter(m_StatisticsFilter,.5f); + progress->RegisterInternalFilter(m_ShiftScaleFilter,.5f); + + // Gather statistics + + m_StatisticsFilter->SetInput(this->GetInput()); + m_StatisticsFilter->GetOutput()->SetRequestedRegion(this->GetOutput()->GetRequestedRegion()); + m_StatisticsFilter->SetImageMask(this->m_ImageMask); + m_StatisticsFilter->Update(); + + // Set the parameters for Shift + m_ShiftScaleFilter->SetShift(-m_StatisticsFilter->GetMean()); + m_ShiftScaleFilter->SetScale(NumericTraits::RealType>::One + / m_StatisticsFilter->GetSigma()); + m_ShiftScaleFilter->SetInput(this->GetInput()); + + m_ShiftScaleFilter->GetOutput()->SetRequestedRegion(this->GetOutput()->GetRequestedRegion()); + m_ShiftScaleFilter->Update(); + + // Graft the mini pipeline output to this filters output + this->GraftOutput(m_ShiftScaleFilter->GetOutput()); +} + +} // end namespace itk + +#endif diff --git a/include/itkNumericInverse.h b/include/itkNumericInverse.h new file mode 100644 index 0000000..84bd026 --- /dev/null +++ b/include/itkNumericInverse.h @@ -0,0 +1,183 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : itkNumericTransform.h +// Author : Pavel A. Koshevoy +// Created : 2005/06/03 10:16 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : A numerical inverse transform class, based on the +// Newton-Raphson method for nonlinear systems of equations. + +#ifndef __itkNumericInverse_h +#define __itkNumericInverse_h + +// system includes: +#include +#include +#include + +// ITK includes: +#include +#include + +// VXL includes: +#include + +namespace help +{ + //---------------------------------------------------------------- + // nonlinear_system_evaluator_t + // + template + class nonlinear_system_evaluator_t + { + public: + virtual ~nonlinear_system_evaluator_t() {} + + // evaluate the nonlinear system of equations + // and its Jacobian (dF/dx) at a given point: + virtual void + operator() (const std::vector & x, + std::vector & F, + std::vector > & J) const = 0; + }; + + //---------------------------------------------------------------- + // NewtonRaphson + // + template + bool + NewtonRaphson(const nonlinear_system_evaluator_t & usrfun, + std::vector & x, // estimated root point + const unsigned int & ntrial, // number of iterations + const ScalarType & tolx, // convergence tolerance in x + const ScalarType & tolf) // convergence tolerance in F + { + // shortcut: + const unsigned int n = x.size(); + + std::vector F(n); + std::vector > J(n); + for (unsigned int i = 0; i < n; i++) J[i].resize(n); + + vnl_matrix A(n, n); + vnl_vector b(n); + + for (unsigned int k = 0; k < ntrial; k++) + { + // evaluate the function at the current position: + usrfun(x, F, J); + + // check for function convergence: + ScalarType errf = ScalarType(0); + for (unsigned int i = 0; i < n; i++) + { + errf += fabs(F[i]); + } + if (errf <= tolf) break; + + // setup the left hand side of the linear system: + for (unsigned int i = 0; i < n; i++) + { + for (unsigned int j = 0; j < n; j++) + { + A[i][j] = J[i][j]; + } + } + + // setup the right hand side of the linear system: + for (unsigned int i = 0; i < n; i++) + { + b[i] = -F[i]; + } + + vnl_svd svd(A); + vnl_vector dx = svd.solve(b); + + // check for root convergence: + ScalarType errx = ScalarType(0); + for (unsigned int i = 0; i < n; i++) + { + errx += fabs(dx[i]); + x[i] += dx[i]; + } + if (errx <= tolx) break; + } + + return true; + } + +} // namespace help + +namespace itk +{ + //---------------------------------------------------------------- + // NumericInverse + // + template + class NumericInverse : + public help::nonlinear_system_evaluator_t + { + public: + typedef TTransform TransformType; + typedef typename TTransform::ScalarType ScalarType; + + NumericInverse(const TransformType & transform): + transform_(transform) + {} + + // virtual: + void operator() (const std::vector & x, + std::vector & F, + std::vector > & J) const + { + transform_.eval(x, F, J); + + const unsigned int n = x.size(); + for (unsigned int i = 0; i < n; i++) + { + F[i] -= y_[i]; + } + } + + // If y = Transform(x), then x = BackTransform(y). + // given y, find x: + bool transform(const std::vector & y, + std::vector & x, + bool x_is_initialized = false) const + { + y_ = y; + if (!x_is_initialized) x = y; + return NewtonRaphson(*this, x, 50, 1e-12, 1e-12); + } + + private: + // the transform whose inverse we are trying to evaluate: + const TransformType & transform_; + + // the point for which we are tryying to find the inverse mapping: + mutable std::vector y_; + }; + +} // namespace itk + +#endif // __itkNumericInverse_h diff --git a/include/itkRBFTransform.h b/include/itkRBFTransform.h new file mode 100644 index 0000000..d1342b0 --- /dev/null +++ b/include/itkRBFTransform.h @@ -0,0 +1,322 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : itkRBFTransform.h +// Author : Pavel A. Koshevoy +// Created : 2007/01/23 10:15 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : A Thin Plate Spline transform. + +#ifndef __itkRBFTransform_h +#define __itkRBFTransform_h + +// system includes: +#include +#include + +// ITK includes: +#include +#include + +// local includes: +#include "itkInverseTransform.h" + + +//---------------------------------------------------------------- +// itk::RBFTransform +// +// Radial Basis Function transform: +// +// Let +// A = (u - uc) / Xmax +// B = (v - vc) / Ymax +// Ci = (u - ui) / Xmax +// Di = (v - vi) / Ymax +// +// where Xmax, Ymax are normalization parameters (typically the width and +// height of the image), uc, vc corresponds to the center or rotation +// of the image expressed in the coordinate system of the mosaic, +// and ui, vi are the mosaic space coordinates of the control points. +// +// The transform is defined as +// x(u, v) = Xmax * (a0 + a1 * A + a2 * B + F) +// y(u, v) = Ymax * (b0 + b1 * A + b2 * B + G) +// +// where +// F = sum(i in [0, k-1], fi * Q(Ci, Di)); +// G = sum(i in [0, k-1], gi * Q(Ci, Di)); +// Q = r2 * ln(r2) +// r2 = Ci^2 + Di^2 +// +namespace itk +{ +class RBFTransform : public Transform +{ +public: + // standard typedefs: + typedef RBFTransform Self; + typedef SmartPointer Pointer; + typedef SmartPointer ConstPointer; + + typedef Transform Superclass; + + // Base inverse transform type: + typedef Superclass InverseTransformType; + typedef SmartPointer InverseTransformPointer; + + // RTTI: + itkTypeMacro(RBFTransform, Transform); + + // macro for instantiation through the object factory: + itkNewMacro(Self); + + /** Standard scalar type for this class. */ + typedef double ScalarType; + + /** Dimension of the domain space. */ + itkStaticConstMacro(InputSpaceDimension, unsigned int, 2); + itkStaticConstMacro(OutputSpaceDimension, unsigned int, 2); + + // shortcuts: + typedef Superclass::ParametersType ParametersType; + typedef Superclass::JacobianType JacobianType; + + typedef Superclass::InputPointType InputPointType; + typedef Superclass::OutputPointType OutputPointType; + + // virtual: + OutputPointType + TransformPoint(const InputPointType & uv) const; + + // Inverse transformations: + // If y = Transform(x), then x = BackTransform(y); + // if no mapping from y to x exists, then an exception is thrown. + InputPointType + BackTransformPoint(const OutputPointType & y) const; + + using InputDiffusionTensor3DType = typename Superclass::InputDiffusionTensor3DType; + using OutputDiffusionTensor3DType = typename Superclass::OutputDiffusionTensor3DType; + OutputDiffusionTensor3DType + TransformDiffusionTensor3D(const InputDiffusionTensor3DType & inputTensor, const InputPointType & point) const override + { + itkExceptionMacro("TransformDiffusionTensor3D( const InputDiffusionTensor3DType & ) is " + "unimplemented for " + << this->GetNameOfClass()); + } + + using InputVectorPixelType = typename Superclass::InputVectorPixelType; + using OutputVectorPixelType = typename Superclass::OutputVectorPixelType; + OutputVectorPixelType + TransformDiffusionTensor3D(const InputVectorPixelType & inputTensor, const InputPointType & point) const override + { + itkExceptionMacro("TransformDiffusionTensor3D( const InputVectorPixelType & ) is " + "unimplemented for " + << this->GetNameOfClass()); + } + + // virtual: + void + SetFixedParameters(const ParametersType & params) + { + this->m_FixedParameters = params; + } + + // virtual: + const ParametersType & + GetFixedParameters() const + { + return this->m_FixedParameters; + } + + // virtual: + void + SetParameters(const ParametersType & params) + { + this->m_Parameters = params; + } + + // virtual: + const ParametersType & + GetParameters() const + { + return this->m_Parameters; + } + + IdentifierType + GetNumberOfParameters() const override + { + return this->m_Parameters.size(); + } + + // virtual: return an inverse of this transform. + InverseTransformPointer + GetInverse() const; + + // setup the transform parameters: + void + setup( // image bounding box expressed in the image space, + // defines transform normalization parameters: + const OutputPointType & tile_min, // tile space + const OutputPointType & tile_max, // tile space + + // landmark correspondences: + const unsigned int num_pts, // number of pairs + const InputPointType * uv, // mosaic space + const OutputPointType * xy); // tile space + + void + ComputeJacobianWithRespectToParameters(const InputPointType & point, JacobianType & jacobian) const override; + +#if 0 + // helper required for numeric inverse transform calculation; + // evaluate F = T(x), J = dT/dx (another Jacobian): + void eval(const std::vector & x, + std::vector & F, + std::vector > & J) const; +#endif + + // number of landmark correspondences: + inline unsigned int + num_points() const + { + return (this->m_FixedParameters.size() - 4) / 2; + } + + // calculate parameter vector indeces for various transform parameters: + inline static unsigned int + index_a(const unsigned int & i) + { + return i * 2; + } + + inline static unsigned int + index_b(const unsigned int & i) + { + return i * 2 + 1; + } + + inline static unsigned int + index_f(const unsigned int & i) + { + return i * 2 + 6; + } + + inline static unsigned int + index_g(const unsigned int & i) + { + return i * 2 + 7; + } + + inline static unsigned int + index_uv(const unsigned int & i) + { + return i * 2 + 4; + } + + // accessors to the normalization parameters Xmax, Ymax: + inline const double & + GetXmax() const + { + return this->m_FixedParameters[0]; + } + + inline const double & + GetYmax() const + { + return this->m_FixedParameters[1]; + } + + // accessors to the warp origin expressed in the mosaic coordinate system: + inline const double & + GetUc() const + { + return this->m_FixedParameters[2]; + } + + inline const double & + GetVc() const + { + return this->m_FixedParameters[3]; + } + + // mosaic space landmark accessor: + inline const double * + uv(const unsigned int & i) const + { + return &(this->m_FixedParameters[index_uv(i)]); + } + + // polynomial coefficient accessors: + inline const double & + a(const unsigned int & i) const + { + return this->m_Parameters[index_a(i)]; + } + + inline const double & + b(const unsigned int & i) const + { + return this->m_Parameters[index_b(i)]; + } + + // radial basis function accessors: + inline const double & + f(const unsigned int & i) const + { + return this->m_Parameters[index_f(i)]; + } + + inline const double & + g(const unsigned int & i) const + { + return this->m_Parameters[index_g(i)]; + } + + // the Radial Basis Function kernel: + inline static double + kernel(const double * uv, const double * uv_i, const double & Xmax, const double & Ymax) + { + double U = (uv[0] - uv_i[0]) / Xmax; + double V = (uv[1] - uv_i[1]) / Ymax; + double R = U * U + V * V; + return R == 0 ? 0 : R * log(R); + } + +protected: + RBFTransform(); + + // virtual: + void + PrintSelf(std::ostream & s, Indent indent) const; + +private: + // disable default copy constructor and assignment operator: + RBFTransform(const Self & other); + const Self & + operator=(const Self & t); + +}; // class RBFTransform + +} // namespace itk + + +#endif // __itkRBFTransform_h diff --git a/include/itkRadialDistortionTransform.h b/include/itkRadialDistortionTransform.h new file mode 100644 index 0000000..8206c9f --- /dev/null +++ b/include/itkRadialDistortionTransform.h @@ -0,0 +1,272 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : itkRadialDistortionTransform.h +// Author : Pavel A. Koshevoy +// Created : 2005/06/03 10:16 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : A radial distortion transform. + +#ifndef __itkRadialDistortionTransform_h +#define __itkRadialDistortionTransform_h + +// system includes: +#include +#include + +// ITK includes: +#include +#include + +// local includes: +#include "itkInverseTransform.h" + + +//---------------------------------------------------------------- +// itk::RadialDistortionTransform +// +// Let +// A = (a + ta * Rmax - ac) +// B = (b + tb * Rmax - bc) +// where ta, tb specify percentage of translation along the +// a and b axis respectively, and +// ac = 0.5 * (a_min + a_max) +// bc = 0.5 * (b_min + b_max) +// define the center of distortion (specified by a_min, a_max, +// b_min, b_max -- the bounding box of some image). +// +// The transform is defined as +// x(a, b) = ac + A * S +// y(a, b) = bc + B * S +// +// where +// S = sum(n in [0, N - 1], k[n] * pow(R2 / Rmax2, n)); +// R2 = A^2 + B^2 +// Rmax2 = Rmax * Rmax +// +namespace itk +{ +template +class RadialDistortionTransform : public Transform +{ +public: + // standard typedefs: + typedef RadialDistortionTransform Self; + typedef SmartPointer Pointer; + typedef SmartPointer ConstPointer; + + typedef Transform Superclass; + + // Base inverse transform type: + typedef Superclass InverseTransformType; + typedef SmartPointer InverseTransformPointer; + + // static constant for the number of polynomial coefficients: + itkStaticConstMacro(Nk, unsigned int, N); + + // RTTI: + itkTypeMacro(RadialDistortionTransform, Transform); + + // macro for instantiation through the object factory: + itkNewMacro(Self); + + /** Standard scalar type for this class. */ + typedef typename Superclass::ScalarType ScalarType; + + // shortcuts: + typedef typename Superclass::ParametersType ParametersType; + typedef typename Superclass::JacobianType JacobianType; + + typedef typename Superclass::InputPointType InputPointType; + typedef typename Superclass::OutputPointType OutputPointType; + + // virtual: + OutputPointType + TransformPoint(const InputPointType & p) const; + + // virtual: Inverse transformations: + // If y = Transform(x), then x = BackTransform(y); + // if no mapping from y to x exists, then an exception is thrown. + InputPointType + BackTransformPoint(const OutputPointType & y) const; + + using InputDiffusionTensor3DType = typename Superclass::InputDiffusionTensor3DType; + using OutputDiffusionTensor3DType = typename Superclass::OutputDiffusionTensor3DType; + OutputDiffusionTensor3DType + TransformDiffusionTensor3D(const InputDiffusionTensor3DType & inputTensor, const InputPointType & point) const override + { + itkExceptionMacro("TransformDiffusionTensor3D( const InputDiffusionTensor3DType & ) is " + "unimplemented for " + << this->GetNameOfClass()); + } + + using InputVectorPixelType = typename Superclass::InputVectorPixelType; + using OutputVectorPixelType = typename Superclass::OutputVectorPixelType; + OutputVectorPixelType + TransformDiffusionTensor3D(const InputVectorPixelType & inputTensor, const InputPointType & point) const override + { + itkExceptionMacro("TransformDiffusionTensor3D( const InputVectorPixelType & ) is " + "unimplemented for " + << this->GetNameOfClass()); + } + + // virtual: + void + SetFixedParameters(const ParametersType & params) + { + this->m_FixedParameters = params; + } + + // virtual: + const ParametersType & + GetFixedParameters() const + { + return this->m_FixedParameters; + } + + // virtual: + void + SetParameters(const ParametersType & params) + { + this->m_Parameters = params; + } + + // virtual: + const ParametersType & + GetParameters() const + { + return this->m_Parameters; + } + + IdentifierType + GetNumberOfParameters() const override + { + return N + 2; + } + + void + ComputeJacobianWithRespectToParameters(const InputPointType & point, JacobianType & jacobian) const override; + + // virtual: return an inverse of this transform. + InverseTransformPointer + GetInverse() const + { + typedef InverseTransform InvTransformType; + typename InvTransformType::Pointer inv = InvTransformType::New(); + inv->SetForwardTransform(this); + return inv.GetPointer(); + } + + // setup the fixed transform parameters: + void + setup( // image bounding box expressed in the physical space: + const double a_min, + const double a_max, + const double b_min, + const double b_max, + + // normalization parameter (image radius in physical space): + const double Rmax = 0.0) + { + double & ac_ = this->m_FixedParameters[0]; + double & bc_ = this->m_FixedParameters[1]; + double & rmax_ = this->m_FixedParameters[2]; + + // center of distortion: + ac_ = 0.5 * (a_min + a_max); + bc_ = 0.5 * (b_min + b_max); + + // setup the normalization parameter: + if (Rmax != 0.0) + { + rmax_ = Rmax; + } + else + { + const double w = a_max - a_min; + const double h = b_max - b_min; + rmax_ = sqrt(w * w + h * h) / 2.0; + } + } + + // setup the translation parameters: + void + setup_translation( // translation is expressed in the physical space: + const double ta_Rmax = 0.0, + const double tb_Rmax = 0.0) + { + const double & Rmax = this->m_FixedParameters[2]; + assert(Rmax != 0.0); + + // store ta,tb as translation relative to Rmax: + double & ta = this->m_Parameters[N]; + double & tb = this->m_Parameters[N + 1]; + ta = ta_Rmax / Rmax; + tb = tb_Rmax / Rmax; + } + + // helper required by BackTransform: + // evaluate F = T(x), J = dT/dx (another Jacobian): + void + eval(const std::vector & x, std::vector & F, std::vector> & J) const; + + // accessors to the fixed normalization parameter: + inline const double & + GetRmax() const + { + return this->m_FixedParameters[2]; + } + + // generate a mask of shared parameters: + static void + setup_shared_params_mask(bool shared, std::vector & mask) + { + mask.assign(N + 2, false); + for (unsigned int i = 0; i < N; i++) + { + mask[i] = shared; + } + } + +protected: + RadialDistortionTransform(); + + // virtual: + void + PrintSelf(std::ostream & s, Indent indent) const; + +private: + // disable default copy constructor and assignment operator: + RadialDistortionTransform(const Self & other); + const Self & + operator=(const Self & t); + +}; // class RadialDistortionTransform + +} // namespace itk + + +#ifndef ITK_MANUAL_INSTANTIATION +# include "itkRadialDistortionTransform.hxx" +#endif + +#endif // __itkRadialDistortionTransform_h diff --git a/include/itkRadialDistortionTransform.hxx b/include/itkRadialDistortionTransform.hxx new file mode 100644 index 0000000..67bf53f --- /dev/null +++ b/include/itkRadialDistortionTransform.hxx @@ -0,0 +1,277 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : itkRadialDistortionTransform.txx +// Author : Pavel A. Koshevoy +// Created : 2005/06/03 10:16 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : A radial distortion transform. + +#ifndef _itkRadialDistortionTransform_txx +#define _itkRadialDistortionTransform_txx + +// local includes: +#include "itkNumericInverse.h" + +// system includes: +#include +#include + + +namespace itk +{ +//---------------------------------------------------------------- +// RadialDistortionTransform +// +template +RadialDistortionTransform::RadialDistortionTransform() +{ + // by default, the parameters are initialized for an identity transform: + this->m_Parameters[0] = 1.0; + for (unsigned int i = 1; i < N + 2; i++) + { + this->m_Parameters[i] = 0.0; + } + + // allocate some space for the fixed parameters (ac, bc, Rmax): + this->m_FixedParameters.SetSize(3); + double & ac = this->m_FixedParameters[0]; + double & bc = this->m_FixedParameters[1]; + double & Rmax = this->m_FixedParameters[2]; + ac = 0.0; + bc = 0.0; + Rmax = 0.0; +} + +//---------------------------------------------------------------- +// TransformPoint +// +template +typename RadialDistortionTransform::OutputPointType +RadialDistortionTransform::TransformPoint(const InputPointType & x) const +{ + const double & ac = this->m_FixedParameters[0]; + const double & bc = this->m_FixedParameters[1]; + const double & Rmax = this->m_FixedParameters[2]; + + const double & ta = this->m_Parameters[N]; + const double & tb = this->m_Parameters[N + 1]; + + const ScalarType & a = x[0]; + const ScalarType & b = x[1]; + + const double A = (a + ta * Rmax - ac); + const double B = (b + tb * Rmax - bc); + const double A2 = A * A; + const double B2 = B * B; + const double R2 = A2 + B2; + const double Rmax2 = Rmax * Rmax; + const double RRmax2 = R2 / Rmax2; + + // n = 0: + double S = this->m_Parameters[0]; + + // n = 1 ... N-1: + double RRmax2n = RRmax2; + for (unsigned int n = 1; n < N; n++) + { + const double knRRmax2n = this->m_Parameters[n] * RRmax2n; + S += knRRmax2n; + RRmax2n *= RRmax2; + } + + OutputPointType y; + y[0] = ac + A * S; + y[1] = bc + B * S; + + return y; +} + +//---------------------------------------------------------------- +// BackTransformPoint +// +template +typename RadialDistortionTransform::InputPointType +RadialDistortionTransform::BackTransformPoint(const OutputPointType & y) const +{ + NumericInverse> inverse(*this); + + std::vector vy(2); + std::vector vx(2); + vy[0] = y[0]; + vy[1] = y[1]; + + // initialize x: first guess - x is close to y: + vx = vy; + const double & Rmax = this->m_FixedParameters[2]; + const double & ta = this->m_Parameters[N]; + const double & tb = this->m_Parameters[N + 1]; + vx[0] -= ta * Rmax; + vx[1] -= tb * Rmax; + bool ok = inverse.transform(vy, vx, true); + if (!ok) + assert(false); + + OutputPointType x; + x[0] = vx[0]; + x[1] = vx[1]; + + return x; +} + +template +void +RadialDistortionTransform::ComputeJacobianWithRespectToParameters(const InputPointType & x, + JacobianType & jacobian) const +{ + const double & ac = this->m_FixedParameters[0]; + const double & bc = this->m_FixedParameters[1]; + const double & Rmax = this->m_FixedParameters[2]; + + const double & ta = this->m_Parameters[N]; + const double & tb = this->m_Parameters[N + 1]; + + const ScalarType & a = x[0]; + const ScalarType & b = x[1]; + + const double A = (a + ta * Rmax - ac); + const double B = (b + tb * Rmax - bc); + const double A2 = A * A; + const double B2 = B * B; + const double R2 = A2 + B2; + const double Rmax2 = Rmax * Rmax; + const double RRmax2 = R2 / Rmax2; + + // derivatives with respect to kn: + double RRmax2n = double(1); + for (unsigned int n = 0; n < N; n++) + { + jacobian(0, n) = A * RRmax2n; + jacobian(1, n) = B * RRmax2n; + RRmax2n *= RRmax2; + } + + // derivatives with respect to ta, tb: + + // n = 0: + double P = double(0); + double Q = double(0); + + // n = 1 ... N-1: + double RRmax2n1 = double(1); // (R^2 / Rmax^2)^(n - 1) + for (unsigned int n = 1; n < N; n++) + { + const double & k = this->m_Parameters[n]; + const double scale_n = k * RRmax2n1; // kn * (R^2/Rmax^2)^n + P += scale_n; + Q += scale_n * double(n); + RRmax2n1 *= RRmax2; + } + + double S = this->m_Parameters[0] + RRmax2 * P; + + jacobian(0, N) = Rmax * S + (double(2) * A2 / Rmax) * Q; // dx/dta + jacobian(1, N + 1) = Rmax * S + (double(2) * B2 / Rmax) * Q; // dy/dtb + + jacobian(0, N + 1) = ((double(2) * A * B) / Rmax) * Q; // dx/dtb + jacobian(1, N) = jacobian(0, N + 1); // dy/dta +} + +//---------------------------------------------------------------- +// eval +// +template +void +RadialDistortionTransform::eval(const std::vector & x, + std::vector & F, + std::vector> & J) const +{ + const double & ac = this->m_FixedParameters[0]; + const double & bc = this->m_FixedParameters[1]; + const double & Rmax = this->m_FixedParameters[2]; + + const double & ta = this->m_Parameters[N]; + const double & tb = this->m_Parameters[N + 1]; + + const ScalarType & a = x[0]; + const ScalarType & b = x[1]; + + const double A = (a + ta * Rmax - ac); + const double B = (b + tb * Rmax - bc); + const double A2 = A * A; + const double B2 = B * B; + const double R2 = A2 + B2; + const double Rmax2 = Rmax * Rmax; + const double RRmax2 = R2 / Rmax2; + + // n = 0: + double P = double(0); + double Q = double(0); + + // n = 1 ... N-1: + double RRmax2n1 = double(1); // (R^2 / Rmax^2)^(n - 1) + for (unsigned int n = 1; n < N; n++) + { + const double & k = this->m_Parameters[n]; + const double scale_n = k * RRmax2n1; // kn * (R^2/Rmax^2)^n + P += scale_n; + Q += scale_n * double(n); + RRmax2n1 *= RRmax2; + } + + double S = this->m_Parameters[0] + RRmax2 * P; + F[0] = ac + A * S; + F[1] = bc + B * S; + + // calc dF/dx: + J[0][0] = Rmax * S + (double(2) * A2 / Rmax) * Q; + J[1][1] = Rmax * S + (double(2) * B2 / Rmax) * Q; + + J[0][1] = ((double(2) * A * B) / Rmax) * Q; + J[1][0] = J[0][1]; +} + +//---------------------------------------------------------------- +// PrintSelf +// +template +void +RadialDistortionTransform::PrintSelf(std::ostream & os, Indent indent) const +{ + Superclass::PrintSelf(os, indent); + + for (unsigned int i = 0; i < N; i++) + { + os << indent << "k[" << i << "] = " << this->m_Parameters[i] << std::endl; + } + + os << indent << "ta = " << this->m_Parameters[N] << std::endl + << indent << "tb = " << this->m_Parameters[N + 1] << std::endl + << indent << "ac = " << this->m_FixedParameters[0] << std::endl + << indent << "bc = " << this->m_FixedParameters[1] << std::endl + << indent << "Rmax = " << this->m_FixedParameters[2] << std::endl; +} + +} // namespace itk + + +#endif // _itkRadialDistortionTransform_txx diff --git a/include/itkRegularStepGradientDescentOptimizer2.h b/include/itkRegularStepGradientDescentOptimizer2.h new file mode 100644 index 0000000..abcfcc5 --- /dev/null +++ b/include/itkRegularStepGradientDescentOptimizer2.h @@ -0,0 +1,196 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : itkRegularStepGradientDescentOptimizer2.h +// Author : Pavel A. Koshevoy, Tolga Tasdizen +// Created : 2005/11/11 14:54 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : An enhanced version of the +// itk::RegularStepGradientDescentOptimizer +// fixing a bug with relaxation and adding support for +// step size increase (pick up pace), back tracking and +// keeping track of the best metric value +// and associated parameters. + +#ifndef __itkRegularStepGradientDescentOptimizer2_h +#define __itkRegularStepGradientDescentOptimizer2_h + +// ITK includes: +#include + +// the includes: +#include "IRLog.h" + + +namespace itk +{ +#if 0 +} +#endif + +//---------------------------------------------------------------- +// RegularStepGradientDescentOptimizer2 +// +class ITK_EXPORT RegularStepGradientDescentOptimizer2 : + public SingleValuedNonLinearOptimizer +{ +public: + /** Standard "Self" typedef. */ + typedef RegularStepGradientDescentOptimizer2 Self; + typedef SingleValuedNonLinearOptimizer Superclass; + typedef SmartPointer Pointer; + typedef SmartPointer ConstPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self); + + /** Run-time type information (and related methods). */ + itkTypeMacro(RegularStepGradientDescentOptimizer2, + SingleValuedNonLinearOptimizer); + + //---------------------------------------------------------------- + // StopConditionType + // + // Codes of stopping conditions: + // + typedef enum { + GradientMagnitudeTolerance = 1, + StepTooSmall = 2, + ImageNotAvailable = 3, + SamplesNotAvailable = 4, + MaximumNumberOfIterations = 5 + } StopConditionType; + + /** Specify whether to minimize or maximize the cost function. */ + itkSetMacro(Maximize, bool); + itkGetConstReferenceMacro(Maximize, bool); + itkBooleanMacro(Maximize); + + inline bool GetMinimize() const { return !m_Maximize; } + inline void SetMinimize(bool v) { this->SetMaximize(!v); } + inline void MinimizeOn() { SetMaximize(false); } + inline void MinimizeOff() { SetMaximize(true); } + + // virtual: + void StartOptimization(); + + // virtual: + void ResumeOptimization(); + + // virtual: + void StopOptimization(); + + // Set/Get parameters to control the optimization process: + itkSetMacro(MaximumStepLength, double); + itkSetMacro(MinimumStepLength, double); + itkGetConstReferenceMacro(MaximumStepLength, double); + itkGetConstReferenceMacro(MinimumStepLength, double); + + itkSetMacro(RelaxationFactor, double); + itkGetConstReferenceMacro(RelaxationFactor, double); + + itkSetMacro(NumberOfIterations, unsigned long); + itkGetConstReferenceMacro(NumberOfIterations, unsigned long); + + itkSetMacro(GradientMagnitudeTolerance, double); + itkGetConstReferenceMacro(GradientMagnitudeTolerance, double); + + itkSetMacro(BackTracking, bool); + itkGetConstReferenceMacro(BackTracking, bool); + + itkSetMacro(PickUpPaceSteps, unsigned int); + itkGetConstReferenceMacro(PickUpPaceSteps, unsigned int); + + inline void SetLog(the_log_t * log) + { log_ = log; } + + itkGetConstReferenceMacro(CurrentStepLength, double); + itkGetConstMacro(CurrentIteration, unsigned int); + itkGetConstReferenceMacro(StopCondition, StopConditionType); + itkGetConstReferenceMacro(Value, MeasureType); + itkGetConstReferenceMacro(Gradient, DerivativeType); + itkGetConstReferenceMacro(BestParams, ParametersType); + itkGetConstReferenceMacro(BestValue, MeasureType); + +protected: + RegularStepGradientDescentOptimizer2(); + virtual ~RegularStepGradientDescentOptimizer2() {} + + // advance one step following the gradient direction: + virtual void AdvanceOneStep(); + + // advance one step along the given direction vector + // scaled by the step length scale; this method is + // called by AdvanceOneStep: + virtual void StepAlongGradient(double step_length_scale, + const DerivativeType & step_direction); + + // virtual: + void PrintSelf(std::ostream& os, Indent indent) const; + + // data members: + DerivativeType m_Gradient; + DerivativeType m_PreviousGradient; + + bool m_Stop; + bool m_Maximize; + MeasureType m_Value; + MeasureType m_PreviousValue; + double m_GradientMagnitudeTolerance; + double m_MaximumStepLength; + double m_MinimumStepLength; + double m_CurrentStepLength; + double m_RelaxationFactor; + StopConditionType m_StopCondition; + unsigned long m_NumberOfIterations; + unsigned long m_CurrentIteration; + + // this keeps track of the best function value and corresponding parameters: + MeasureType m_BestValue; + ParametersType m_BestParams; + + // this flag controls whether the algorithm will step back + // to a previous position as well as relax every time it + // detects a worsening of the metric; + // default is false: + bool m_BackTracking; + + // this is the number of successful iterations that must occur + // after which the algorithm will increase the step size; + // default is 1000000: + unsigned int m_PickUpPaceSteps; + + the_log_t * log_; + +private: + // disable the copy constructor and assignment operator: + RegularStepGradientDescentOptimizer2(const Self &); + void operator = (const Self &); +}; + +#if 0 +{ +#endif +} // end namespace itk + + +#endif // __itkRegularStepGradientDescentOptimizer2_h diff --git a/include/itkStatisticsImageFilterWithMask.h b/include/itkStatisticsImageFilterWithMask.h new file mode 100644 index 0000000..1e111f8 --- /dev/null +++ b/include/itkStatisticsImageFilterWithMask.h @@ -0,0 +1,192 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : itkStatisticsImageFilterWithMask.h +// Author : Pavel A. Koshevoy +// Created : 2006/06/15 17:09 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : An enhanced version of the itk::StatisticsImageFilter +// adding support for a mask image. + +#ifndef __itkStatisticsImageFilterWithMask_h +#define __itkStatisticsImageFilterWithMask_h + +// ITK includes: +#include +#include +#include +#include +#include + + +namespace itk { + +/** \class StatisticsImageFilterWithMask + * \brief Compute min. max, variance and mean of an Image. + * + * StatisticsImageFilterWithMask computes the minimum, maximum, sum, mean, variance + * sigma of an image. The filter needs all of its input image. It + * behaves as a filter with an input and output. Thus it can be inserted + * in a pipline with other filters and the statistics will only be + * recomputed if a downstream filter changes. + * + * The filter passes its input through unmodified. The filter is + * threaded. It computes statistics in each thread then combines them in + * its AfterThreadedGenerate method. + * + * \ingroup MathematicalStatisticsImageFilters + */ +template +class ITK_EXPORT StatisticsImageFilterWithMask : + public ImageToImageFilter +{ +public: + /** Standard Self typedef */ + typedef StatisticsImageFilterWithMask Self; + typedef ImageToImageFilter Superclass; + typedef SmartPointer Pointer; + typedef SmartPointer ConstPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self); + + /** Runtime information support. */ + itkTypeMacro(StatisticsImageFilterWithMask, ImageToImageFilter); + + /** Image related typedefs. */ + typedef typename TInputImage::Pointer InputImagePointer; + + typedef typename TInputImage::RegionType RegionType ; + typedef typename TInputImage::SizeType SizeType ; + typedef typename TInputImage::IndexType IndexType ; + typedef typename TInputImage::PixelType PixelType ; + typedef typename TInputImage::PointType PointType ; + + /** Image related typedefs. */ + itkStaticConstMacro(ImageDimension, unsigned int, + TInputImage::ImageDimension ) ; + + /** Type to use for computations. */ + typedef typename NumericTraits::RealType RealType; + + /** Smart Pointer type to a DataObject. */ + typedef typename DataObject::Pointer DataObjectPointer; + + /** Type of DataObjects used for scalar outputs */ + typedef SimpleDataObjectDecorator RealObjectType; + typedef SimpleDataObjectDecorator PixelObjectType; + + /** Type for the mask of the fixed image. Only pixels that are "inside" + this mask will be considered for the computation of the metric */ + typedef SpatialObject ImageMaskType; + typedef typename ImageMaskType::Pointer ImageMaskPointer; + + /** Set/Get the image mask. */ + itkSetObjectMacro( ImageMask, ImageMaskType ); + itkGetConstObjectMacro( ImageMask, ImageMaskType ); + + /** Return the computed Minimum. */ + PixelType GetMinimum() const + { return this->GetMinimumOutput()->Get(); } + PixelObjectType* GetMinimumOutput(); + const PixelObjectType* GetMinimumOutput() const; + + /** Return the computed Maximum. */ + PixelType GetMaximum() const + { return this->GetMaximumOutput()->Get(); } + PixelObjectType* GetMaximumOutput(); + const PixelObjectType* GetMaximumOutput() const; + + /** Return the computed Mean. */ + RealType GetMean() const + { return this->GetMeanOutput()->Get(); } + RealObjectType* GetMeanOutput(); + const RealObjectType* GetMeanOutput() const; + + /** Return the computed Standard Deviation. */ + RealType GetSigma() const + { return this->GetSigmaOutput()->Get(); } + RealObjectType* GetSigmaOutput(); + const RealObjectType* GetSigmaOutput() const; + + /** Return the computed Variance. */ + RealType GetVariance() const + { return this->GetVarianceOutput()->Get(); } + RealObjectType* GetVarianceOutput(); + const RealObjectType* GetVarianceOutput() const; + + /** Return the compute Sum. */ + RealType GetSum() const + { return this->GetSumOutput()->Get(); } + RealObjectType* GetSumOutput(); + const RealObjectType* GetSumOutput() const; + + /** Make a DataObject of the correct type to be used as the specified + * output. */ + virtual DataObjectPointer MakeOutput(unsigned int idx); + +protected: + StatisticsImageFilterWithMask(); + ~StatisticsImageFilterWithMask(){}; + void PrintSelf(std::ostream& os, Indent indent) const; + + /** Pass the input through unmodified. Do this by Grafting in the AllocateOutputs method. */ + void AllocateOutputs(); + + /** Initialize some accumulators before the threads run. */ + void BeforeThreadedGenerateData (); + + /** Do final mean and variance computation from data accumulated in threads. */ + void AfterThreadedGenerateData (); + + /** Multi-thread version GenerateData. */ + void ThreadedGenerateData (const RegionType& + outputRegionForThread, + int threadId) ; + + // Override since the filter needs all the data for the algorithm + void GenerateInputRequestedRegion(); + + // Override since the filter produces all of its output + void EnlargeOutputRequestedRegion(DataObject *data); + +private: + StatisticsImageFilterWithMask(const Self&); //purposely not implemented + void operator=(const Self&); //purposely not implemented + + mutable ImageMaskPointer m_ImageMask; + + Array m_ThreadSum; + Array m_SumOfSquares; + Array m_Count; + Array m_ThreadMin; + Array m_ThreadMax; + +} ; // end of class + +} // end namespace itk + +#ifndef ITK_MANUAL_INSTANTIATION +#include "itkStatisticsImageFilterWithMask.hxx" +#endif + +#endif diff --git a/include/itkStatisticsImageFilterWithMask.hxx b/include/itkStatisticsImageFilterWithMask.hxx new file mode 100644 index 0000000..284f359 --- /dev/null +++ b/include/itkStatisticsImageFilterWithMask.hxx @@ -0,0 +1,390 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : itkStatisticsImageFilterWithMask.txx +// Author : Pavel A. Koshevoy +// Created : 2006/06/15 17:09 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : An enhanced version of the itk::StatisticsImageFilter +// adding support for a mask image. + +#ifndef _itkStatisticsImageFilterWithMask_txx +#define _itkStatisticsImageFilterWithMask_txx + +// ITK includes: +#include +#include +#include +#include + +namespace itk { + +template +StatisticsImageFilterWithMask +::StatisticsImageFilterWithMask(): m_ThreadSum(1), m_SumOfSquares(1), m_Count(1), m_ThreadMin(1), m_ThreadMax(1) +{ + // first output is a copy of the image, DataObject created by + // superclass + // + // allocate the data objects for the outputs which are + // just decorators around pixel types + for (int i=1; i < 3; ++i) + { + typename PixelObjectType::Pointer output + = static_cast(this->MakeOutput(i).GetPointer()); + this->ProcessObject::SetNthOutput(i, output.GetPointer()); + } + // allocate the data objects for the outputs which are + // just decorators around real types + for (int i=3; i < 7; ++i) + { + typename RealObjectType::Pointer output + = static_cast(this->MakeOutput(i).GetPointer()); + this->ProcessObject::SetNthOutput(i, output.GetPointer()); + } + + this->GetMinimumOutput()->Set( NumericTraits::max() ); + this->GetMaximumOutput()->Set( NumericTraits::NonpositiveMin() ); + this->GetMeanOutput()->Set( NumericTraits::max() ); + this->GetSigmaOutput()->Set( NumericTraits::max() ); + this->GetVarianceOutput()->Set( NumericTraits::max() ); + this->GetSumOutput()->Set( NumericTraits::Zero ); +} + + +template +DataObject::Pointer +StatisticsImageFilterWithMask +::MakeOutput(unsigned int output) +{ + switch (output) + { + case 0: + return static_cast(TInputImage::New().GetPointer()); + break; + case 1: + return static_cast(PixelObjectType::New().GetPointer()); + break; + case 2: + return static_cast(PixelObjectType::New().GetPointer()); + break; + case 3: + case 4: + case 5: + case 6: + return static_cast(RealObjectType::New().GetPointer()); + break; + default: + // might as well make an image + return static_cast(TInputImage::New().GetPointer()); + break; + } +} + + +template +typename StatisticsImageFilterWithMask::PixelObjectType* +StatisticsImageFilterWithMask +::GetMinimumOutput() +{ + return static_cast(this->ProcessObject::GetOutput(1)); +} + +template +const typename StatisticsImageFilterWithMask::PixelObjectType* +StatisticsImageFilterWithMask +::GetMinimumOutput() const +{ + return static_cast(this->ProcessObject::GetOutput(1)); +} + + +template +typename StatisticsImageFilterWithMask::PixelObjectType* +StatisticsImageFilterWithMask +::GetMaximumOutput() +{ + return static_cast(this->ProcessObject::GetOutput(2)); +} + +template +const typename StatisticsImageFilterWithMask::PixelObjectType* +StatisticsImageFilterWithMask +::GetMaximumOutput() const +{ + return static_cast(this->ProcessObject::GetOutput(2)); +} + + +template +typename StatisticsImageFilterWithMask::RealObjectType* +StatisticsImageFilterWithMask +::GetMeanOutput() +{ + return static_cast(this->ProcessObject::GetOutput(3)); +} + +template +const typename StatisticsImageFilterWithMask::RealObjectType* +StatisticsImageFilterWithMask +::GetMeanOutput() const +{ + return static_cast(this->ProcessObject::GetOutput(3)); +} + + +template +typename StatisticsImageFilterWithMask::RealObjectType* +StatisticsImageFilterWithMask +::GetSigmaOutput() +{ + return static_cast(this->ProcessObject::GetOutput(4)); +} + +template +const typename StatisticsImageFilterWithMask::RealObjectType* +StatisticsImageFilterWithMask +::GetSigmaOutput() const +{ + return static_cast(this->ProcessObject::GetOutput(4)); +} + + +template +typename StatisticsImageFilterWithMask::RealObjectType* +StatisticsImageFilterWithMask +::GetVarianceOutput() +{ + return static_cast(this->ProcessObject::GetOutput(5)); +} + +template +const typename StatisticsImageFilterWithMask::RealObjectType* +StatisticsImageFilterWithMask +::GetVarianceOutput() const +{ + return static_cast(this->ProcessObject::GetOutput(5)); +} + + +template +typename StatisticsImageFilterWithMask::RealObjectType* +StatisticsImageFilterWithMask +::GetSumOutput() +{ + return static_cast(this->ProcessObject::GetOutput(6)); +} + +template +const typename StatisticsImageFilterWithMask::RealObjectType* +StatisticsImageFilterWithMask +::GetSumOutput() const +{ + return static_cast(this->ProcessObject::GetOutput(6)); +} + +template +void +StatisticsImageFilterWithMask +::GenerateInputRequestedRegion() +{ + Superclass::GenerateInputRequestedRegion(); + if ( this->GetInput() ) + { + InputImagePointer image = + const_cast< typename Superclass::InputImageType * >( this->GetInput() ); + image->SetRequestedRegionToLargestPossibleRegion(); + } +} + +template +void +StatisticsImageFilterWithMask +::EnlargeOutputRequestedRegion(DataObject *data) +{ + Superclass::EnlargeOutputRequestedRegion(data); + data->SetRequestedRegionToLargestPossibleRegion(); +} + + +template +void +StatisticsImageFilterWithMask +::AllocateOutputs() +{ + // Pass the input through as the output + InputImagePointer image = + const_cast< TInputImage * >( this->GetInput() ); + this->GraftOutput( image ); + + // Nothing that needs to be allocated for the remaining outputs +} + +template +void +StatisticsImageFilterWithMask +::BeforeThreadedGenerateData() +{ + int numberOfThreads = this->GetNumberOfThreads(); + + // Resize the thread temporaries + m_Count.SetSize(numberOfThreads); + m_SumOfSquares.SetSize(numberOfThreads); + m_ThreadSum.SetSize(numberOfThreads); + m_ThreadMin.SetSize(numberOfThreads); + m_ThreadMax.SetSize(numberOfThreads); + + // Initialize the temporaries + m_Count.Fill(NumericTraits::Zero); + m_ThreadSum.Fill(NumericTraits::Zero); + m_SumOfSquares.Fill(NumericTraits::Zero); + m_ThreadMin.Fill(NumericTraits::max()); + m_ThreadMax.Fill(NumericTraits::NonpositiveMin()); +} + +template +void +StatisticsImageFilterWithMask +::AfterThreadedGenerateData() +{ + int i; + long count; + RealType sumOfSquares; + + int numberOfThreads = this->GetNumberOfThreads(); + + PixelType minimum; + PixelType maximum; + RealType mean; + RealType sigma; + RealType variance; + RealType sum; + + sum = sumOfSquares = NumericTraits::Zero; + count = 0; + + // Find the min/max over all threads and accumulate count, sum and + // sum of squares + minimum = NumericTraits::max(); + maximum = NumericTraits::NonpositiveMin(); + for( i = 0; i < numberOfThreads; i++) + { + count += m_Count[i]; + sum += m_ThreadSum[i]; + sumOfSquares += m_SumOfSquares[i]; + + if (m_ThreadMin[i] < minimum) + { + minimum = m_ThreadMin[i]; + } + if (m_ThreadMax[i] > maximum) + { + maximum = m_ThreadMax[i]; + } + } + // compute statistics + mean = sum / static_cast(count); + + // unbiased estimate + variance = (sumOfSquares - (sum*sum / static_cast(count))) + / (static_cast(count) - 1); + sigma = sqrt(variance); + + // Set the outputs + this->GetMinimumOutput()->Set( minimum ); + this->GetMaximumOutput()->Set( maximum ); + this->GetMeanOutput()->Set( mean ); + this->GetSigmaOutput()->Set( sigma ); + this->GetVarianceOutput()->Set( variance ); + this->GetSumOutput()->Set( sum ); +} + +template +void +StatisticsImageFilterWithMask +::ThreadedGenerateData(const RegionType& outputRegionForThread, + int threadId) +{ + RealType realValue; + PixelType value; + ImageRegionConstIteratorWithIndex it (this->GetInput(), outputRegionForThread); + + // support progress methods/callbacks + ProgressReporter progress(this, threadId, outputRegionForThread.GetNumberOfPixels()); + + const TInputImage * image = this->GetInput(); + + // do the work + while (!it.IsAtEnd()) + { + bool skip = false; + if (m_ImageMask) + { + // test the mask: + PointType point; + image->TransformIndexToPhysicalPoint( it.GetIndex(), point ); + skip = !m_ImageMask->IsInside( point ); + } + + if (!skip) + { + value = it.Get(); + realValue = static_cast( value ); + if (value < m_ThreadMin[threadId]) + { + m_ThreadMin[threadId] = value; + } + if (value > m_ThreadMax[threadId]) + { + m_ThreadMax[threadId] = value; + } + + m_ThreadSum[threadId] += realValue; + m_SumOfSquares[threadId] += (realValue * realValue); + m_Count[threadId]++; + } + + ++it; + progress.CompletedPixel(); + } +} + +template +void +StatisticsImageFilterWithMask +::PrintSelf(std::ostream& os, Indent indent) const +{ + Superclass::PrintSelf(os,indent); + + os << indent << "Minimum: " + << static_cast::PrintType>(this->GetMinimum()) << std::endl; + os << indent << "Maximum: " + << static_cast::PrintType>(this->GetMaximum()) << std::endl; + os << indent << "Sum: " << this->GetSum() << std::endl; + os << indent << "Mean: " << this->GetMean() << std::endl; + os << indent << "Sigma: " << this->GetSigma() << std::endl; + os << indent << "Variance: " << this->GetVariance() << std::endl; +} + + +}// end namespace itk +#endif diff --git a/itk-module.cmake b/itk-module.cmake index 009cc43..e7972d7 100644 --- a/itk-module.cmake +++ b/itk-module.cmake @@ -13,15 +13,20 @@ file(READ "${MY_CURRENT_DIR}/README.rst" DOCUMENTATION) # define the dependencies of the include module and the tests itk_module(Nornir DEPENDS - ITKCommon - ITKStatistics + ITKCommon + ITKStatistics + ITKIOImageBase + ITKTransformFactory COMPILE_DEPENDS - ITKImageSources + ITKImageSources + ITKImageIntensity + ITKThresholding + ITKSmoothing + ITKRegistrationCommon TEST_DEPENDS - ITKTestKernel - ITKMetaIO + ITKTestKernel DESCRIPTION - "${DOCUMENTATION}" + "${DOCUMENTATION}" EXCLUDE_FROM_DEFAULT ENABLE_SHARED ) diff --git a/package.json b/package.json index 70a6ea3..e60f4ab 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "test:wasi": "itk-wasm pnpm-script test:wasi", "clean": "git clean -fdx -e node_modules" }, - "license": "Apache-2.0", + "license": "GPL-2", "devDependencies": { "@itk-wasm/dam": "^1.1.1", "@thewtex/setup-micromamba": "^1.9.7", diff --git a/pyproject.toml b/pyproject.toml index 87bc24a..7dc47dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "scikit_build_core.build" [project] name = "itk-nornir" version = "0.2.0" -description = "Nornir’s takes large sets of overlapping images in 2D and produces registered (a.k.a. aligned) 2D and 3D volumes of any size and scale." +description = "Nornir's takes large sets of overlapping images in 2D and produces registered (a.k.a. aligned) 2D and 3D volumes of any size and scale." readme = "README.rst" license = {file = "LICENSE"} authors = [ @@ -20,7 +20,6 @@ classifiers = [ "Intended Audience :: Education", "Intended Audience :: Healthcare Industry", "Intended Audience :: Science/Research", - "License :: OSI Approved :: Apache Software License", "Operating System :: Android", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", @@ -35,7 +34,7 @@ classifiers = [ ] requires-python = ">=3.8" dependencies = [ - "itk~=5.4.0" + "itk == 5.4.*" ] [project.urls] diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 92c2241..95b69a8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,5 +1,26 @@ set(Nornir_SRCS - itkMinimalStandardRandomVariateGenerator.cxx - ) + itkIRCommon.cxx + itkIRText.cxx + itkIRUtils.cxx + IRTerminator.cxx + IRThreadStorage.cxx + IRStdThread.cxx + IRThreadInterface.cxx + IRMutex.cxx + IRMutexInterface.cxx + IRTransaction.cxx + IRLog.cxx + IRThreadPool.cxx + IRMosaicRefinementCommon.cxx + IRGridCommon.cxx + IRFFTCommon.cxx + IRGridTransform.cxx + itkRegularStepGradientDescentOptimizer2.cxx + itkRBFTransform.cxx + IRAABBox.cxx + IRTerminator.cxx + IRStdMutex.cxx + IRFFT.cxx +) itk_module_add_library(Nornir ${Nornir_SRCS}) diff --git a/src/IRAABBox.cxx b/src/IRAABBox.cxx new file mode 100644 index 0000000..2e9f4f9 --- /dev/null +++ b/src/IRAABBox.cxx @@ -0,0 +1,366 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_aa_bbox.hxx +// Author : Pavel A. Koshevoy +// Created : Mon Jun 7 22:14:00 MDT 2004 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : Axis aligned bounding box. + +// system includes: +#include +#include + +// local includes: +#include "IRAABBox.h" + + +///---------------------------------------------------------------- +// the_aa_bbox_t::operator << +// +the_aa_bbox_t & +the_aa_bbox_t::operator << (const p3x1_t & pt) +{ + if (min_.x() > pt.x()) min_.x() = pt.x(); + if (max_.x() < pt.x()) max_.x() = pt.x(); + if (min_.y() > pt.y()) min_.y() = pt.y(); + if (max_.y() < pt.y()) max_.y() = pt.y(); + if (min_.z() > pt.z()) min_.z() = pt.z(); + if (max_.z() < pt.z()) max_.z() = pt.z(); + + return *this; +} + +//---------------------------------------------------------------- +// the_aa_bbox_t::operator += +// +the_aa_bbox_t & +the_aa_bbox_t::operator += (const the_aa_bbox_t & bbox) +{ + if (bbox.is_empty()) return *this; + return (*this) << bbox.min_ << bbox.max_; +} + +//---------------------------------------------------------------- +// the_aa_bbox_t::operator *= +// +the_aa_bbox_t & +the_aa_bbox_t::operator *= (const float & s) +{ + if (is_empty()) return *this; + + p3x1_t o = min_ + 0.5 * (max_ - min_); + min_ = o + s * (min_ - o); + max_ = o + s * (max_ - o); + return *this; +} + +//---------------------------------------------------------------- +// the_aa_bbox_t::operator += +// +the_aa_bbox_t & +the_aa_bbox_t::operator += (const float & r) +{ + if (is_empty()) + { + min_.assign(-r, -r, -r); + max_.assign( r, r, r); + } + else + { + v3x1_t shift(r, r, r); + min_ -= shift; + max_ += shift; + } + + assert((min_.x() < max_.x()) && + (min_.y() < max_.y()) && + (min_.z() < max_.z())); + + return *this; +} + +//---------------------------------------------------------------- +// the_aa_bbox_t::corners +// +void +the_aa_bbox_t::corners(p3x1_t * corner) const +{ + corner[0] = p3x1_t(max_.x(), min_.y(), max_.z()); + corner[1] = p3x1_t(min_.x(), min_.y(), max_.z()); + corner[2] = p3x1_t(min_.x(), min_.y(), min_.z()); + corner[3] = p3x1_t(max_.x(), min_.y(), min_.z()); + corner[4] = p3x1_t(max_.x(), max_.y(), max_.z()); + corner[5] = p3x1_t(min_.x(), max_.y(), max_.z()); + corner[6] = p3x1_t(min_.x(), max_.y(), min_.z()); + corner[7] = p3x1_t(max_.x(), max_.y(), min_.z()); +} + +//---------------------------------------------------------------- +// the_aa_bbox_t::is_empty +// +bool +the_aa_bbox_t::is_empty() const +{ + return ((min_.x() == FLT_MAX) && + (min_.y() == FLT_MAX) && + (min_.z() == FLT_MAX) && + (max_.x() == -FLT_MAX) && + (max_.y() == -FLT_MAX) && + (max_.z() == -FLT_MAX)); +} + +//---------------------------------------------------------------- +// the_aa_bbox_t::is_linear +// +bool +the_aa_bbox_t::is_linear() const +{ + if (is_empty()) return false; + + float dx = max_.x() - min_.x(); + float dy = max_.y() - min_.y(); + float dz = max_.z() - min_.z(); + return (((dx != 0.0) && (dy == 0.0) && (dz == 0.0)) || + ((dx == 0.0) && (dy != 0.0) && (dz == 0.0)) || + ((dx == 0.0) && (dy == 0.0) && (dz != 0.0))); +} + +//---------------------------------------------------------------- +// the_aa_bbox_t::is_planar +// +bool +the_aa_bbox_t::is_planar() const +{ + if (is_empty()) return false; + + float dx = max_.x() - min_.x(); + float dy = max_.y() - min_.y(); + float dz = max_.z() - min_.z(); + return (((dx == 0.0) && (dy != 0.0) && (dz != 0.0)) || + ((dx != 0.0) && (dy == 0.0) && (dz != 0.0)) || + ((dx != 0.0) && (dy != 0.0) && (dz == 0.0))); +} + +//---------------------------------------------------------------- +// the_aa_bbox_t::is_spacial +// +bool +the_aa_bbox_t::is_spacial() const +{ + if (is_empty()) return false; + + float dx = max_.x() - min_.x(); + float dy = max_.y() - min_.y(); + float dz = max_.z() - min_.z(); + return ((dx != 0.0) && (dy != 0.0) && (dz != 0.0)); +} + +//---------------------------------------------------------------- +// the_aa_bbox_t::radius +// +float +the_aa_bbox_t::radius(const p3x1_t & center) const +{ + if (is_empty()) return 0.0; + + p3x1_t corner[] = + { + p3x1_t(min_.x(), min_.y(), min_.z()), + p3x1_t(min_.x(), min_.y(), max_.z()), + p3x1_t(min_.x(), max_.y(), min_.z()), + p3x1_t(min_.x(), max_.y(), max_.z()), + p3x1_t(max_.x(), min_.y(), min_.z()), + p3x1_t(max_.x(), min_.y(), max_.z()), + p3x1_t(max_.x(), max_.y(), min_.z()), + p3x1_t(max_.x(), max_.y(), max_.z()) + }; + + float max_dist = -FLT_MAX; + for (unsigned int i = 0; i < 8; i++) + { + float dist = ~(center - corner[i]); + if (dist > max_dist) max_dist = dist; + } + + return max_dist; +} + +//---------------------------------------------------------------- +// the_aa_bbox_t::radius +// +float +the_aa_bbox_t::radius(const p3x1_t & center, + const unsigned int & axis_w_id) const +{ + assert(axis_w_id < 3); + unsigned int axis_u_id = (axis_w_id + 1) % 3; + unsigned int axis_v_id = (axis_u_id + 1) % 3; + + p2x1_t uv_center(center[axis_u_id], + center[axis_v_id]); + + p2x1_t uv_corner[] = { + p2x1_t(min_[axis_u_id], min_[axis_v_id]), + p2x1_t(min_[axis_u_id], max_[axis_v_id]), + p2x1_t(max_[axis_u_id], min_[axis_v_id]), + p2x1_t(max_[axis_u_id], max_[axis_v_id]) + }; + + float max_dist = -FLT_MAX; + for (unsigned int i = 0; i < 4; i++) + { + v2x1_t uv_vec = uv_center - uv_corner[i]; + float dist = ~uv_vec; + if (dist > max_dist) max_dist = dist; + } + + return max_dist; +} + +//---------------------------------------------------------------- +// the_aa_bbox_t::contains +// +void +the_aa_bbox_t::contains(const p3x1_t & pt, + bool & contained_in_x, + bool & contained_in_y, + bool & contained_in_z) const +{ + // check whether the point is within the bounding box limits: + contained_in_x = ((pt.x() <= max_.x()) && + (pt.x() >= min_.x())); + contained_in_y = ((pt.y() <= max_.y()) && + (pt.y() >= min_.y())); + contained_in_z = ((pt.z() <= max_.z()) && + (pt.z() >= min_.z())); +} + +//---------------------------------------------------------------- +// the_aa_bbox_t::intersects +// +bool +the_aa_bbox_t::intersects(const the_aa_bbox_t & b) const +{ + bool disjoint = + (min_[0] > b.max_[0] || b.min_[0] > max_[0]) || + (min_[1] > b.max_[1] || b.min_[1] > max_[1]) || + (min_[2] > b.max_[2] || b.min_[2] > max_[2]); + + return !disjoint; +} + +//---------------------------------------------------------------- +// the_aa_bbox_t::clamp +// +void +the_aa_bbox_t::clamp(const the_aa_bbox_t & confines) +{ + if (confines.is_empty()) return; + + min_[0] = std::min(confines.max_[0], std::max(confines.min_[0], min_[0])); + min_[1] = std::min(confines.max_[1], std::max(confines.min_[1], min_[1])); + min_[2] = std::min(confines.max_[2], std::max(confines.min_[2], min_[2])); + + max_[0] = std::min(confines.max_[0], std::max(confines.min_[0], max_[0])); + max_[1] = std::min(confines.max_[1], std::max(confines.min_[1], max_[1])); + max_[2] = std::min(confines.max_[2], std::max(confines.min_[2], max_[2])); +} + +//---------------------------------------------------------------- +// the_aa_bbox_t::intersects_ray +// +bool +the_aa_bbox_t::intersects_ray(const p3x1_t & o, + const v3x1_t & d, + float & t_min, + float & t_max) const +{ + float t[3][2]; + + for (unsigned int i = 0; i < 3; i++) + { + float inv_d = 1.0f / d[i]; + unsigned int e = inv_d < 0.0f; + + t[i][e] = (min_[i] - o[i]) * inv_d; + t[i][(e + 1) % 2] = (max_[i] - o[i]) * inv_d; + } + + t_min = std::max(t[0][0], std::max(t[1][0], t[2][0])); + t_max = std::min(t[0][1], std::min(t[1][1], t[2][1])); + + return t_min < t_max; +} + +//---------------------------------------------------------------- +// the_aa_bbox_t::largest_dimension +// +unsigned int +the_aa_bbox_t::largest_dimension() const +{ + float d[] = + { + max_[0] - min_[0], + max_[1] - min_[1], + max_[2] - min_[2] + }; + + unsigned int axis = 0; + if (d[1] > d[0]) axis = 1; + if (d[2] > d[axis]) axis = 2; + + return axis; +} + +//---------------------------------------------------------------- +// the_aa_bbox_t::smallest_dimension +// +unsigned int +the_aa_bbox_t::smallest_dimension() const +{ + float d[] = + { + max_[0] - min_[0], + max_[1] - min_[1], + max_[2] - min_[2] + }; + + unsigned int axis = 0; + if (d[1] < d[0]) axis = 1; + if (d[2] < d[axis]) axis = 2; + + return axis; +} + +//---------------------------------------------------------------- +// the_aa_bbox_t::dump +// +void +the_aa_bbox_t::dump(ostream & strm) const +{ + strm << "the_aa_bbox_t(" << (void *)this << ")" << endl + << "{" << endl + << " min_ = " << min_ << endl + << " max_ = " << max_ << endl + << "}" << endl; +} diff --git a/src/IRFFT.cxx b/src/IRFFT.cxx new file mode 100644 index 0000000..244c663 --- /dev/null +++ b/src/IRFFT.cxx @@ -0,0 +1,601 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : fft.cxx +// Author : Pavel A. Koshevoy +// Created : 2005/06/03 10:16 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : Wrapper class and helper functions for working with +// FFTW3 and ITK images. + +// system includes: +#ifndef _USE_MATH_DEFINES +#define _USE_MATH_DEFINES +#endif +#include +#include + +// ITK includes: +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// local includes: +#include "IRFFT.h" +#include "IRMutexInterface.h" +#include "itkIRUtils.h" + + +namespace itk_fft +{ + // static the_mutex_interface_t * mutex = NULL; + // //---------------------------------------------------------------- + // // fftw_mutex + // // + // static the_mutex_interface_t * + // fftw_mutex() + // { + // if(mutex == NULL) + // mutex = the_mutex_interface_t::create(); + // return mutex; + // } + + //---------------------------------------------------------------- + // NUM_FFTW_THREADS + // + static std::size_t NUM_FFTW_THREADS = 1; + + //---------------------------------------------------------------- + // set_num_fftw_threads + // + // set's number of threads used by fftw, returns previous value: + // std::size_t set_num_fftw_threads(std::size_t num_threads) + // { + // the_lock_t lock(fftw_mutex()); + // std::size_t prev = NUM_FFTW_THREADS; + // if (num_threads > 0) + // { + + // NUM_FFTW_THREADS = num_threads; + // } + + // return prev; + // } + + + // //---------------------------------------------------------------- + // // fft_cache_t + // // + // class fft_cache_t + // { + // public: + // fft_cache_t(): + // fwd_(NULL), + // inv_(NULL), + // w_(0), + // h_(0), + // h_complex_(0), + // h_padded_(0), + // buffer_(NULL) + // {} + + // ~fft_cache_t() + // { + // clear(); + // } + + // void clear() + // { + // if (buffer_) + // { + // // fftw is not thread safe: + // // the_lock_t lock(fftw_mutex()); + + // fftwf_destroy_plan(fwd_); + // fwd_ = NULL; + + // fftwf_destroy_plan(inv_); + // inv_ = NULL; + + // fftwf_free(buffer_); + // buffer_ = NULL; + // } + + // w_ = 0; + // h_ = 0; + // h_complex_ = 0; + // h_padded_ = 0; + // } + + // void update(const unsigned int & w, const unsigned int & h) + // { + // if (w_ == w && h_ == h) + // { + // return; + // } + + // // fftw is not thread safe: + // // the_lock_t lock(fftw_mutex()); + + // // if (buffer_) + // // { + // // fftwf_destroy_plan(fwd_); + // // fftwf_destroy_plan(inv_); + // // fftwf_free(buffer_); + // // } + + // w_ = w; + // h_ = h; + + // h_complex_ = h_ / 2 + 1; + // h_padded_ = h_complex_ * 2; + + // buffer_ = (float *)(fftwf_malloc(w_ * h_padded_ * sizeof(float))); + // fftwf_plan_with_nthreads(NUM_FFTW_THREADS); + // fwd_ = fftwf_plan_dft_r2c_2d(w_, + // h_, + // buffer_, + // (fftwf_complex *)buffer_, + // FFTW_DESTROY_INPUT | FFTW_ESTIMATE); + + // fft_data_t dummy_in(4, 4); + // fft_data_t dummy_out(4, 4); + // fftwf_plan_with_nthreads(NUM_FFTW_THREADS); + // inv_ = fftwf_plan_dft_2d(w_, + // h_, + // (fftwf_complex *)(dummy_in.data()), + // (fftwf_complex *)(dummy_out.data()), + // FFTW_BACKWARD, + // FFTW_ESTIMATE); + // } + + // fftwf_plan fwd_; // analysis, forward fft + // fftwf_plan inv_; // synthesis, inverse fft + // unsigned int w_; + // unsigned int h_; + // unsigned int h_complex_; + // unsigned int h_padded_; + // float * buffer_; + // }; + + //---------------------------------------------------------------- + // tss + // + // static boost::thread_specific_ptr tss; + using ForwardFFTFilterType = itk::ForwardFFTImageFilter; + static thread_local ForwardFFTFilterType::Pointer forwardFFTFilter = ForwardFFTFilterType::New(); + using InverseFFTFilterType = itk::ComplexToComplexFFTImageFilter; + static thread_local InverseFFTFilterType::Pointer inverseFFTFilter = InverseFFTFilterType::New(); + + //---------------------------------------------------------------- + // fft_data_t::fft_data_t + // + fft_data_t::fft_data_t(const itk_image_t::Pointer & real): + image_(), + nx_(0), + ny_(0) + { + setup(real); + } + + //---------------------------------------------------------------- + // fft_data_t::fft_data_t + // + fft_data_t::fft_data_t(const unsigned int w, const unsigned int h): + image_(), + nx_(0), + ny_(0) + { + resize(w, h); + } + + //---------------------------------------------------------------- + // fft_data_t::fft_data_t + // + fft_data_t::fft_data_t(const itk_image_t::Pointer & real, + const itk_image_t::Pointer & imag): + image_(), + nx_(0), + ny_(0) + { + setup(real, imag); + } + + //---------------------------------------------------------------- + // fft_data_t::fft_data_t + // + fft_data_t::fft_data_t(const fft_data_t & data): + image_(), + nx_(0), + ny_(0) + { + *this = data; + } + + //---------------------------------------------------------------- + // fft_data_t::operator = + // + fft_data_t & + fft_data_t::operator = (const fft_data_t & data) + { + assert(this != &data); + + if (data.image_ != nullptr) + { + using DuplicatorType = itk::ImageDuplicator; + DuplicatorType::Pointer duplicator = DuplicatorType::New(); + duplicator->SetInputImage(data.image_); + duplicator->Update(); + image_ = duplicator->GetOutput(); + resize(data.nx_, data.ny_); + } + else + { + cleanup(); + } + + return *this; + } + + //---------------------------------------------------------------- + // fft_data_t::cleanup + // + void + fft_data_t::cleanup() + { + // if (data_ != NULL) fftwf_free((fft_complex_t *)(data_)); + // data_ = NULL; + nx_ = 0; + ny_ = 0; + } + + //---------------------------------------------------------------- + // fft_data_t::resize + // + void + fft_data_t::resize(const unsigned int w, const unsigned int h) + { + const unsigned int new_sz = w * h; + const unsigned int old_sz = nx_ * ny_; + if (old_sz == new_sz) return; + + // if (data_ != NULL) fftwf_free((fft_complex_t *)(data_)); + // data_ = (fft_complex_t *)(fftwf_malloc(new_sz * sizeof(fft_complex_t))); + // assert(data_ != NULL); + image_->SetRegions({ w, h }); + image_->Allocate(); + + nx_ = w; + ny_ = h; + } + + //---------------------------------------------------------------- + // fft_data_t::fill + // + void + fft_data_t::fill(const float real, const float imag) + { + const fft_complex_t value(real, imag); + this->image_->FillBuffer(value); + } + + //---------------------------------------------------------------- + // fft_data_t::setup + // + void + fft_data_t::setup(const itk_image_t::Pointer & real, + const itk_image_t::Pointer & imag) + { + using ComposeFilterType = itk::ComposeImageFilter; + ComposeFilterType::Pointer composeFilter = ComposeFilterType::New(); + composeFilter->SetInput1(real); + composeFilter->SetInput2(imag); + composeFilter->Update(); + image_ = composeFilter->GetOutput(); + } + + //---------------------------------------------------------------- + // fft_data_t::component + // + itk_image_t::Pointer + fft_data_t::component(const bool imag) const + { + if (imag) + { + using ComplexToImaginaryFilterType = itk::ComplexToImaginaryImageFilter; + ComplexToImaginaryFilterType::Pointer complexToImaginaryFilter = ComplexToImaginaryFilterType::New(); + complexToImaginaryFilter->SetInput(image_); + complexToImaginaryFilter->Update(); + return complexToImaginaryFilter->GetOutput(); + } + using ComplexToRealFilterType = itk::ComplexToRealImageFilter; + ComplexToRealFilterType::Pointer complexToRealFilter = ComplexToRealFilterType::New(); + complexToRealFilter->SetInput(image_); + complexToRealFilter->Update(); + return complexToRealFilter->GetOutput(); + } + + static std::vector syLookup; + static std::vector sySquaredLookup; + + //---------------------------------------------------------------- + // fft_data_t::apply_lp_filter + // + void + fft_data_t::apply_lp_filter(const double r, const double s) + { + if (r > ::sqrt(2.0)) return; + + const unsigned int hx = nx_ / 2; + const unsigned int hy = ny_ / 2; + + const double r0 = (r - s); + const double r1 = (r + s); + const double dr = r1 - r0; + + const double r0sqr = r0 * r0; + const double r1sqr = r1 * r1; + + //The use of this static cache needs to be wrapped in a lock, copied, or some other protection if multithreaded calls with different image sizes are used. + if((unsigned int)syLookup.size() != ny_) + { + syLookup.clear(); + sySquaredLookup.clear(); + + syLookup.reserve(ny_); + sySquaredLookup.reserve(ny_); + for (unsigned int y = 0; y < ny_; y++) + { + double sy = 2.0 * (double((y + hy) % ny_) - double(hy)) / double(ny_); + double y2 = sy * sy; + syLookup.push_back(sy); + sySquaredLookup.push_back(sy); + } + } + + fft_complex_t * data_ = this->image_->GetBufferPointer(); + unsigned int i = 0; + for (unsigned int x = 0; x < nx_; x++) + { + double sx = 2.0 * (double((x + hx) % nx_) - double(hx)) / double(nx_); + double x2 = sx * sx; + + for (unsigned int y = 0; y < ny_; y++) + { + double sy = syLookup[y]; + double y2 = sySquaredLookup[y]; + + double d2 = x2 + y2; + double v; + + if(d2 < r0sqr) + v = 1.0; + else if(d2 > r1sqr) + v = 0.0; + else + { + double d = ::sqrt(x2 + y2); + v = (1.0 + cos(M_PI * (d - r0) / dr)) / 2.0; + } + + data_[i++] *= v; + } + } + } + + + //---------------------------------------------------------------- + // fft + // + bool + fft(itk_image_t::ConstPointer & in, fft_data_t & out) + { + forwardFFTFilter->SetInput(in); + forwardFFTFilter->GraftOutput(out.data()); + forwardFFTFilter->UpdateLargestPossibleRegion(); + + return true; + } + + + //---------------------------------------------------------------- + // ifft + // + bool + ifft(const fft_data_t & in, fft_data_t & out) + { + out.resize(in.nx(), in.ny()); + inverseFFTFilter->SetInput(in.data()); + inverseFFTFilter->GraftOutput(out.data()); + inverseFFTFilter->UpdateLargestPossibleRegion(); + + return true; + } + + // //---------------------------------------------------------------- + // // elem_by_elem + // // + // void + // elem_by_elem(fn_fft_c_t f, + // const fft_data_t & in, + // fft_data_t & out) + // { + // const unsigned nx = in.nx(); + // const unsigned ny = in.ny(); + + // out.resize(nx, ny); + // fft_complex_t * dout = out.data(); + // const fft_complex_t * din = in.data(); + + // const unsigned int size = nx * ny; + // for (unsigned int i = 0; i < size; i++) + // { + // dout[i] = f(din[i]); + // } + // } + + + // //---------------------------------------------------------------- + // // elem_by_elem + // // + // void + // elem_by_elem(fft_complex_t(*f)(const double & a, + // const fft_complex_t & b), + // const double & a, + // const fft_data_t & b, + // fft_data_t & c) + // { + // assert(&b != &c); + + // const unsigned nx = b.nx(); + // const unsigned ny = b.ny(); + // c.resize(nx, ny); + + // fft_complex_t * dc = c.data(); + // const fft_complex_t * db = b.data(); + + // const unsigned int size = nx * ny; + // for (unsigned int i = 0; i < size; i++) + // { + // dc[i] = f(a, db[i]); + // } + // } + + + // //---------------------------------------------------------------- + // // elem_by_elem + // // + // void + // elem_by_elem(fft_complex_t(*f)(const fft_complex_t & a, + // const double & b), + // const fft_data_t & a, + // const double & b, + // fft_data_t & c) + // { + // assert(&a != &c); + + // const unsigned nx = a.nx(); + // const unsigned ny = a.ny(); + // c.resize(nx, ny); + + // fft_complex_t * dc = c.data(); + // const fft_complex_t * da = a.data(); + + // const unsigned int size = nx * ny; + // for (unsigned int i = 0; i < size; i++) + // { + // dc[i] = f(da[i], b); + // } + // } + + + // //---------------------------------------------------------------- + // // elem_by_elem + // // + // void + // elem_by_elem(fft_complex_t(*f)(const fft_complex_t & a, + // const fft_complex_t & b), + // const fft_complex_t & a, + // const fft_data_t & b, + // fft_data_t & c) + // { + // assert(&b != &c); + + // const unsigned nx = b.nx(); + // const unsigned ny = b.ny(); + // c.resize(nx, ny); + + // fft_complex_t * dc = c.data(); + // const fft_complex_t * db = b.data(); + + // const unsigned int size = nx * ny; + // for (unsigned int i = 0; i < size; i++) + // { + // dc[i] = f(a, db[i]); + // } + // } + + + // //---------------------------------------------------------------- + // // elem_by_elem + // // + // void + // elem_by_elem(fft_complex_t(*f)(const fft_complex_t & a, + // const fft_complex_t & b), + // const fft_data_t & a, + // const fft_complex_t & b, + // fft_data_t & c) + // { + // assert(&a != &c); + + // const unsigned nx = a.nx(); + // const unsigned ny = a.ny(); + // c.resize(nx, ny); + + // fft_complex_t * dc = c.data(); + // const fft_complex_t * da = a.data(); + + // const unsigned int size = nx * ny; + // for (unsigned int i = 0; i < size; i++) + // { + // dc[i] = f(da[i], b); + // } + // } + + + // //---------------------------------------------------------------- + // // elem_by_elem + // // + // void + // elem_by_elem(fn_fft_cc_t f, + // const fft_data_t & a, + // const fft_data_t & b, + // fft_data_t & c) + // { + // assert(&a != &c); + // assert(&b != &c); + + // const unsigned nx = a.nx(); + // const unsigned ny = a.ny(); + // assert(nx == b.nx() && ny == b.ny()); + + // c.resize(nx, ny); + // fft_complex_t * dc = c.data(); + + // const fft_complex_t * da = a.data(); + // const fft_complex_t * db = b.data(); + + // const unsigned int size = nx * ny; + // for (unsigned int i = 0; i < size; i++) + // { + // dc[i] = f(da[i], db[i]); + // } + // } + +} // namespace itk_fft diff --git a/src/IRFFTCommon.cxx b/src/IRFFTCommon.cxx new file mode 100644 index 0000000..85cb62b --- /dev/null +++ b/src/IRFFTCommon.cxx @@ -0,0 +1,699 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : fft_common.cxx +// Author : Pavel A. Koshevoy +// Created : 2005/11/10 14:05 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : Helper functions for image alignment (registration) +// using phase correlation to find the translation vector. + +// local includes: +#include "IRFFTCommon.h" +#include "itkIRUtils.h" + +// system includes: +#include + +//---------------------------------------------------------------- +// DEBUG_COUNTER1 +// +#ifdef DEBUG_PDF +unsigned int DEBUG_COUNTER1 = 0; +unsigned int DEBUG_COUNTER2 = 0; +#endif + + +//---------------------------------------------------------------- +// find_maxima_cm +// +// The percentage below refers to the number of pixels that fall +// below the maxima. Thus, the number of pixels above the maxima +// is 1 - percentage. This way it is possible to specify a +// thresholding value without knowing anything about the image. +// +// The given image is thresholded, the resulting clusters/blobs +// are identified/classified, the center of mass of each cluster +// is treated as a maxima in the image. +// +unsigned int +find_maxima_cm(std::list & max_list, + const itk_image_t::Pointer & image, + const double percentage, + const the_text_t & prefix, + const the_text_t & suffix) +{ + typedef itk::ImageRegionConstIterator iter_t; + typedef itk::ImageRegionConstIteratorWithIndex itex_t; + typedef itk_image_t::IndexType index_t; + typedef std::list cluster_t; + + // local copy of the image that will be destroyed in the process: + itk_image_t::Pointer peaks = cast(image); + + // save(peaks, "Peaks.png"); + + // FIXME: +#ifdef DEBUG_PDF + if (prefix.size() != 0) + { + save(cast(remap_min_max(peaks, 0.0, 255.0)), + prefix + the_text_t("PDF") + suffix); + } +#endif + + // first find minimax/maxima of the image: + double v_min = std::numeric_limits::max(); + double v_max = -v_min; + + iter_t iter(peaks, peaks->GetLargestPossibleRegion()); + for (iter.GoToBegin(); !iter.IsAtEnd(); ++iter) + { + const double v = iter.Get(); + v_min = std::min(v_min, v); + v_max = std::max(v_max, v); + } + + // calculate the min/max range: + const double v_rng = v_max - v_min; + + // NaN is the only number which is not equal to itself + if (v_rng == 0.0 || v_rng != v_rng || v_rng == std::numeric_limits::infinity()) + { + // there are no peaks in this image: + return 0; + } + + // build a histogram: + const unsigned int bins = 4096; + unsigned int pdf[bins] = { 0 }; + + for (iter.GoToBegin(); !iter.IsAtEnd(); ++iter) + { + const double v = iter.Get(); + const unsigned int bin = (unsigned int)(double((v - v_min) / v_rng) * double(bins - 1)); + pdf[bin]++; + } + + // build the cumulative histogram: + unsigned int cdf[bins]; + cdf[0] = pdf[0]; + for (unsigned int i = 1; i < bins; i++) + { + cdf[i] = cdf[i - 1] + pdf[i]; + } + + // shortcuts: + itk_image_t::SizeType size = peaks->GetLargestPossibleRegion().GetSize(); + const unsigned int & w = size[0]; + const unsigned int & h = size[1]; + const double wh = double(w * h); + + // find the CDF bin that contains a given percentage of the total image: + double clip_min = 0.0; + for (unsigned int i = 1; i < bins; i++) + { + clip_min = v_min + (double(i) / double(bins - 1)) * v_rng; + if (double(cdf[i]) >= percentage * wh) + break; + } + + // threshold the peaks: + double background = clip_min - v_rng * 1e-3; + peaks = threshold(peaks, clip_min, v_max, background, v_max); + peaks = remap_min_max(peaks, 0.0, 1.0); + background = 0.0; + + // FIXME: +#ifdef DEBUG_CLUSTERS + if (prefix.size() != 0) + { + save(cast(remap_min_max(peaks, 0.0, 255.0)), + prefix + the_text_t("clusters") + suffix); + } +#endif + + // classify the clusters: + static const int stencil[][2] = { // 4 connected: + { 0, -1 }, + { -1, 0 }, + { 0, 1 }, + { 1, 0 }, + + // 8 connected: + { -1, -1 }, + { 1, 1 }, + { -1, 1 }, + { 1, -1 } + }; + + the_dynamic_array_t clusters; + the_dynamic_array_t bboxes; + std::vector cluster_map(w * h); + cluster_map.assign(w * h, ~0); + + itex_t itex(peaks, peaks->GetLargestPossibleRegion()); + for (itex.GoToBegin(); !itex.IsAtEnd(); ++itex) + { + const double v = itex.Get(); + + // skip over the background: + if (v <= background) + continue; + + const index_t index = itex.GetIndex(); + const unsigned int x = index[0]; + const unsigned int y = index[1]; + + // iterate over the neighborhood, collect the blob ids + // of the neighbors: + // James A: Changed to vector instead of a list... much faster + std::vector neighbors; + neighbors.reserve(8); + + for (unsigned int k = 0; k < 8; k++) + { + int u = x + stencil[k][0]; + int v = y + stencil[k][1]; + if ((unsigned int)(u) >= w || (unsigned int)(v) >= h) + continue; + + unsigned int cluster_id = cluster_map[u * h + v]; + if (cluster_id != (unsigned int)(~0)) + { + push_back_unique(neighbors, cluster_id); + } + } + + if (neighbors.empty()) + { + // make a new cluster: + clusters.append(cluster_t()); + bboxes.append(cluster_bbox_t()); + + unsigned int id = clusters.end_index(true); + cluster_map[x * h + y] = id; + clusters[id].push_back(index); + bboxes[id].update(x, y); + } + else + { + // add this pixel to the cluster: + unsigned int id = *(neighbors.begin()); + cluster_map[x * h + y] = id; + clusters[id].push_back(index); + bboxes[id].update(x, y); + + if (neighbors.size() > 1) + { + // merge the clusters into one (the first one): + // James A: Changed to vector instead of a list... much faster + std::vector::iterator bi = ++(neighbors.begin()); + for (; bi != neighbors.end(); ++bi) + { + unsigned int old_id = *bi; + bboxes[old_id].reset(); + + while (!clusters[old_id].empty()) + { + index_t ij = remove_head(clusters[old_id]); + + cluster_map[ij[0] * h + ij[1]] = id; + clusters[id].push_back(ij); + bboxes[id].update(ij[0], ij[1]); + } + } + } + } + } + + // merge the clusters that are broken up across the periodic boundary: + for (unsigned int i = 0; i < clusters.size(); i++) + { + cluster_t & cluster = clusters[i]; + if (cluster.empty()) + continue; + + for (std::list::iterator j = cluster.begin(); j != cluster.end(); ++j) + { + const index_t index = *j; + unsigned int x = (index[0] + w) % w; + unsigned int y = (index[1] + h) % h; + + for (unsigned int k = 0; k < 8; k++) + { + int dx = stencil[k][0]; + int dy = stencil[k][1]; + + // adjust for periodicity: + int u = (x + dx + w) % w; + int v = (y + dy + h) % h; + + unsigned int cluster_id = cluster_map[u * h + v]; + if (cluster_id == i || cluster_id == (unsigned int)(~0)) + continue; + + // figure out which boundaries this cluster was broken accross: + cluster_bbox_t & ba = bboxes[i]; + cluster_bbox_t & bb = bboxes[cluster_id]; + + bool merge_x = ((bb.max_[0] - ba.min_[0] > int(w / 2)) || (ba.max_[0] - bb.min_[0] > int(w / 2))); + + bool merge_y = ((bb.max_[1] - ba.min_[1] > int(h / 2)) || (ba.max_[1] - bb.min_[1] > int(h / 2))); + + int shift_x = (!merge_x) ? 0 : (ba.min_[0] <= 0) ? -w : w; + int shift_y = (!merge_y) ? 0 : (ba.min_[1] <= 0) ? -h : h; + + // merge the clusters into one (the first one): + cluster_t & neighbor = clusters[cluster_id]; + + bb.reset(); + while (!neighbor.empty()) + { + index_t ij = remove_head(neighbor); + cluster_map[ij[0] * h + ij[1]] = i; + + ij[0] += shift_x; + ij[1] += shift_y; + clusters[i].push_back(ij); + ba.update(ij[0], ij[1]); + } + } + } + } + + // FIXME: +#ifdef DEBUG_MARKERS + itk_image_t::Pointer markers = make_image(size, background); +#endif + + // calculate the center of mass for each cluster: + unsigned int num_peaks = 0; + for (unsigned int i = 0; i < clusters.size(); i++) + { + const cluster_t & cluster = clusters[i]; + if (cluster.empty()) + continue; + + double mx = 0.0; + double my = 0.0; + double mt = 0.0; + + for (std::list::const_iterator j = cluster.begin(); j != cluster.end(); ++j) + { + index_t ij = *j; + double x = double(ij[0]); + double y = double(ij[1]); + + // adjust index for periodicity: + if (x < 0) + ij[0] += w; + if (x >= w) + ij[0] -= w; + if (y < 0) + ij[1] += h; + if (y >= h) + ij[1] -= h; + + double m = peaks->GetPixel(ij); + mx += m * x; + my += m * y; + mt += m; + } + + double cm_x = mx / mt; + double cm_y = my / mt; + double m = mt / double(cluster.size()); + + // FIXME: +#ifdef DEBUG_MARKERS + mark(markers, pnt2d(cm_x, cm_y), m, 2, '+'); +#endif + + max_list.push_back(local_max_t(m, cm_x, cm_y, cluster.size())); + num_peaks++; + } + + // FIXME: +#ifdef DEBUG_MARKERS + save(cast(remap_min_max(markers, 0.0, 255.0)), + prefix + the_text_t("markings") + suffix, + false); +#endif + + // sort the max points so that the best candidate is first: + max_list.sort(std::greater()); + + return num_peaks; +} + + +//---------------------------------------------------------------- +// threshold_maxima +// +// Discard maxima whose mass is below a given threshold ratio +// of the total mass of all maxima: +// +void +threshold_maxima(std::list & max_list, const double threshold) +{ + double total_mass = 0.0; + for (std::list::iterator i = max_list.begin(); i != max_list.end(); ++i) + { + double mass = double((*i).area_) * ((*i).value_); + total_mass += mass; + } + + std::list new_list; + double threshold_mass = threshold * total_mass; + for (std::list::iterator i = max_list.begin(); i != max_list.end(); ++i) + { + double mass = double((*i).area_) * (*i).value_; + if (mass < threshold_mass) + continue; + + new_list.push_back(*i); + } + + max_list = new_list; +} + + +//---------------------------------------------------------------- +// reject_negligible_maxima +// +// Discard maxima that are worse than the best maxima by a factor +// greater than the given threshold ratio: +// +unsigned int +reject_negligible_maxima(std::list & max_list, const double threshold) +{ + double best_mass = 0.0; + for (std::list::iterator i = max_list.begin(); i != max_list.end(); ++i) + { + double mass = (*i).value_; + best_mass = std::max(best_mass, mass); + } + + unsigned int new_size = 0; + std::list new_list; + while (!max_list.empty()) + { + local_max_t lm = remove_head(max_list); + double mass = lm.value_; + if (best_mass / mass > threshold) + continue; + + new_list.push_back(lm); + new_size++; + } + + max_list.splice(max_list.end(), new_list); + return new_size; +} + + +//---------------------------------------------------------------- +// reject_negligible_overlap +// +void +reject_negligible_overlap(std::list & ol, const double threshold) +{ + double best_overlap = 0.0; + for (std::list::iterator i = ol.begin(); i != ol.end(); ++i) + { + double overlap = (*i).overlap_; + best_overlap = std::max(best_overlap, overlap); + } + + std::list new_list; + for (std::list::iterator i = ol.begin(); i != ol.end(); ++i) + { + double overlap = (*i).overlap_; + if (overlap == 0.0) + continue; + if (best_overlap / overlap > threshold) + continue; + + new_list.push_back(*i); + } + + ol = new_list; +} + + +//---------------------------------------------------------------- +// find_correlation +// +template <> +unsigned int +find_correlation(std::list & max_list, + const itk_image_t * fi, + const itk_image_t * mi, + + // low pass filter parameters + // (resampled data requires less smoothing): + double lp_filter_r, + double lp_filter_s, + const double overlap_min, + const double overlap_max) +{ + itk_image_t::SizeType max_sz = calc_padding(fi, mi); + + typedef itk_image_t::SizeType sz_t; + typedef itk_image_t::RegionType rn_t; + + rn_t fi_region = fi->GetLargestPossibleRegion(); + sz_t fi_size = fi_region.GetSize(); + + rn_t mi_region = mi->GetLargestPossibleRegion(); + sz_t mi_size = mi_region.GetSize(); + + itk_image_t::ConstPointer z0 = + (fi_size[0] == max_sz[0] && fi_size[1] == max_sz[1]) ? fi : pad(fi, max_sz); + itk_image_t::ConstPointer z1 = + (mi_size[0] == max_sz[0] && mi_size[1] == max_sz[1]) ? mi : pad(mi, max_sz); + + fft_data_t f0; + fft(z0, f0); + f0.apply_lp_filter(lp_filter_r, lp_filter_s); + + fft_data_t f1; + fft(z1, f1); + f1.apply_lp_filter(lp_filter_r, lp_filter_s); + + const unsigned int & nx = f0.nx(); + const unsigned int & ny = f0.ny(); + fft_data_t P(nx, ny); + + // Blank out areas outside the min or max overlap + unsigned int xLeftBorderStart = std::ceil(nx * overlap_min); + unsigned int xRightBorderStart = nx - xLeftBorderStart; + unsigned int yLowBorderStart = std::ceil(ny * overlap_min); + unsigned int yHighBorderStart = ny - yLowBorderStart; + + + unsigned int xLeftBorderCutoff = std::ceil(nx * overlap_max); + unsigned int xRightBorderCutoff = nx - xLeftBorderCutoff; + unsigned int yLowBorderCutoff = std::ceil(ny * overlap_max); + unsigned int yHighBorderCutoff = ny - yLowBorderCutoff; + + for (unsigned int x = 0; x < nx; x++) + { + for (unsigned int y = 0; y < ny; y++) + { + // TODO: Skip pixels we know can't possibly be overlapping + /* + if(!(y <= yLowBorderCutoff || + y >= yHighBorderCutoff || + x <= xLeftBorderCutoff || + x >= xRightBorderCutoff)) + { + P(x, y) = 0; + continue; + } + */ +#if 1 + // Girod-Kuo, normalized cross power spectrum, + // corresponds to phase correlation in spatial domain: + fft_complex_t p10 = f1(x, y) * std::conj(f0(x, y)); + P(x, y) = _div(p10, _add(std::sqrt(p10 * std::conj(p10)), 1e-8f)); +#else + // cross power spectrum, + // corresponds to cross correlation in spatial domain: + P(x, y) = f1(x, y) * std::conj(f0(x, y)); +#endif + } + } + + // resampled data produces less noisy PDF and requires less smoothing: + P.apply_lp_filter(lp_filter_r * 0.8, lp_filter_s); + + // calculate the displacement probability density function: + fft_data_t ifft_P; + +#ifndef NDEBUG // get around an annoying compiler warning: + bool ok = +#endif + ifft(P, ifft_P); + assert(ok); + + itk_image_t::Pointer PDF = ifft_P.real(); + + itk_image_t::PixelType min; + itk_image_t::PixelType max; + image_min_max(PDF.GetPointer(), min, max); + + // save(cast(remap_min_max(PDF, 0.0, 255.0)), + // "Prefill.tif"); + + // Set areas that cannot be a match to zero + // TODO: //Need to fill with an ellipse to get mins/maxs correct + + // if(xLeftBorderCutoff < xRightBorderCutoff && + // yLowBorderCutoff < yHighBorderCutoff) + // { + // fill(PDF, xLeftBorderCutoff, yLowBorderCutoff, xRightBorderCutoff-xLeftBorderCutoff, + //yHighBorderCutoff-yLowBorderCutoff, min); + // } + // + // #ifdef DEBUG + // save(cast(remap_min_max(PDF, 0.0, 255.0)), + //"Prefill.tif"); + // #endif + + int PixelsInOverlapZone = 0; + itk_image_t::IndexType iPixel; + for (iPixel[1] = 0; iPixel[1] <= ny / 2; iPixel[1]++) + { + vec2d_t pt; + for (iPixel[0] = 0; iPixel[0] <= nx / 2; iPixel[0]++) + { + pt[0] = iPixel[0]; + pt[1] = iPixel[1]; + + double overlap = OverlapPercent(fi_size, pt); + if (overlap >= overlap_min && overlap <= overlap_max) + { + PixelsInOverlapZone += 4; + continue; + } + + pt[0] = nx - iPixel[0]; + pt[1] = iPixel[1]; + + overlap = OverlapPercent(fi_size, pt); + if (overlap >= overlap_min && overlap <= overlap_max) + { + PixelsInOverlapZone += 4; + continue; + } + + pt[0] = iPixel[0]; + pt[1] = ny - iPixel[1]; + + overlap = OverlapPercent(fi_size, pt); + if (overlap >= overlap_min && overlap <= overlap_max) + { + PixelsInOverlapZone += 4; + continue; + } + + pt[0] = nx - iPixel[0]; + pt[1] = ny - iPixel[1]; + + overlap = OverlapPercent(fi_size, pt); + if (overlap >= overlap_min && overlap <= overlap_max) + { + PixelsInOverlapZone += 4; + continue; + } + + // If we got here the pixel can't be overlapping + itk_image_t::IndexType iSetPixel = iPixel; + PDF->SetPixel(iSetPixel, min); + + iSetPixel[0] = (nx - 1) - iPixel[0]; + PDF->SetPixel(iSetPixel, min); + + iSetPixel[0] = iPixel[0]; + iSetPixel[1] = (ny - 1) - iPixel[1]; + PDF->SetPixel(iSetPixel, min); + + iSetPixel[0] = (nx - 1) - iPixel[0]; + iSetPixel[1] = (ny - 1) - iPixel[1]; + PDF->SetPixel(iSetPixel, min); + /* + if(overlap < overlap_min) + PDF->SetPixel(iPixel, min); + + else if(overlap > overlap_max) + PDF->SetPixel(iPixel, min); + */ + } + } + + /* + itk_image_t::IndexType ix; + ix[0] = 0; + ix[1] = 0; + PDF->SetPixel(ix, min); + ix[0] = nx-1; + ix[1] = 0; + PDF->SetPixel(ix, min); + ix[0] = 0; + ix[1] = ny-1; + PDF->SetPixel(ix, min); + ix[0] = nx-1; + ix[1] = ny-1; + PDF->SetPixel(ix, min); + */ + // fill(PDF, 0, 0, xLeftBorderStart, ny, min); + // ill(PDF, xRightBorderStart, 0, nx-xRightBorderStart, ny, min); + // fill(PDF, xLeftBorderStart, 0, xRightBorderStart-xLeftBorderStart, yLowBorderStart, min); + // fill(PDF, xLeftBorderStart, yHighBorderStart, xRightBorderStart-xLeftBorderStart, + //ny-yHighBorderStart, min); + + // #ifdef DEBUG + // save(cast(remap_min_max(PDF, 0.0, 255.0)), + //"Postfill.tif"); + // #endif + + // look for the maxima in the PDF, + // TODO: Should we count the dead regions towards our histogram because they'll be examined? + // double area = double(max_sz[0] * max_sz[1]); + // if(xLeftBorderCutoff < xRightBorderCutoff && + // yLowBorderCutoff < yHighBorderCutoff) + // area -= ((xRightBorderCutoff-xLeftBorderCutoff) * (yHighBorderCutoff-yLowBorderCutoff)); + double area = PixelsInOverlapZone; + + // a minimum of 5 pixels and a maximum of 64 pixels may be attributed + // to local maxima in the image: + // double fraction = std::min(64.0 / area, std::max(5.0 / area, 1e-3)); + double fraction = std::min(64.0 / area, std::max(5.0 / area, 1e-2)); + + // the entire image should never be treated as a maxima cluster: + assert(fraction < 1.0); + + // find the maxima clusters: + return find_maxima_cm(max_list, PDF, 1.0 - fraction); +} diff --git a/src/IRGridCommon.cxx b/src/IRGridCommon.cxx new file mode 100644 index 0000000..8405bc1 --- /dev/null +++ b/src/IRGridCommon.cxx @@ -0,0 +1,333 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : grid_common.cxx +// Author : Pavel A. Koshevoy +// Created : Wed Jan 10 09:31:00 MDT 2007 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : code used to refine mesh transform control points + +// the includes: +#include "IRGridCommon.h" + + +//---------------------------------------------------------------- +// setup_grid_transform +// +bool +setup_grid_transform(the_grid_transform_t & transform, + unsigned int rows, + unsigned int cols, + const pnt2d_t & tile_min, + const pnt2d_t & tile_max, + const mask_t * tile_mask, + base_transform_t::ConstPointer mosaic_to_tile, + unsigned int max_iterations, + double min_step_scale, + double min_error_sqrd, + unsigned int pick_up_pace_steps) +{ + const itk::GridTransform * gt = dynamic_cast(mosaic_to_tile.GetPointer()); + + std::vector xy_arr((rows + 1) * (cols + 1)); + std::vector xy_apx((rows + 1) * (cols + 1)); + + image_t::Pointer dx = make_image(cols + 1, rows + 1, 1.0, 0.0); + image_t::Pointer dy = make_image(cols + 1, rows + 1, 1.0, 0.0); + + typedef itk::LegendrePolynomialTransform approx_transform_t; + + // the mosaic to tile transform is typically more stable: + approx_transform_t::Pointer mosaic_to_tile_approx; + + if (gt == NULL) + { + mosaic_to_tile_approx = approx_transform(tile_min, + tile_max, + tile_mask, + mosaic_to_tile.GetPointer(), + 16, // samples per edge + 1, // landmark generator version + true); // iterative refinement + } + + // temporaries: + vec2d_t tile_ext = tile_max - tile_min; + + bool ReturnVal = true; + +#pragma omp parallel for + for (int row = 0; row <= (int)rows; row++) + { + pnt2d_t uv; + pnt2d_t pq; + + pq[1] = double(row) / double(rows); + uv[1] = tile_min[1] + tile_ext[1] * pq[1]; + for (int col = 0; col <= (int)cols; col++) + { + pq[0] = double(col) / double(cols); + uv[0] = tile_min[0] + tile_ext[0] * pq[0]; + + // shortcut: + unsigned int index = row * (cols + 1) + col; + pnt2d_t & xy = xy_arr[index]; + pnt2d_t & xy_approx = xy_apx[index]; + + if (gt == NULL) + { + // general transform: + if (!find_inverse(tile_min, + tile_max, + mosaic_to_tile_approx.GetPointer(), + uv, + xy_approx, + max_iterations, + min_step_scale, + min_error_sqrd, + pick_up_pace_steps)) + { + // we are screwed: + ReturnVal |= false; + break; + } + + if (!find_inverse(tile_min, + tile_max, + mosaic_to_tile.GetPointer(), + uv, + xy, + max_iterations, + min_step_scale, + min_error_sqrd, + pick_up_pace_steps)) + { + xy = xy_approx; + } + + pnt2d_t uv2 = mosaic_to_tile->TransformPoint(xy); + + // verify that the point maps back correctly within some tolerance: + vec2d_t e_uv = uv2 - uv; + + // verify that the approximate and exact aren't too far apart: + vec2d_t e_xy = xy_approx - xy; + + double e_uv_absolute = e_uv.GetSquaredNorm(); + + // FIXME: this is an idea -- if the exact transform give wildely + // differing result from the approximate transform, we may be able + // remove such outliers via a median filter. To do that, we have to + // maintain an image of the approximate/exact result differences: + image_t::IndexType ix; + ix[0] = col; + ix[1] = row; + dx->SetPixel(ix, e_xy[0]); + dy->SetPixel(ix, e_xy[1]); + + // FIXME: this is a temporary crutch, the method outlined above + // should be used instead: + static const double uv_tolerance = 1e-6; + if (e_uv_absolute > uv_tolerance) + { + xy = xy_approx; + } + } + else + { + // discontinuous transform -- this is a more stable way to resample + // the transformation mesh: + bool ok = gt->transform_.transform_inv(pq, xy); +#if !defined(__APPLE__) + assert(ok); + assert(xy[0] == xy[0] && xy[1] == xy[1]); + ReturnVal &= ok; +#endif + } + } + } + + if (!ReturnVal) + return false; + +#if 0 + save(cast + (remap_min_max(dx)), + "init-error-x.tif"); + + save(cast + (remap_min_max(dy)), + "init-error-y.tif"); +#endif + + transform.setup(rows, cols, tile_min, tile_max, xy_arr); + return true; +} + +//---------------------------------------------------------------- +// setup_mesh_transform +// +bool +setup_mesh_transform(the_mesh_transform_t & transform, + unsigned int rows, + unsigned int cols, + const pnt2d_t & tile_min, + const pnt2d_t & tile_max, + const mask_t * tile_mask, + base_transform_t::ConstPointer mosaic_to_tile, + unsigned int max_iterations, + double min_step_scale, + double min_error_sqrd, + unsigned int pick_up_pace_steps) +{ + const itk::GridTransform * gt = dynamic_cast(mosaic_to_tile.GetPointer()); + + std::vector xy_arr((rows + 1) * (cols + 1)); + std::vector xy_apx((rows + 1) * (cols + 1)); + + image_t::Pointer dx = make_image(cols + 1, rows + 1, 1.0, 0.0); + image_t::Pointer dy = make_image(cols + 1, rows + 1, 1.0, 0.0); + + typedef itk::LegendrePolynomialTransform approx_transform_t; + + // the mosaic to tile transform is typically more stable: + approx_transform_t::Pointer mosaic_to_tile_approx; + + if (gt == NULL) + { + mosaic_to_tile_approx = approx_transform(tile_min, + tile_max, + tile_mask, + mosaic_to_tile.GetPointer(), + 16, // samples per edge + 1, // landmark generator version + true); // iterative refinement + } + + // temporaries: + vec2d_t tile_ext = tile_max - tile_min; + pnt2d_t uv; + pnt2d_t pq; + + std::vector uv_list((rows + 1) * (cols + 1)); + + for (unsigned int row = 0; row <= rows; row++) + { + pq[1] = double(row) / double(rows); + uv[1] = tile_min[1] + tile_ext[1] * pq[1]; + for (unsigned int col = 0; col <= cols; col++) + { + pq[0] = double(col) / double(cols); + uv[0] = tile_min[0] + tile_ext[0] * pq[0]; + + // shortcut: + unsigned int index = row * (cols + 1) + col; + pnt2d_t & xy = xy_arr[index]; + pnt2d_t & xy_approx = xy_apx[index]; + uv_list[index] = pq; + + if (gt == NULL) + { + // general transform: + if (!find_inverse(tile_min, + tile_max, + mosaic_to_tile_approx.GetPointer(), + uv, + xy_approx, + max_iterations, + min_step_scale, + min_error_sqrd, + pick_up_pace_steps)) + { + // we are screwed: + return false; + } + + if (!find_inverse(tile_min, + tile_max, + mosaic_to_tile.GetPointer(), + uv, + xy, + max_iterations, + min_step_scale, + min_error_sqrd, + pick_up_pace_steps)) + { + xy = xy_approx; + } + + pnt2d_t uv2 = mosaic_to_tile->TransformPoint(xy); + + // verify that the point maps back correctly within some tolerance: + vec2d_t e_uv = uv2 - uv; + + // verify that the approximate and exact aren't too far apart: + vec2d_t e_xy = xy_approx - xy; + + double e_uv_absolute = e_uv.GetSquaredNorm(); + + // FIXME: this is an idea -- if the exact transform give wildely + // differing result from the approximate transform, we may be able + // remove such outliers via a median filter. To do that, we have to + // maintain an image of the approximate/exact result differences: + image_t::IndexType ix; + ix[0] = col; + ix[1] = row; + dx->SetPixel(ix, e_xy[0]); + dy->SetPixel(ix, e_xy[1]); + + // FIXME: this is a temporary crutch, the method outlined above + // should be used instead: + static const double uv_tolerance = 1e-6; + if (e_uv_absolute > uv_tolerance) + { + xy = xy_approx; + } + } + else + { + // discontinuous transform -- this is a more stable way to resample + // the transformation mesh: + bool ok = gt->transform_.transform_inv(pq, xy); +#if !defined(__APPLE__) + assert(ok); + assert(xy[0] == xy[0] && xy[1] == xy[1]); +#endif + if (!ok) + return false; + } + } + } + +#if 0 + save(cast + (remap_min_max(dx)), + "init-error-x.tif"); + + save(cast + (remap_min_max(dy)), + "init-error-y.tif"); +#endif + + transform.setup(tile_min, tile_max, uv_list, xy_arr); + return true; +} diff --git a/src/IRGridTransform.cxx b/src/IRGridTransform.cxx new file mode 100644 index 0000000..96f3279 --- /dev/null +++ b/src/IRGridTransform.cxx @@ -0,0 +1,1487 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: nil -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_grid_transform.cxx +// Author : Pavel A. Koshevoy +// Created : Thu Nov 30 13:37:18 MST 2006 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : A discontinuous transform -- a uniform grid of vertices is +// mapped to an image. At each vertex, in addition to image +// space coordinates, a second set of coordinates is stored. +// This is similar to texture mapped OpenGL triangle meshes, +// where the texture coordinates correspond to the image space +// vertex coordinates. + +// local includes: +#include "IRGridTransform.h" + +// system includes: +#include +#include +#include + + +//---------------------------------------------------------------- +// GRID_DENSITY +// +static const int GRID_DENSITY = 1; + +//---------------------------------------------------------------- +// EPSILON +// +static const double EPSILON = 1e-6; + + +//---------------------------------------------------------------- +// WARPING +// +#ifdef DEBUG_WARPING +bool WARPING = false; +#endif + +//---------------------------------------------------------------- +// triangle_t::triangle_t +// +triangle_t::triangle_t() +{ + vertex_[0] = ~0; + vertex_[1] = ~0; + vertex_[2] = ~0; +} + +//---------------------------------------------------------------- +// triangle_intersect +// +inline static bool +triangle_intersect(const double * pwb, + const double * pwc, + const double & pt_x, + const double & pt_y, + double * barycentric_coords) +{ +#define wa barycentric_coords[0] +#define wb barycentric_coords[1] +#define wc barycentric_coords[2] + + wb = pwb[0] * pt_x + pwb[1] * pt_y + pwb[2]; + wc = pwc[0] * pt_x + pwc[1] * pt_y + pwc[2]; + + // corner cases -- clamp when within tolerance: + if (wb < 0 && wb > -EPSILON) + wb = 0; + if (wb > 1 && (wb - 1) < EPSILON) + wb = 1; + if (wc < 0 && wc > -EPSILON) + wc = 0; + if (wc > 1 && (wc - 1) < EPSILON) + wc = 1; + + wa = 1.0 - (wb + wc); + if (wa < 0 && wa > -EPSILON) + wa = 0; + if (wa > 1 && (wa - 1) < EPSILON) + wa = 1; + + return (wa >= 0.0 && wb >= 0.0 && wc >= 0.0); + +#undef wa +#undef wb +#undef wc +} + +//---------------------------------------------------------------- +// triangle_t::intersect +// +bool +triangle_t::xy_intersect(const vertex_t * v_arr, const pnt2d_t & xy, pnt2d_t & uv) const +{ +#define wa barycentric_coords[0] +#define wb barycentric_coords[1] +#define wc barycentric_coords[2] + + double barycentric_coords[3]; + if (!triangle_intersect(xy_pwb, xy_pwc, xy[0], xy[1], barycentric_coords)) + { + return false; + } + + const pnt2d_t & A = v_arr[vertex_[0]].uv_; + const pnt2d_t & B = v_arr[vertex_[1]].uv_; + const pnt2d_t & C = v_arr[vertex_[2]].uv_; + + uv[0] = A[0] * wa + B[0] * wb + C[0] * wc; + uv[1] = A[1] * wa + B[1] * wb + C[1] * wc; + +#ifdef DEBUG_WARPING + static const double THRESHOLD = 5e-2; + if (WARPING && (fabs(wa) < THRESHOLD || fabs(wb) < THRESHOLD || fabs(wc) < THRESHOLD)) + { + uv[0] = 0.0; + uv[1] = 0.0; + } +#endif // DEBUG_WARPING + + return true; + +#undef wa +#undef wb +#undef wc +} + +//---------------------------------------------------------------- +// triangle_t::uv_intersect +// +bool +triangle_t::uv_intersect(const vertex_t * v_arr, const pnt2d_t & uv, pnt2d_t & xy) const +{ +#define wa barycentric_coords[0] +#define wb barycentric_coords[1] +#define wc barycentric_coords[2] + + double barycentric_coords[3]; + if (!triangle_intersect(uv_pwb, uv_pwc, uv[0], uv[1], barycentric_coords)) + { +#if 0 + // for debugging: + static int count = 0; + count++; + printf("%3i. %+e %+e %+e, %e\n", count, wa, wb, wc, + fabs(wa) + fabs(wb) + fabs(wc)); + triangle_intersect(uv_pwb, uv_pwc, uv[0], uv[1], barycentric_coords); +#endif + + return false; + } + + const pnt2d_t & A = v_arr[vertex_[0]].xy_; + const pnt2d_t & B = v_arr[vertex_[1]].xy_; + const pnt2d_t & C = v_arr[vertex_[2]].xy_; + + xy[0] = A[0] * wa + B[0] * wb + C[0] * wc; + xy[1] = A[1] * wa + B[1] * wb + C[1] * wc; + + return true; + +#undef wa +#undef wb +#undef wc +} + + +//---------------------------------------------------------------- +// the_acceleration_grid_t::the_acceleration_grid_t +// +the_acceleration_grid_t::the_acceleration_grid_t() + : rows_(0) + , cols_(0) +{ + // reset the grid bounding box: + xy_min_[0] = std::numeric_limits::max(); + xy_min_[1] = xy_min_[0]; + xy_ext_[0] = 0.0; + xy_ext_[1] = 0.0; +} + +//---------------------------------------------------------------- +// the_acceleration_grid_t::xy_cell +// +unsigned int +the_acceleration_grid_t::xy_cell(const pnt2d_t & xy) const +{ + // find where in the grid the point lands: + double a = (xy[0] - xy_min_[0]) / xy_ext_[0]; + double b = (xy[1] - xy_min_[1]) / xy_ext_[1]; + if (a >= 0.0 && a <= 1.0 && b >= 0.0 && b <= 1.0) + { + double c = a * double(cols_); + double r = b * double(rows_); + + unsigned int col = c >= cols_ ? cols_ - 1 : c; + unsigned int row = r >= rows_ ? rows_ - 1 : r; + return row * cols_ + col; + } + + return ~0; +} + +//---------------------------------------------------------------- +// the_acceleration_grid_t::xy_triangle +// +unsigned int +the_acceleration_grid_t::xy_triangle(const pnt2d_t & xy, pnt2d_t & uv) const +{ + unsigned int cell_id = xy_cell(xy); + + if (cell_id != (unsigned int)(~0)) + { + // shortcuts: + const std::vector & cell = xy_[cell_id]; + const vertex_t * v_arr = &(mesh_[0]); + + // check each candidate triangle for intersection: + for (int iter = 0; iter < (int)cell.size(); iter++) + { + unsigned int iTri = cell[iter]; + const triangle_t & tri = tri_[iTri]; + if (tri.xy_intersect(v_arr, xy, uv)) + { + return iTri; + } + } + } + + return ~0; +} + +//---------------------------------------------------------------- +// the_acceleration_grid_t::uv_cell +// +unsigned int +the_acceleration_grid_t::uv_cell(const pnt2d_t & uv) const +{ + // find where in the grid the point lands: + double a = uv[0]; + double b = uv[1]; + if (a >= 0.0 && a <= 1.0 && b >= 0.0 && b <= 1.0) + { + double c = std::min(double(cols_ - 1), a * double(cols_)); + double r = std::min(double(rows_ - 1), b * double(rows_)); + + unsigned int col = (unsigned int)(floor(c)); + unsigned int row = (unsigned int)(floor(r)); + + return row * cols_ + col; + } + + return ~0; +} + +//---------------------------------------------------------------- +// the_acceleration_grid_t::uv_triangle +// +unsigned int +the_acceleration_grid_t::uv_triangle(const pnt2d_t & uv, pnt2d_t & xy) const +{ + unsigned int cell_id = uv_cell(uv); + + if (cell_id != (unsigned int)(~0)) + { + // shortcuts: + const std::vector & cell = uv_[cell_id]; + const vertex_t * v_arr = &(mesh_[0]); + + // check each candidate triangle for intersection: + for (unsigned int iter = 0; iter < cell.size(); iter++) + { + unsigned int iTri = cell[iter]; + const triangle_t & tri = tri_[iTri]; + if (tri.uv_intersect(v_arr, uv, xy)) + { + return iTri; + } + } + } + + return ~0; +} + +//---------------------------------------------------------------- +// the_acceleration_grid_t::update +// +void +the_acceleration_grid_t::update(const vec2d_t * xy_shift) +{ + unsigned int num_verts = mesh_.size(); + vertex_t * v_arr = &(mesh_[0]); + for (unsigned int i = 0; i < num_verts; i++) + { + // cerr << v_arr[i].xy_ << " -> "; + v_arr[i].xy_ += xy_shift[i]; + // cerr << v_arr[i].xy_ << endl; + } + + rebuild(); +} + +//---------------------------------------------------------------- +// the_acceleration_grid_t::shift +// +void +the_acceleration_grid_t::shift(const vec2d_t & xy_shift) +{ + unsigned int num_verts = mesh_.size(); + vertex_t * v_arr = &(mesh_[0]); + for (unsigned int i = 0; i < num_verts; i++) + { + // cerr << v_arr[i].xy_ << " -> "; + v_arr[i].xy_ += xy_shift; + // cerr << v_arr[i].xy_ << endl; + } + + rebuild(); +} + +//---------------------------------------------------------------- +// the_acceleration_grid_t::resize +// +void +the_acceleration_grid_t::resize(unsigned int rows, unsigned int cols) +{ + // reset the grid bounding box: + xy_min_[0] = std::numeric_limits::max(); + xy_min_[1] = xy_min_[0]; + xy_ext_[0] = 0.0; + xy_ext_[1] = 0.0; + + rows_ = rows; + cols_ = cols; + xy_.resize(rows_ * cols_); + uv_.resize(rows_ * cols_); +} + +//---------------------------------------------------------------- +// the_acceleration_grid_t::rebuild +// +void +the_acceleration_grid_t::rebuild() +{ + // reset the grid bounding box and destroy the the old grid: + xy_min_[0] = std::numeric_limits::max(); + xy_min_[1] = xy_min_[0]; + + pnt2d_t xy_max; + xy_max[0] = -xy_min_[0]; + xy_max[1] = -xy_min_[1]; + + // find the new grid bounding box: + unsigned int num_verts = mesh_.size(); + vertex_t * v_arr = &(mesh_[0]); + for (unsigned int i = 0; i < num_verts; i++) + { + update_bbox(xy_min_, xy_max, v_arr[i].xy_); + } + xy_ext_ = xy_max - xy_min_; + +#if 0 + cerr << "xy_min: " << xy_min_ << endl + << "xy_max: " << xy_max << endl + << "xy_ext: " << xy_ext_ << endl; +#endif + + // reset to empty grid: + xy_.assign(xy_.size(), std::vector()); + uv_.assign(uv_.size(), std::vector()); + + xy_.reserve(10); + uv_.reserve(10); + + // add triangles to the grid: + unsigned int num_triangles = tri_.size(); + for (unsigned int i = 0; i < num_triangles; i++) + { + update_grid(i); + } + +#if 0 + int count = 0; + for (unsigned int row = 0; row < rows_; row++) + { + for (unsigned int col = 0; col < cols_; col++) + { + count += xy_[row * cols_ + col].size(); + cout << setw(3) << xy_[row * cols_ + col].size(); + } + cout << endl; + } + + assert(count > 0); +#endif +} + +//---------------------------------------------------------------- +// intersect_lines_2d +// +// Find the intersection parameters (u, v) of 2 parametric lines +// A = a + b * u +// B = c + d * v +// +static bool +intersect_lines_2d( // start point A: + double ax, + double ay, + + // direction vector A: + double bx, + double by, + + // start point B: + double cx, + double cy, + + // direction vector B: + double dx, + double dy, + + // results: + double & u, + double & v) +{ + // solve the linear system Ax = b: + // + // [bx -dx] [u] = [cx - ax] + // [by -dy] [v] [cy - ay] + // + + double det_A = dx * by - bx * dy; + if (det_A == 0.0) + return false; + + double det_A_inv = 1.0 / det_A; + double A_inv[][2] = { { -dy * det_A_inv, dx * det_A_inv }, { -by * det_A_inv, bx * det_A_inv } }; + + double b[] = { cx - ax, cy - ay }; + u = A_inv[0][0] * b[0] + A_inv[0][1] * b[1]; + v = A_inv[1][0] * b[0] + A_inv[1][1] * b[1]; + + return true; +} + +//---------------------------------------------------------------- +// intersect_bbox_triangle +// +static bool +intersect_bbox_triangle(const pnt2d_t & min, + const pnt2d_t & max, + const pnt2d_t & A, + const pnt2d_t & B, + const pnt2d_t & C) +{ + double lines_a[][4] = { // horizontal: + { min[0], min[1], max[0] - min[0], 0.0 }, + { min[0], max[1], max[0] - min[0], 0.0 }, + // vertical: + { min[0], min[1], 0.0, max[1] - min[1] }, + { max[0], min[1], 0.0, max[1] - min[1] } + }; + + double lines_b[][4] = { { A[0], A[1], B[0] - A[0], B[1] - A[1] }, + { A[0], A[1], C[0] - A[0], C[1] - A[1] }, + { B[0], B[1], C[0] - B[0], C[1] - B[1] } }; + + double u; + double v; + for (unsigned int i = 0; i < 4; i++) + { + for (unsigned int j = 0; j < 3; j++) + { + if (intersect_lines_2d(lines_a[i][0], + lines_a[i][1], + lines_a[i][2], + lines_a[i][3], + + lines_b[j][0], + lines_b[j][1], + lines_b[j][2], + lines_b[j][3], + + u, + v)) + { + if (u >= 0.0 && u <= 1.0 && v >= 0.0 && v <= 1.0) + { + return true; + } + } + } + } + + return false; +} + +//---------------------------------------------------------------- +// update_grid +// +static void +update_grid( // the acceleration grid: + std::vector * grid, + const unsigned int rows, + const unsigned int cols, + + // acceleration grid bounding box: + const pnt2d_t & grid_min, + const vec2d_t & grid_ext, + + // the triangle being added to the grid: + const pnt2d_t & A, + const pnt2d_t & B, + const pnt2d_t & C, + const unsigned int tri_id, + + // calculate fast barycentric coordinate calculation coefficients: + double * pwb, + double * pwc) +{ + // precompute the barycentric calculation coefficients: + { + vec2d_t b = B - A; + vec2d_t c = C - A; + double bycx_bxcy = b[1] * c[0] - b[0] * c[1]; + + pwb[0] = -c[1] / bycx_bxcy; + pwb[1] = c[0] / bycx_bxcy; + pwb[2] = (c[1] * A[0] - c[0] * A[1]) / bycx_bxcy; + + pwc[0] = b[1] / bycx_bxcy; + pwc[1] = -b[0] / bycx_bxcy; + pwc[2] = (b[0] * A[1] - b[1] * A[0]) / bycx_bxcy; + } + + pnt2d_t min = A; + pnt2d_t max = min; + update_bbox(min, max, B); + update_bbox(min, max, C); + + const double & gw = grid_ext[0]; + double a[] = { (min[0] - grid_min[0]) / gw, (max[0] - grid_min[0]) / gw }; + + unsigned int c[] = { std::min(cols - 1, (unsigned int)(floor(a[0] * double(cols)))), + std::min(cols - 1, (unsigned int)(floor(a[1] * double(cols)))) }; + + const double & gh = grid_ext[1]; + double b[] = { (min[1] - grid_min[1]) / gh, (max[1] - grid_min[1]) / gh }; + + unsigned int r[] = { std::min(rows - 1, (unsigned int)(floor(b[0] * double(rows)))), + std::min(rows - 1, (unsigned int)(floor(b[1] * double(rows)))) }; + + // temporary barycentric coordinates of point inside the triangle: + double barycentric_coords[3]; + + for (unsigned int row = r[0]; row <= r[1]; row++) + { + for (unsigned int col = c[0]; col <= c[1]; col++) + { + unsigned int i = row * cols + col; + std::vector & cell = grid[i]; + + // calculate the bounding box of this grid cell: + pnt2d_t min(grid_min); + min[0] += gw * double(col) / double(cols); + min[1] += gh * double(row) / double(rows); + + pnt2d_t max(grid_min); + max[0] += gw * double(col + 1) / double(cols); + max[1] += gh * double(row + 1) / double(rows); + + if (inside_bbox(min, max, A) || inside_bbox(min, max, B) || inside_bbox(min, max, C) || + triangle_intersect(pwb, pwc, min[0], min[1], barycentric_coords) || + triangle_intersect(pwb, pwc, max[0], min[1], barycentric_coords) || + triangle_intersect(pwb, pwc, max[0], max[1], barycentric_coords) || + triangle_intersect(pwb, pwc, min[0], max[1], barycentric_coords) || + intersect_bbox_triangle(min, max, A, B, C)) + { + // James A: Modifying this to be a bit more selective about which triangles are added to which grid cells. If a + // triangle only shares an edge with the top or right cell wall we do not add it + unsigned int numVertsInsideBBox = 0; + numVertsInsideBBox += inside_bbox(min, max, A); + numVertsInsideBBox += inside_bbox(min, max, B); + numVertsInsideBBox += inside_bbox(min, max, C); + + // If all three verts are inside the box then this it is more likely this triangle will match a test than a + // triangle with only one or two verts inside the box. Put it at the front of the line + if (numVertsInsideBBox == 3) + cell.insert(cell.begin(), tri_id); + else + cell.push_back(tri_id); + } + } + } +} + +//---------------------------------------------------------------- +// the_acceleration_grid_t::update_grid +// +void +the_acceleration_grid_t::update_grid(unsigned int t_idx) +{ + // shortcuts: + triangle_t & tri = tri_[t_idx]; + const vertex_t * v_arr = &(mesh_[0]); + const vertex_t & v0 = v_arr[tri.vertex_[0]]; + const vertex_t & v1 = v_arr[tri.vertex_[1]]; + const vertex_t & v2 = v_arr[tri.vertex_[2]]; + + // update the xy-grid: + ::update_grid(&(xy_[0]), + rows_, + cols_, + + // xy-grid bbox: + xy_min_, + xy_ext_, + + // xy-triangle: + v0.xy_, + v1.xy_, + v2.xy_, + + t_idx, + tri.xy_pwb, + tri.xy_pwc); + + // update the uv-grid: + ::update_grid(&(uv_[0]), + rows_, + cols_, + + // uv-grid bbox: + pnt2d(0, 0), + vec2d(1, 1), + + // xy-triangle: + v0.uv_, + v1.uv_, + v2.uv_, + + t_idx, + tri.uv_pwb, + tri.uv_pwc); +} + + +//---------------------------------------------------------------- +// the_base_triangle_transform_t::transform +// +bool +the_base_triangle_transform_t::transform(const pnt2d_t & xy, pnt2d_t & uv) const +{ + unsigned int t_id = grid_.xy_triangle(xy, uv); + + if (t_id == (unsigned int)(~0)) + { + uv[0] = std::numeric_limits::quiet_NaN(); + uv[1] = uv[0]; + } + + return t_id != (unsigned int)(~0); +} + +//---------------------------------------------------------------- +// the_base_triangle_transform_t::transform_inv +// +bool +the_base_triangle_transform_t::transform_inv(const pnt2d_t & uv, pnt2d_t & xy) const +{ + unsigned int t_id = grid_.uv_triangle(uv, xy); + + if (t_id == (unsigned int)(~0)) + { +#if 0 + // for debugging: + grid_.uv_triangle(uv, xy); +#endif + + xy[0] = std::numeric_limits::quiet_NaN(); + xy[1] = xy[0]; + } + + return t_id != (unsigned int)(~0); +} + +//---------------------------------------------------------------- +// the_base_triangle_transform_t::jacobian +// +bool +the_base_triangle_transform_t::jacobian(const pnt2d_t & P, unsigned int * idx, double * jac) const +{ + pnt2d_t uv; + unsigned int t_id = grid_.xy_triangle(P, uv); + if (t_id == (unsigned int)(~0)) + return false; + + const triangle_t & tri = grid_.tri_[t_id]; + idx[0] = tri.vertex_[0]; + idx[1] = tri.vertex_[1]; + idx[2] = tri.vertex_[2]; + + const vertex_t & A = grid_.mesh_[idx[0]]; + const vertex_t & B = grid_.mesh_[idx[1]]; + const vertex_t & C = grid_.mesh_[idx[2]]; + + // calculate partial derivatrives of wB and wC with respect to A, B, C: + double dw[2][6]; + { + const double & Ax = A.xy_[0]; + const double & Ay = A.xy_[1]; + const double & Bx = B.xy_[0]; + const double & By = B.xy_[1]; + const double & Cx = C.xy_[0]; + const double & Cy = C.xy_[1]; + const double & Px = P[0]; + const double & Py = P[1]; + + double bx = Bx - Ax; + double cx = Cx - Ax; + double px = Px - Ax; + + double by = By - Ay; + double cy = Cy - Ay; + double py = Py - Ay; + + double pycx_pxcy = py * cx - px * cy; + double pxby_pybx = px * by - py * bx; + double bycx_bxcy = by * cx - bx * cy; + double bycx_bxcy_2 = bycx_bxcy * bycx_bxcy; + + // dwB/dAx, dwB/dAy: + dw[0][0] = (Cy - Py) / bycx_bxcy - (Cy - By) / bycx_bxcy_2; + dw[0][1] = (Px - Cx) / bycx_bxcy - (Bx - Cx) / bycx_bxcy_2; + + // dwB/dBx, dwB/dBy: + dw[0][2] = cy * pycx_pxcy / bycx_bxcy_2; + dw[0][3] = -cx * pycx_pxcy / bycx_bxcy_2; + + // dwB/dCx, dwB/dCy: + dw[0][4] = py / bycx_bxcy - by * pycx_pxcy / bycx_bxcy_2; + dw[0][5] = bx * pycx_pxcy / bycx_bxcy_2 - px / bycx_bxcy; + + // dwC/dAx, dwC/dAy: + dw[1][0] = (Py - By) / bycx_bxcy - (Cy - By) * pxby_pybx / bycx_bxcy_2; + dw[1][1] = (Bx - Px) / bycx_bxcy - (Bx - Cx) * pxby_pybx / bycx_bxcy_2; + + // dwC/dBx, dwC/dBy: + dw[1][2] = cy * pxby_pybx / bycx_bxcy_2 - py / bycx_bxcy; + dw[1][3] = px / bycx_bxcy - cx * pxby_pybx / bycx_bxcy_2; + + // dwC/dCx, dwC/dCy: + dw[1][4] = -by * pxby_pybx / bycx_bxcy_2; + dw[1][5] = bx * pxby_pybx / bycx_bxcy_2; + } + + double bu = B.uv_[0] - A.uv_[0]; + double bv = B.uv_[1] - A.uv_[1]; + double cu = C.uv_[0] - A.uv_[0]; + double cv = C.uv_[1] - A.uv_[1]; + + // shortcut: + double * du = &(jac[0]); + double * dv = &(jac[6]); + + for (unsigned int i = 0; i < 6; i++) + { + du[i] = bu * dw[0][i] + cu * dw[1][i]; + dv[i] = bv * dw[0][i] + cv * dw[1][i]; + } + + return true; +} + + +//---------------------------------------------------------------- +// the_grid_transform_t::the_grid_transform_t +// +the_grid_transform_t::the_grid_transform_t() + : rows_(0) + , cols_(0) +{} + +//---------------------------------------------------------------- +// the_grid_transform_t::is_ready +// +bool +the_grid_transform_t::is_ready() const +{ + unsigned int n = rows_ * cols_; + return (n != 0) && (grid_.tri_.size() == 2 * n); +} + +//---------------------------------------------------------------- +// the_grid_transform_t::transform_inv +// +bool +the_grid_transform_t::transform_inv(const pnt2d_t & uv, pnt2d_t & xy) const +{ +#if 0 + const double & c = uv[0]; + int c0 = int(floor(c * double(cols_))); + int c1 = c0 + 1; + if (c0 < 0 || c0 > int(cols_)) return false; + if (c0 == int(cols_)) + { + c0--; + c1--; + } + + const double & r = uv[1]; + int r0 = int(floor(r * double(rows_))); + int r1 = r0 + 1; + if (r0 < 0 || r0 > int(rows_)) return false; + if (r0 == int(rows_)) + { + r0--; + r1--; + } + + double w[4]; + { + double wx[] = { + double(c1) - c * double(cols_), + c * double(cols_) - double(c0) + }; + + double wy[] = { + double(r1) - r * double(rows_), + r * double(rows_) - double(r0) + }; + + w[0] = wx[0] * wy[0]; + w[1] = wx[1] * wy[0]; + w[2] = wx[1] * wy[1]; + w[3] = wx[0] * wy[1]; + } + + xy[0] = + vertex(r0, c0).xy_[0] * w[0] + + vertex(r0, c1).xy_[0] * w[1] + + vertex(r1, c1).xy_[0] * w[2] + + vertex(r1, c0).xy_[0] * w[3]; + + xy[1] = + vertex(r0, c0).xy_[1] * w[0] + + vertex(r0, c1).xy_[1] * w[1] + + vertex(r1, c1).xy_[1] * w[2] + + vertex(r1, c0).xy_[1] * w[3]; + + return true; + +#else + + return the_base_triangle_transform_t::transform_inv(uv, xy); +#endif +} + +//---------------------------------------------------------------- +// the_grid_transform_t::setup +// +void +the_grid_transform_t::setup(unsigned int rows, + unsigned int cols, + const pnt2d_t & tile_min, + const pnt2d_t & tile_max, + const std::vector & xy) +{ + rows_ = rows; + cols_ = cols; + + tile_min_ = tile_min; + tile_ext_ = tile_max - tile_min; + + grid_.mesh_.resize((rows_ + 1) * (cols_ + 1)); + grid_.resize(rows_ * GRID_DENSITY, cols_ * GRID_DENSITY); + + // temporaries: + pnt2d_t uv; + + for (unsigned int row = 0; row <= rows_; row++) + { + uv[1] = double(row) / double(rows_); + for (unsigned int col = 0; col <= cols_; col++) + { + uv[0] = double(col) / double(cols_); + + vertex_t & vx = vertex(row, col); + vx.uv_ = uv; + vx.xy_ = xy[row * (cols_ + 1) + col]; + } + } + + setup_mesh(); +} + +//---------------------------------------------------------------- +// the_grid_transform_t::setup_mesh +// +void +the_grid_transform_t::setup_mesh() +{ + grid_.tri_.resize(rows_ * cols_ * 2); + + // temporaries: + unsigned int v_idx[4]; + unsigned int t_idx[2]; + + for (unsigned int row = 0; row < rows_; row++) + { + for (unsigned int col = 0; col < cols_; col++) + { + // vertex indices: + v_idx[0] = (cols_ + 1) * row + col; + v_idx[1] = v_idx[0] + (cols_ + 1); + v_idx[2] = v_idx[1] + 1; + v_idx[3] = v_idx[0] + 1; + + // triangle indices: + t_idx[0] = (cols_ * row + col) * 2; + t_idx[1] = t_idx[0] + 1; + + // setup triangle A: + triangle_t & tri_a = grid_.tri_[t_idx[0]]; + tri_a.vertex_[0] = v_idx[0]; + tri_a.vertex_[1] = v_idx[1]; + tri_a.vertex_[2] = v_idx[2]; + + // setup triangle B: + triangle_t & tri_b = grid_.tri_[t_idx[1]]; + tri_b.vertex_[0] = v_idx[0]; + tri_b.vertex_[1] = v_idx[2]; + tri_b.vertex_[2] = v_idx[3]; + } + } + + // initialize the acceleration grid: + grid_.rebuild(); +} + + +//---------------------------------------------------------------- +// the_mesh_transform_t::is_ready +// +bool +the_mesh_transform_t::is_ready() const +{ + bool ready = (grid_.tri_.size() > 1); + return ready; +} + +//---------------------------------------------------------------- +// the_mesh_transform_t::setup +// +bool +the_mesh_transform_t::setup(const pnt2d_t & tile_min, + const pnt2d_t & tile_max, + const std::vector & uv, + const std::vector & xy, + unsigned int accel_grid_rows, + unsigned int accel_grid_cols) +{ + const std::size_t num_pts = uv.size(); + + tile_min_ = tile_min; + tile_ext_ = tile_max - tile_min; + + grid_.mesh_.resize(num_pts); + grid_.resize(accel_grid_rows, accel_grid_cols); + + for (unsigned int i = 0; i < num_pts; i++) + { + vertex_t & vx = grid_.mesh_[i]; + vx.uv_ = uv[i]; + vx.xy_ = xy[i]; + } + + return setup_mesh(); +} + +//---------------------------------------------------------------- +// the_mesh_transform_t::insert_point +// +bool +the_mesh_transform_t::insert_point(const pnt2d_t & uv, const pnt2d_t & xy, const bool delay_setup) +{ + vertex_t vx; + vx.uv_ = uv; + vx.xy_ = xy; + + // Check for duplicate entries. + std::vector::iterator iter = grid_.mesh_.begin(); + for (; iter != grid_.mesh_.end(); ++iter) + { + if (vx.uv_ == (*iter).uv_ && vx.xy_ == (*iter).xy_) + break; + } + + // We can't allow duplicates, as it breaks the triangulation algorithm. + if (iter != grid_.mesh_.end()) + return false; + + grid_.mesh_.push_back(vx); + + // At times we want to add a bunch of points before performing + // Delauney. By waiting we can save cycles. + return (delay_setup) ? false : setup_mesh(); +} + +//---------------------------------------------------------------- +// the_mesh_transform_t::insert_point +// +bool +the_mesh_transform_t::insert_point(const pnt2d_t & uv) +{ + pnt2d_t xy; + if (!transform_inv(uv, xy)) + { + return false; + } + + return insert_point(uv, xy); +} + + +//---------------------------------------------------------------- +// CircumCircle +// +// Credit to Paul Bourke (pbourke@swin.edu.au) +// for the original Fortran 77 Program :)) +// +// Check out http://local.wasp.uwa.edu.au/~pbourke/papers/triangulate/index.html +// You can use this code however you like providing the above credits +// remain in tact. +// +// Return true if a point (xp, yp) is inside the circumcircle +// made up of the points (x1, y1), (x2, y2), (x3, y3) +// +// The circumcircle centre is returned in (xc,yc) and the radius r +// +// NOTE: A point on the edge is inside the circumcircle +// +static bool +CircumCircle(double xp, + double yp, + double x1, + double y1, + double x2, + double y2, + double x3, + double y3, + double * xc, + double * yc, + double * rsqr) +{ + double fabsy1y2 = fabs(y1 - y2); + double fabsy2y3 = fabs(y2 - y3); + + // Check for coincident points + if (fabsy1y2 < EPSILON && fabsy2y3 < EPSILON) + { + return (false); + } + + // temporaries: + double m1, m2, mx1, mx2, my1, my2; + + if (fabsy1y2 < EPSILON) + { + m2 = -(x3 - x2) / (y3 - y2); + mx2 = (x2 + x3) / 2.0; + my2 = (y2 + y3) / 2.0; + *xc = (x2 + x1) / 2.0; + *yc = m2 * (*xc - mx2) + my2; + } + else if (fabsy2y3 < EPSILON) + { + m1 = -(x2 - x1) / (y2 - y1); + mx1 = (x1 + x2) / 2.0; + my1 = (y1 + y2) / 2.0; + *xc = (x3 + x2) / 2.0; + *yc = m1 * (*xc - mx1) + my1; + } + else + { + m1 = -(x2 - x1) / (y2 - y1); + m2 = -(x3 - x2) / (y3 - y2); + mx1 = (x1 + x2) / 2.0; + mx2 = (x2 + x3) / 2.0; + my1 = (y1 + y2) / 2.0; + my2 = (y2 + y3) / 2.0; + *xc = (m1 * mx1 - m2 * mx2 + my2 - my1) / (m1 - m2); + + if (fabsy1y2 > fabsy2y3) + { + *yc = m1 * (*xc - mx1) + my1; + } + else + { + *yc = m2 * (*xc - mx2) + my2; + } + } + + double dx = x2 - *xc; + double dy = y2 - *yc; + *rsqr = dx * dx + dy * dy; + + dx = xp - *xc; + dy = yp - *yc; + + double drsqr = dx * dx + dy * dy; + return (drsqr <= *rsqr); +} + +//---------------------------------------------------------------- +// TVertex +// +typedef struct +{ + double x, y; + unsigned int idx; +} TVertex; + +//---------------------------------------------------------------- +// TTriangle +// +typedef struct +{ + int p1, p2, p3; +} TTriangle; + +//---------------------------------------------------------------- +// TEdge +// +typedef struct +{ + int p1, p2; +} TEdge; + +//---------------------------------------------------------------- +// vertex_sorter_t +// +static bool +TVertexCompare(const TVertex & a, const TVertex & b) +{ + // sort the vertices on the x-coordinate, in ascending order: + return a.x < b.x; +} + +//---------------------------------------------------------------- +// Triangulate +// +// Credit to Paul Bourke (pbourke@swin.edu.au) +// for the original Fortran 77 Program :)) +// +// Check out http://local.wasp.uwa.edu.au/~pbourke/papers/triangulate/index.html +// You can use this code however you like providing the above credits +// remain in tact. +// +// Takes as input a number of vertices in vertex array +// Passes back a list of triangular faces +// +// The triangles are arranged in a consistent clockwise order. +// The triangle array should be allocated to hold 2 * (nv + 3) - 2 triangles +// +// The vertex array must be big enough to hold 3 extra vertices +// (used internally for the supertiangle) +// +// The vertex array must be sorted in increasing x values +// +static int +Triangulate(int num_vertices, TVertex * vertex, TTriangle * tri, const int max_triangles, int & num_triangles) +{ + // Allocate memory for the completeness list, flag for each triangle + std::vector complete; + complete.assign(max_triangles, false); + + // Allocate memory for the edge list + int emax = 200; + std::vector edges(emax); + + // Set up the supertriangle + // + // This is a triangle which encompasses all the sample points. + // The supertriangle coordinates are added to the end of the + // vertex list. The supertriangle is the first triangle in + // the triangle list. + // + { + // Given a bounding box of all the vertices we can + // construct a bounding circle. Given the bounding circle we can + // construct a bounding triangle. + + // find the bounding box: + double xmin = vertex[0].x; + double ymin = vertex[0].y; + double xmax = xmin; + double ymax = ymin; + + for (int i = 1; i < num_vertices; i++) + { + if (vertex[i].x < xmin) + xmin = vertex[i].x; + if (vertex[i].x > xmax) + xmax = vertex[i].x; + if (vertex[i].y < ymin) + ymin = vertex[i].y; + if (vertex[i].y > ymax) + ymax = vertex[i].y; + } + + // find the (inflated) bounding circle: + double dx = xmax - xmin; + double dy = ymax - ymin; + + double xmid = (xmax + xmin) / 2.0; + double ymid = (ymax + ymin) / 2.0; + + double inflate = 1.1; + double r = (sqrt(dx * dx + dy * dy) / 2.0) * inflate; + + // find the bounding triangle with clockwise winding: + static const double sqrt3 = sqrt(3.0); + + vertex[num_vertices + 0].x = xmid - r * sqrt3; + vertex[num_vertices + 0].y = ymid - r; + vertex[num_vertices + 0].idx = num_vertices; + + vertex[num_vertices + 1].x = xmid; + vertex[num_vertices + 1].y = ymid + r * 2.0; + vertex[num_vertices + 1].idx = num_vertices + 1; + + vertex[num_vertices + 2].x = xmid + r * sqrt3; + vertex[num_vertices + 2].y = ymid - r; + vertex[num_vertices + 2].idx = num_vertices + 2; + + tri[0].p1 = num_vertices; + tri[0].p2 = num_vertices + 1; + tri[0].p3 = num_vertices + 2; + + complete[0] = false; + num_triangles = 1; + } + + // temporaries: + double xp = 0; + double yp = 0; + double x1 = 0; + double y1 = 0; + double x2 = 0; + double y2 = 0; + double x3 = 0; + double y3 = 0; + double xc = 0; + double yc = 0; + double r = 0; + + // Include each point one at a time into the existing mesh + int nedge = 0; + + for (int i = 0; i < num_vertices; i++) + { + xp = vertex[i].x; + yp = vertex[i].y; + nedge = 0; + + // Set up the edge buffer. + // If the point (xp,yp) lies inside the circumcircle then the + // three edges of that triangle are added to the edge buffer + // and that triangle is removed. + // + for (int j = 0; j < num_triangles; j++) + { + if (complete[j]) + { + continue; + } + + x1 = vertex[tri[j].p1].x; + y1 = vertex[tri[j].p1].y; + x2 = vertex[tri[j].p2].x; + y2 = vertex[tri[j].p2].y; + x3 = vertex[tri[j].p3].x; + y3 = vertex[tri[j].p3].y; + + const bool inside = CircumCircle(xp, yp, x1, y1, x2, y2, x3, y3, &xc, &yc, &r); + + if (xc < xp && ((xp - xc) * (xp - xc)) > r) + { + complete[j] = true; + } + + if (inside) + { + // Check that we haven't exceeded the edge list size + if (nedge + 3 >= emax) + { + emax += 100; + edges.resize(emax); + } + + edges[nedge + 0].p1 = tri[j].p1; + edges[nedge + 0].p2 = tri[j].p2; + edges[nedge + 1].p1 = tri[j].p2; + edges[nedge + 1].p2 = tri[j].p3; + edges[nedge + 2].p1 = tri[j].p3; + edges[nedge + 2].p2 = tri[j].p1; + + nedge += 3; + tri[j] = tri[num_triangles - 1]; + complete[j] = complete[num_triangles - 1]; + + num_triangles--; + j--; + } + } + + // Tag multiple edges + // + // NOTE: if all triangles are specified anticlockwise then all + // interior edges are opposite pointing in direction. + // + for (int j = 0; j < nedge - 1; j++) + { + for (int k = j + 1; k < nedge; k++) + { + if ((edges[j].p1 == edges[k].p2) && (edges[j].p2 == edges[k].p1)) + { + edges[j].p1 = -1; + edges[j].p2 = -1; + edges[k].p1 = -1; + edges[k].p2 = -1; + } + + // Shouldn't need the following, see note above + if ((edges[j].p1 == edges[k].p1) && (edges[j].p2 == edges[k].p2)) + { + edges[j].p1 = -1; + edges[j].p2 = -1; + edges[k].p1 = -1; + edges[k].p2 = -1; + } + } + } + + // Form new triangles for the current point + // Skipping over any tagged edges. + // All edges are arranged in clockwise order. + // + for (int j = 0; j < nedge; j++) + { + if (edges[j].p1 < 0 || edges[j].p2 < 0) + { + continue; + } + + if (num_triangles >= max_triangles) + { + return 4; + } + + tri[num_triangles].p1 = edges[j].p1; + tri[num_triangles].p2 = edges[j].p2; + tri[num_triangles].p3 = i; + complete[num_triangles] = false; + num_triangles++; + } + } + + // Remove triangles with supertriangle vertices + // (triangles which have a vertex number greater than num_vertices) + // + for (int i = 0; i < num_triangles; i++) + { + if (tri[i].p1 >= num_vertices || tri[i].p2 >= num_vertices || tri[i].p3 >= num_vertices) + { + tri[i] = tri[num_triangles - 1]; + num_triangles--; + i--; + } + } + + return 0; +} + + +//---------------------------------------------------------------- +// the_mesh_transform_t::setup_mesh +// +bool +the_mesh_transform_t::setup_mesh() +{ + std::size_t num_vertices = grid_.mesh_.size(); + if (num_vertices == 0) + { + return false; + } + + // shortcut: + const vertex_t * verts = &(grid_.mesh_[0]); + + // added 3 extra points for the super-triangle (bounding triangle): + std::vector vertex_vec(num_vertices + 3); + for (std::size_t i = 0; i < num_vertices; i++) + { + TVertex & vertex = vertex_vec[i]; + + // triangulation happens in the uv-space: + vertex.x = verts[i].uv_[0]; + vertex.y = verts[i].uv_[1]; + + // keep track of the original vertex index: + vertex.idx = i; + } + + // sort the vertices on the x-coordinate, in ascending order: + std::sort(vertex_vec.begin(), vertex_vec.begin() + num_vertices, &TVertexCompare); + + // In 2-D the number of triangles is calculated as + // Nt = 2 * Nv - 2 - Nb, where Nv is the number of vertices + // and Nb is the number of boundary vertices (vertices on the convex hull). + // + // Since we don't know how many of the vertices are boundary vertices + // we allocate a triangle buffer larger than necessary + // also accounting for the supertriangle: + // + const int max_triangles = 2 * (num_vertices + 3) - 2; + std::vector triangle_vec(max_triangles); + + // shortcuts: + TVertex * vertices = &(vertex_vec[0]); + TTriangle * triangles = &(triangle_vec[0]); + + int num_triangles = 0; + int error = ::Triangulate(num_vertices, vertices, triangles, max_triangles, num_triangles); + if (error != 0) + { + // this shouldn't happen: + return false; + } + + if (num_triangles == 0) + { + return true; + } + + // setup the mesh triangles: + grid_.tri_.resize(num_triangles); + triangle_t * tris = &(grid_.tri_[0]); + + for (int i = 0; i < num_triangles; i++) + { + triangle_t & tri = tris[i]; + + // flip triangle winding to counterclockwise: + tri.vertex_[0] = (vertices[triangles[i].p1]).idx; + tri.vertex_[2] = (vertices[triangles[i].p2]).idx; + tri.vertex_[1] = (vertices[triangles[i].p3]).idx; + } + + // initialize the acceleration grid: + grid_.rebuild(); + + return true; +} diff --git a/src/IRLog.cxx b/src/IRLog.cxx new file mode 100644 index 0000000..e88bf3a --- /dev/null +++ b/src/IRLog.cxx @@ -0,0 +1,200 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_log.cxx +// Author : Pavel A. Koshevoy +// Created : Fri Mar 23 11:04:53 MDT 2007 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : A text log object -- behaves almost like a std::ostream. + +// local includes: +#include "IRLog.h" + + +//---------------------------------------------------------------- +// the_log_t::the_log_t +// +the_log_t::the_log_t(): + mutex_(NULL) +{ + mutex_ = the_mutex_interface_t::create(); +} + +//----------------------------------------------------------------' +// the_log_t::~the_log_t +// +the_log_t::~the_log_t() +{ + mutex_->delete_this(); +} + +//---------------------------------------------------------------- +// the_log_t::log_no_lock +// +void +the_log_t::log_no_lock(std::ostream & (*f)(std::ostream &)) +{ + f(line_); +} + +//---------------------------------------------------------------- +// the_log_t::operator +// +the_log_t & +the_log_t::operator << (std::ostream & (*f)(std::ostream &)) +{ + the_lock_t lock(mutex_); + log_no_lock(f); + return *this; +} + +//---------------------------------------------------------------- +// the_log_t::precision +// +std::streamsize +the_log_t::precision() +{ + the_lock_t lock(mutex_); + std::streamsize p = line_.precision(); + return p; +} + +//---------------------------------------------------------------- +// the_log_t::precision +// +std::streamsize +the_log_t::precision(std::streamsize n) +{ + the_lock_t lock(mutex_); + std::streamsize p = line_.precision(n); + return p; +} + +//---------------------------------------------------------------- +// the_log_t::flags +// +std::ios::fmtflags +the_log_t::flags() const +{ + the_lock_t lock(mutex_); + std::ios::fmtflags f = line_.flags(); + return f; +} + +//---------------------------------------------------------------- +// the_log_t::flags +// +std::ios::fmtflags +the_log_t::flags(std::ios::fmtflags fmt) +{ + the_lock_t lock(mutex_); + std::ios::fmtflags f = line_.flags(fmt); + return f; +} + +//---------------------------------------------------------------- +// the_log_t::setf +// +void +the_log_t::setf(std::ios::fmtflags fmt) +{ + the_lock_t lock(mutex_); + line_.setf(fmt); +} + +//---------------------------------------------------------------- +// the_log_t::setf +// +void +the_log_t::setf(std::ios::fmtflags fmt, std::ios::fmtflags msk) +{ + the_lock_t lock(mutex_); + line_.setf(fmt, msk); +} + +//---------------------------------------------------------------- +// the_log_t::unsetf +// +void +the_log_t::unsetf(std::ios::fmtflags fmt) +{ + the_lock_t lock(mutex_); + line_.unsetf(fmt); +} + +//---------------------------------------------------------------- +// the_log_t::copyfmt +// +void +the_log_t::copyfmt(std::ostream & ostm) +{ + the_lock_t lock(mutex_); + line_.copyfmt(ostm); +} + + +//---------------------------------------------------------------- +// null_log +// +the_null_log_t * +null_log() +{ + static the_null_log_t * log = NULL; + if (log == NULL) + { + log = new the_null_log_t; + } + + return log; +} + + +//---------------------------------------------------------------- +// cerr_log +// +the_stream_log_t * +cerr_log() +{ + static the_stream_log_t * log = NULL; + if (log == NULL) + { + log = new the_stream_log_t(std::cerr); + } + + return log; +} + + +//---------------------------------------------------------------- +// cout_log +// +the_stream_log_t * +cout_log() +{ + static the_stream_log_t * log = NULL; + if (log == NULL) + { + log = new the_stream_log_t(std::cout); + } + + return log; +} diff --git a/src/IRMosaicRefinementCommon.cxx b/src/IRMosaicRefinementCommon.cxx new file mode 100644 index 0000000..1cab060 --- /dev/null +++ b/src/IRMosaicRefinementCommon.cxx @@ -0,0 +1,163 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : mosaic_refinement_common.cxx +// Author : Pavel A. Koshevoy +// Created : Mon Nov 3 20:26:25 MST 2008 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : Helper functions for automatic mosaic refinement. + +// local includes: +#include "IRMosaicRefinementCommon.h" + + +//---------------------------------------------------------------- +// regularize_displacements +// +void +regularize_displacements( // computed displacement vectors of the moving image + // grid transform control points, in mosaic space: + std::vector & xy_shift, + std::vector & mass, + + image_t::Pointer & dx, + image_t::Pointer & dy, + image_t::Pointer & db, + + // median filter radius: + const unsigned int & median_radius) +{ + // shortcuts: + image_t::RegionType::SizeType sz = dx->GetLargestPossibleRegion().GetSize(); + unsigned int mesh_cols = sz[0]; + unsigned int mesh_rows = sz[1]; + + // denoise + if (median_radius > 0) + { + dx = median(dx, median_radius); + dy = median(dy, median_radius); + // db = median(db, median_radius); + } + + // extend (fill in gaps): + typedef itk::ImageRegionConstIteratorWithIndex iter_t; + iter_t iter(dx, dx->GetLargestPossibleRegion()); + image_t::Pointer dx_blurred = cast(dx); + image_t::Pointer dy_blurred = cast(dy); + image_t::Pointer db_blurred = cast(db); + + for (iter.GoToBegin(); !iter.IsAtEnd(); ++iter) + { + image_t::IndexType index = iter.GetIndex(); + if (!db->GetPixel(index)) + { + static const double max_w = 3.0; + double w = 0.0; + double px = 0.0; + double py = 0.0; + + // keep expanding the neighborhood until at least one + // successful sample is found: + + int max_x = std::max(int(index[0]), int(mesh_cols - 1 - index[0])); + int max_y = std::max(int(index[1]), int(mesh_rows - 1 - index[1])); + int max_r = std::min(1, std::max(max_x, max_y)); + for (int r = 1; r <= max_r && w < max_w; r++) + { + image_t::IndexType ix; + int x0 = index[0] - r; + int x1 = index[0] + r; + int y0 = index[1] - r; + int y1 = index[1] + r; + + int d = 2 * r + 1; + for (int o = 0; o < d; o++) + { + ix[0] = x0; + ix[1] = y0 + o + 1; + if (ix[0] >= 0 && ix[0] < int(mesh_cols) && ix[1] >= 0 && ix[1] < int(mesh_rows) && db->GetPixel(ix)) + { + px += dx->GetPixel(ix); + py += dy->GetPixel(ix); + w += 1.0; + } + + ix[0] = x1; + ix[1] = y0 + o; + if (ix[0] >= 0 && ix[0] < int(mesh_cols) && ix[1] >= 0 && ix[1] < int(mesh_rows) && db->GetPixel(ix)) + { + px += dx->GetPixel(ix); + py += dy->GetPixel(ix); + w += 1.0; + } + + ix[0] = x0 + o; + ix[1] = y0; + if (ix[0] >= 0 && ix[0] < int(mesh_cols) && ix[1] >= 0 && ix[1] < int(mesh_rows) && db->GetPixel(ix)) + { + px += dx->GetPixel(ix); + py += dy->GetPixel(ix); + w += 1.0; + } + + ix[0] = x0 + o + 1; + ix[1] = y1; + if (ix[0] >= 0 && ix[0] < int(mesh_cols) && ix[1] >= 0 && ix[1] < int(mesh_rows) && db->GetPixel(ix)) + { + px += dx->GetPixel(ix); + py += dy->GetPixel(ix); + w += 1.0; + } + } + } + + if (w != 0.0) + { + dx_blurred->SetPixel(index, px / w); + dy_blurred->SetPixel(index, py / w); + db_blurred->SetPixel(index, 1); + } + } + } + + // blur: + dx_blurred = smooth(dx_blurred, 1.0); + dy_blurred = smooth(dy_blurred, 1.0); + // db_blurred = smooth(db_blurred, 1.0); + + dx = dx_blurred; + dy = dy_blurred; + db = db_blurred; + + // update the mesh displacement field: + iter = iter_t(dx, dx->GetLargestPossibleRegion()); + for (iter.GoToBegin(); !iter.IsAtEnd(); ++iter) + { + image_t::IndexType index = iter.GetIndex(); + unsigned int i = index[0] + index[1] * mesh_cols; + + xy_shift[i][0] = dx->GetPixel(index); + xy_shift[i][1] = dy->GetPixel(index); + mass[i] += db->GetPixel(index); + } +} diff --git a/src/IRMutex.cxx b/src/IRMutex.cxx new file mode 100644 index 0000000..c2fb8a2 --- /dev/null +++ b/src/IRMutex.cxx @@ -0,0 +1,113 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_boost_mutex.cxx +// Author : Pavel A. Koshevoy +// Created : Sat Oct 25 12:33:23 MDT 2008 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : A thin wrapper Boost mutex class. + +// local includes: +#include "IRMutex.h" + +// system includes: +#include + +// namespace access: +using std::cerr; +using std::endl; + +//---------------------------------------------------------------- +// define +// +// #define DEBUG_MUTEX + + +//---------------------------------------------------------------- +// the_boost_mutex_t::the_boost_mutex_t +// +the_boost_mutex_t::the_boost_mutex_t(): + the_mutex_interface_t() +{} + +//---------------------------------------------------------------- +// the_boost_mutex_t::~the_boost_mutex_t +// +the_boost_mutex_t::~the_boost_mutex_t() +{} + +//---------------------------------------------------------------- +// the_boost_mutex_t::delete_this +// +void +the_boost_mutex_t::delete_this() +{ + delete this; +} + +//---------------------------------------------------------------- +// the_boost_mutex_t::create +// +the_mutex_interface_t * +the_boost_mutex_t::create() +{ + return new the_boost_mutex_t(); +} + +//---------------------------------------------------------------- +// the_boost_mutex_t::lock +// +void +the_boost_mutex_t::lock() +{ +#ifdef DEBUG_MUTEX + cerr << this << "\tlock" << endl; +#endif + + mutex_.lock(); +} + +//---------------------------------------------------------------- +// the_boost_mutex_t::unlock +// +void +the_boost_mutex_t::unlock() +{ +#ifdef DEBUG_MUTEX + cerr << this << "\tunlock" << endl; +#endif + + mutex_.unlock(); +} + +//---------------------------------------------------------------- +// the_boost_mutex_t::try_lock +// +bool +the_boost_mutex_t::try_lock() +{ +#ifdef DEBUG_MUTEX + cerr << this << "\ttry_lock" << endl; +#endif + + return mutex_.try_lock(); +} diff --git a/src/IRMutexInterface.cxx b/src/IRMutexInterface.cxx new file mode 100644 index 0000000..b0c4d4b --- /dev/null +++ b/src/IRMutexInterface.cxx @@ -0,0 +1,65 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_mutex_interface.cxx +// Author : Pavel A. Koshevoy +// Created : Wed Feb 21 08:22:00 MST 2007 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : An abstract mutex class interface. + +// local includes: +#include "IRMutexInterface.h" + +// system includes: +#include + + +//---------------------------------------------------------------- +// the_mutex_interface_t::creator_ +// +the_mutex_interface_t::creator_t +the_mutex_interface_t::creator_ = NULL; + +//---------------------------------------------------------------- +// the_mutex_interface_t::~the_mutex_interface_t +// +the_mutex_interface_t::~the_mutex_interface_t() +{} + +//---------------------------------------------------------------- +// the_mutex_interface_t::set_creator +// +void +the_mutex_interface_t::set_creator(the_mutex_interface_t::creator_t creator) +{ + creator_ = creator; +} + +//---------------------------------------------------------------- +// the_mutex_interface_t::create +// +the_mutex_interface_t * +the_mutex_interface_t::create() +{ + if (creator_ == NULL) return NULL; + return creator_(); +} diff --git a/src/IRStdMutex.cxx b/src/IRStdMutex.cxx new file mode 100644 index 0000000..5e1b635 --- /dev/null +++ b/src/IRStdMutex.cxx @@ -0,0 +1,113 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_std_mutex.cxx +// Author : Pavel A. Koshevoy +// Created : Sat Oct 25 12:33:23 MDT 2008 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : A thin wrapper Boost mutex class. + +// local includes: +#include "IRStdMutex.h" + +// system includes: +#include + +// namespace access: +using std::cerr; +using std::endl; + +//---------------------------------------------------------------- +// define +// +// #define DEBUG_MUTEX + + +//---------------------------------------------------------------- +// the_std_mutex_t::the_std_mutex_t +// +the_std_mutex_t::the_std_mutex_t(): + the_mutex_interface_t() +{} + +//---------------------------------------------------------------- +// the_std_mutex_t::~the_std_mutex_t +// +the_std_mutex_t::~the_std_mutex_t() +{} + +//---------------------------------------------------------------- +// the_std_mutex_t::delete_this +// +void +the_std_mutex_t::delete_this() +{ + delete this; +} + +//---------------------------------------------------------------- +// the_std_mutex_t::create +// +the_mutex_interface_t * +the_std_mutex_t::create() +{ + return new the_std_mutex_t(); +} + +//---------------------------------------------------------------- +// the_std_mutex_t::lock +// +void +the_std_mutex_t::lock() +{ +#ifdef DEBUG_MUTEX + cerr << this << "\tlock" << endl; +#endif + + mutex_.lock(); +} + +//---------------------------------------------------------------- +// the_std_mutex_t::unlock +// +void +the_std_mutex_t::unlock() +{ +#ifdef DEBUG_MUTEX + cerr << this << "\tunlock" << endl; +#endif + + mutex_.unlock(); +} + +//---------------------------------------------------------------- +// the_std_mutex_t::try_lock +// +bool +the_std_mutex_t::try_lock() +{ +#ifdef DEBUG_MUTEX + cerr << this << "\ttry_lock" << endl; +#endif + + return mutex_.try_lock(); +} diff --git a/src/IRStdThread.cxx b/src/IRStdThread.cxx new file mode 100644 index 0000000..153d921 --- /dev/null +++ b/src/IRStdThread.cxx @@ -0,0 +1,193 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_boost_thread.cxx +// Author : Pavel A. Koshevoy +// Created : Sat Oct 25 12:33:09 MDT 2008 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : A thin wrapper for Boost thread class. + +// local include: +#include "IRStdThread.h" +#include "IRStdThreadStorage.h" +#include "IRStdMutex.h" +#include "IRMutexInterface.h" + +// system includes: +#include + +// namespace access: +using std::cout; +using std::cerr; +using std::endl; + +//---------------------------------------------------------------- +// DEBUG_THREAD +// +// #define DEBUG_THREAD + + +//---------------------------------------------------------------- +// THREAD_STORAGE +// +static thread_local the_std_thread_storage_t THREAD_STORAGE; + +//---------------------------------------------------------------- +// the_std_thread_t::the_std_thread_t +// +the_std_thread_t::the_std_thread_t(): + the_thread_interface_t(the_std_mutex_t::create()), + std_thread_(NULL) +{ + if (THREAD_STORAGE.thread_observer_.get() == nullptr) + { + THREAD_STORAGE.thread_observer_.reset(new the_thread_observer_t(*this)); + } +} + +//---------------------------------------------------------------- +// the_std_thread_t::~the_std_thread_t +// +the_std_thread_t::~the_std_thread_t() +{ + if (std_thread_) + { + wait(); + } +} + +//---------------------------------------------------------------- +// the_std_thread_t::delete_this +// +void +the_std_thread_t::delete_this() +{ + delete this; +} + +//---------------------------------------------------------------- +// ImageProcessingThread::thread_storage +// +the_thread_storage_t & +the_std_thread_t::thread_storage() +{ + return THREAD_STORAGE; +} + +//---------------------------------------------------------------- +// the_std_thread_t::start +// +void +the_std_thread_t::start() +{ + the_lock_t locker(mutex_); +#ifdef DEBUG_THREAD + cerr << "start of thread " << this << " requested" << endl; +#endif + + if (std_thread_) + { + if (!stopped_) + { + // already running: +#ifdef DEBUG_THREAD + cerr << "thread " << this << " is already running" << endl; +#endif + return; + } + else + { + // wait for the shutdown to succeed, then restart the thread: +#ifdef DEBUG_THREAD + cerr << "waiting for thread " << this << " to shut down" << endl; +#endif + wait(); + } + } + +#ifdef DEBUG_THREAD + cerr << "starting thread " << this << endl; +#endif + + // we shouldn't have a Boost thread at this stage: + assert(!std_thread_); + + // clear the termination flag: + stopped_ = false; + std_thread_ = new std::thread(callable_t(this)); +} + +//---------------------------------------------------------------- +// the_std_thread_t::wait +// +void +the_std_thread_t::wait() +{ + if (!std_thread_) return; + + if (std_thread_->get_id() == std::this_thread::get_id()) + { + assert(false); + return; + } + + std_thread_->join(); + delete std_thread_; + std_thread_ = NULL; +} + +//---------------------------------------------------------------- +// the_std_thread_t::take_a_nap +// +void +the_std_thread_t::take_a_nap(const unsigned long & microseconds) +{ + std::this_thread::sleep_for(std::chrono::microseconds(microseconds)); +} + +//---------------------------------------------------------------- +// the_std_thread_t::terminators +// +the_terminators_t & +the_std_thread_t::terminators() +{ + return terminators_; +} + +//---------------------------------------------------------------- +// the_std_thread_t::run +// +void +the_std_thread_t::run() +{ + // setup the thread storage: + { + the_lock_t locker(mutex_); + THREAD_STORAGE.thread_observer_.reset(new the_thread_observer_t(*this)); + } + + // process the transactions: + work(); + + // clean up the thread storage: + THREAD_STORAGE.thread_observer_.reset(nullptr); +} diff --git a/src/IRTerminator.cxx b/src/IRTerminator.cxx new file mode 100644 index 0000000..3c0eb48 --- /dev/null +++ b/src/IRTerminator.cxx @@ -0,0 +1,231 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_terminator.cxx +// Author : Pavel A. Koshevoy +// Created : Sun Sep 24 18:08:00 MDT 2006 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : a thread terminator convenience class + + +// local includes: +#include "IRTerminator.h" +#include "IRException.h" + +// system includes: +#include +#include +#include +#include + +// namespace access: +using std::cerr; +using std::endl; + + +//---------------------------------------------------------------- +// DEBUG_TERMINATORS +// +// #define DEBUG_TERMINATORS + + +//---------------------------------------------------------------- +// the_terminator_t::the_terminator_t +// +the_terminator_t::the_terminator_t(const char * id): + id_(id), + termination_requested_(should_terminate_immediately()) +{ + terminate_on_request(); + add(this); +} + +//---------------------------------------------------------------- +// the_terminator_t:: +// +the_terminator_t::~the_terminator_t() +{ + del(this); +} + +//---------------------------------------------------------------- +// the_terminator_t::terminate +// +void +the_terminator_t::terminate() +{ +#ifdef DEBUG_TERMINATORS + cerr << "TERMINATE: " << id_ << ", this: " << this << endl; +#endif // DEBUG_TERMINATORS + termination_requested_ = true; +} + +//---------------------------------------------------------------- +// the_terminator_t::throw_exception +// +void +the_terminator_t::throw_exception() const +{ + // abort, termination requested: + std::ostringstream os; + os << id_ << " interrupted" << endl; + + the_exception_t e(os.str().c_str()); + throw e; +} + +//---------------------------------------------------------------- +// the_terminator_t::verify_termination +// +bool +the_terminator_t::verify_termination() +{ + return the_thread_storage().terminators().verify_termination(); +} + +//---------------------------------------------------------------- +// the_terminator_t::should_terminate_immediately +// +bool +the_terminator_t::should_terminate_immediately() +{ + return the_thread_storage().thread_stopped(); +} + +//---------------------------------------------------------------- +// the_terminator_t::add +// +void +the_terminator_t::add(the_terminator_t * terminator) +{ + the_thread_storage().terminators().add(terminator); +} + +//---------------------------------------------------------------- +// the_terminator_t::del +// +void +the_terminator_t::del(the_terminator_t * terminator) +{ + the_thread_storage().terminators().del(terminator); +} + + +//---------------------------------------------------------------- +// the_terminators_t::~the_terminators_t +// +the_terminators_t::~the_terminators_t() +{ + for (std::list::iterator i = terminators_.begin(); + i != terminators_.end(); ++i) + { + the_terminator_t * t = *i; + delete t; + } + + terminators_.clear(); +} + +//---------------------------------------------------------------- +// the_terminators_t::terminate +// +void +the_terminators_t::terminate() +{ + the_lock_t lock(*this); + +#ifdef DEBUG_TERMINATORS + cerr << endl << &terminators_ << ": terminate_all -- begin" << endl; +#endif // DEBUG_TERMINATORS + + for (std::list::iterator i = terminators_.begin(); + i != terminators_.end(); ++i) + { + the_terminator_t * t = *i; + t->terminate(); + } + +#ifdef DEBUG_TERMINATORS + cerr << &terminators_ << ": terminate_all -- end" << endl; +#endif // DEBUG_TERMINATORS +} + +//---------------------------------------------------------------- +// the_terminators_t::verify_termination +// +bool +the_terminators_t::verify_termination() +{ + the_lock_t lock(*this); + +#ifdef DEBUG_TERMINATORS + for (std::list::iterator iter = terminators_.begin(); + iter != terminators_.end(); ++iter) + { + the_terminator_t * t = *iter; + cerr << "ERROR: remaining terminators: " << t->id() << endl; + } +#endif + + return terminators_.empty(); +} + +//---------------------------------------------------------------- +// the_terminators_t::add +// +void +the_terminators_t::add(the_terminator_t * terminator) +{ + the_lock_t lock(*this); + terminators_.push_front(terminator); + +#ifdef DEBUG_TERMINATORS + cerr << &terminators_ << ": appended " << terminator->id() + << " terminator, addr " << terminator << endl; +#endif // DEBUG_TERMINATORS +} + +//---------------------------------------------------------------- +// the_terminators_t::del +// +void +the_terminators_t::del(the_terminator_t * terminator) +{ + the_lock_t lock(*this); + + std::list::iterator iter = + std::find(terminators_.begin(), terminators_.end(), terminator); + + if (iter == terminators_.end()) + { + cerr << "ERROR: no such terminator: " << terminator->id() << endl; + assert(0); + return; + } + + terminators_.erase(iter); + +#ifdef DEBUG_TERMINATORS + cerr << &terminators_ << ": removed " << terminator->id() + << " terminator, addr " << terminator << endl; +#endif // DEBUG_TERMINATORS +} diff --git a/src/IRThreadInterface.cxx b/src/IRThreadInterface.cxx new file mode 100644 index 0000000..3d0a698 --- /dev/null +++ b/src/IRThreadInterface.cxx @@ -0,0 +1,510 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_thread_interface.cxx +// Author : Pavel A. Koshevoy +// Created : Fri Feb 16 10:11:00 MST 2007 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : An abstract thread class interface. + +// local includes: +#include "IRThreadInterface.h" +#include "IRThreadPool.h" +#include "IRMutexInterface.h" +#include "IRTerminator.h" +#include "IRTransaction.h" +#include "itkIRUtils.h" + +// system includes: +#include + +// namespace access: +using std::cout; +using std::cerr; +using std::endl; + +//---------------------------------------------------------------- +// DEBUG_THREAD +// +// #define DEBUG_THREAD + + +//---------------------------------------------------------------- +// MUTEX +// +static the_mutex_interface_t * MUTEX() +{ + static the_mutex_interface_t * mutex_ = NULL; + if (mutex_ == NULL) + { + mutex_ = the_mutex_interface_t::create(); + } + + return mutex_; +} + + +//---------------------------------------------------------------- +// the_thread_interface_t::creator_ +// +the_thread_interface_t::creator_t +the_thread_interface_t::creator_ = NULL; + +//---------------------------------------------------------------- +// the_thread_interface_t::the_thread_interface_t +// +the_thread_interface_t::the_thread_interface_t(the_mutex_interface_t * mutex): + mutex_(mutex), + stopped_(true), + sleep_when_idle_(false), + sleep_microsec_(10000), + active_transaction_(NULL), + thread_pool_(NULL), + thread_pool_cb_data_(NULL) +{ + the_lock_t locker(MUTEX()); + static unsigned int counter = 0; + id_ = counter++; +} + +//---------------------------------------------------------------- +// the_thread_interface_t::~the_thread_interface_t +// +the_thread_interface_t::~the_thread_interface_t() +{ + mutex_->delete_this(); +} + +//---------------------------------------------------------------- +// the_thread_interface_t::set_creator +// +void +the_thread_interface_t::set_creator(the_thread_interface_t::creator_t creator) +{ + creator_ = creator; +} + +//---------------------------------------------------------------- +// the_thread_interface_t::create +// +the_thread_interface_t * +the_thread_interface_t::create() +{ + if (creator_ == NULL) return NULL; + return creator_(); +} + +//---------------------------------------------------------------- +// the_thread_interface_t::set_mutex +// +void +the_thread_interface_t::set_mutex(the_mutex_interface_t * mutex) +{ + mutex_->delete_this(); + mutex_ = mutex; +} + +//---------------------------------------------------------------- +// the_thread_interface_t::set_idle_sleep_duration +// +void +the_thread_interface_t::set_idle_sleep_duration(bool enable, + unsigned int microseconds) +{ + sleep_when_idle_ = enable; + sleep_microsec_ = microseconds; +} + +//---------------------------------------------------------------- +// the_thread_interface_t::push_back +// +void +the_thread_interface_t::push_back(the_transaction_t * transaction) +{ + the_lock_t locker(mutex_); + transactions_.push_back(transaction); +} + +//---------------------------------------------------------------- +// the_thread_interface_t::push_front +// +void +the_thread_interface_t::push_front(the_transaction_t * transaction) +{ + the_lock_t locker(mutex_); + transactions_.push_front(transaction); +} + +//---------------------------------------------------------------- +// the_thread_interface_t::push_back +// +void +the_thread_interface_t::push_back(std::list & schedule) +{ + the_lock_t locker(mutex_); + transactions_.splice(transactions_.end(), schedule); +} + +//---------------------------------------------------------------- +// the_thread_interface_t::has_work +// +bool +the_thread_interface_t::has_work() const +{ + return (active_transaction_ != NULL) || !transactions_.empty(); +} + +//---------------------------------------------------------------- +// the_thread_interface_t::start +// +void +the_thread_interface_t::start(the_transaction_t * transaction) +{ + push_back(transaction); + start(); +} + +//---------------------------------------------------------------- +// the_thread_interface_t::stop +// +void +the_thread_interface_t::stop() +{ + the_lock_t locker(mutex_); + if (!stopped_) + { +#ifdef DEBUG_THREAD + cerr << "stopping thread: " << this << endl; +#endif + stopped_ = true; + terminators().terminate(); + } +} + +//---------------------------------------------------------------- +// the_thread_interface_t::flush +// +void +the_thread_interface_t::flush() +{ + the_lock_t locker(mutex_); + while (!transactions_.empty()) + { + the_transaction_t * t = remove_head(transactions_); +#ifdef DEBUG_THREAD + cerr << this << " flush " << t << endl; +#endif + t->notify(this, the_transaction_t::SKIPPED_E); + } +} + +//---------------------------------------------------------------- +// the_thread_interface_t::terminate_transactions +// +void +the_thread_interface_t::terminate_transactions() +{ + the_lock_t locker(mutex_); + + // remove any further pending transactions: + while (!transactions_.empty()) + { + the_transaction_t * t = remove_head(transactions_); +#ifdef DEBUG_THREAD + cerr << this << " terminate_transactions " << t << endl; +#endif + t->notify(this, the_transaction_t::SKIPPED_E); + } + + terminators().terminate(); +} + +//---------------------------------------------------------------- +// the_thread_interface_t::stop_and_go +// +void +the_thread_interface_t::stop_and_go(the_transaction_t * transaction) +{ + the_lock_t locker(mutex_); + + // remove any further pending transactions: + while (!transactions_.empty()) + { + the_transaction_t * t = remove_head(transactions_); +#ifdef DEBUG_THREAD + cerr << this << " stop_and_go (1) " << t << endl; +#endif + t->notify(this, the_transaction_t::SKIPPED_E); + } + + // terminate the currently executing transaction: + terminators().terminate(); + + // schedule the next transaction: + transactions_.push_back(transaction); + + // start the thread if it isn't already running: + start(); +} + +//---------------------------------------------------------------- +// the_thread_interface_t::stop_and_go +// +void +the_thread_interface_t::stop_and_go(std::list & schedule) +{ + the_lock_t locker(mutex_); + + // remove any further pending transactions: + while (!transactions_.empty()) + { + the_transaction_t * t = remove_head(transactions_); +#ifdef DEBUG_THREAD + cerr << this << " stop_and_go " << t << endl; +#endif + t->notify(this, the_transaction_t::SKIPPED_E); + } + + // terminate the currently executing transaction: + terminators().terminate(); + + // schedule the new transactions: + transactions_.splice(transactions_.end(), schedule); + + // start the thread if it isn't already running: + start(); +} + +//---------------------------------------------------------------- +// the_thread_interface_t::flush_and_go +// +void +the_thread_interface_t::flush_and_go(the_transaction_t * transaction) +{ + the_lock_t locker(mutex_); + + // remove any further pending transactions: + while (!transactions_.empty()) + { + the_transaction_t * t = remove_head(transactions_); +#ifdef DEBUG_THREAD + cerr << this << " flush_and_go (1) " << t << endl; +#endif + t->notify(this, the_transaction_t::SKIPPED_E); + } + + // schedule the next transaction: + transactions_.push_back(transaction); + + // start the thread if it isn't already running: + start(); +} + +//---------------------------------------------------------------- +// the_thread_interface_t::flush_and_go +// +void +the_thread_interface_t:: +flush_and_go(std::list & schedule) +{ + the_lock_t locker(mutex_); + + // remove any further pending transactions: + while (!transactions_.empty()) + { + the_transaction_t * t = remove_head(transactions_); +#ifdef DEBUG_THREAD + cerr << this << " flush_and_go " << t << endl; +#endif + t->notify(this, the_transaction_t::SKIPPED_E); + } + + // schedule the next transaction: + transactions_.splice(transactions_.end(), schedule); + + // start the thread if it isn't already running: + start(); +} + +//---------------------------------------------------------------- +// the_thread_interface_t::work +// +bool +the_thread_interface_t::work() +{ + the_lock_t lock_this(mutex_, false); + the_lock_t lock_pool(thread_pool_ ? + thread_pool_->mutex_ : NULL, + false); + bool all_transactions_completed = true; + + while (!stopped_) + { + // get the next transaction: + the_transaction_t * t = NULL; + { + lock_pool.arm(); + lock_this.arm(); + + if (thread_pool_ != NULL) + { + // call back the thread pool: +#ifdef DEBUG_THREAD + cerr << "thread " << this << " calling the pool" << endl; +#endif + thread_pool_->handle_thread(thread_pool_cb_data_); + } + + if (transactions_.empty()) + { + if (sleep_when_idle_ && !stopped_) + { + lock_this.disarm(); + lock_pool.disarm(); + take_a_nap(sleep_microsec_); +#ifdef DEBUG_THREAD + cerr << this << " sleeping" << endl; +#endif + continue; + } + else + { + // NOTE: the mutex will remain locked until the function returns: +#ifdef DEBUG_THREAD + cerr << "thread " << this << " is finishing up" << endl; +#endif + break; + } + } + else + { + t = remove_head(transactions_); +#ifdef DEBUG_THREAD + cerr << "thread " << this << " received " << t << endl; +#endif + lock_this.disarm(); + lock_pool.disarm(); + } + } + + try + { + active_transaction_ = t; + t->notify(this, the_transaction_t::STARTED_E); + t->execute(this); + all_transactions_completed = (all_transactions_completed && true); + active_transaction_ = NULL; + t->notify(this, the_transaction_t::DONE_E); + } + catch (std::exception & e) + { + active_transaction_ = NULL; +#ifdef DEBUG_THREAD + cerr << "FIXME: caught exception: " << e.what() << endl; +#endif + t->notify(this, + the_transaction_t::ABORTED_E, + e.what()); + } + catch (...) + { + active_transaction_ = NULL; +#ifdef DEBUG_THREAD + cerr << "FIXME: caught unknonwn exception" << endl; +#endif + t->notify(this, + the_transaction_t::ABORTED_E, + "unknown exception intercepted"); + } + } + + stopped_ = true; + + // make sure that all terminators have executed: +#ifndef NDEBUG + bool ok = the_terminator_t::verify_termination(); + assert(ok); +#endif + + all_transactions_completed = (all_transactions_completed && + transactions_.empty()); + + // abort pending transaction: + while (!transactions_.empty()) + { + the_transaction_t * t = remove_head(transactions_); +#ifdef DEBUG_THREAD + cerr << this << " work " << t << endl; +#endif + t->notify(this, the_transaction_t::SKIPPED_E); + } + +#ifdef DEBUG_THREAD + cerr << "thread " << this << " is finished" << endl; +#endif + + if (thread_pool_ != NULL) + { + lock_pool.arm(); + lock_this.arm(); + thread_pool_->handle_thread(thread_pool_cb_data_); + } + + return all_transactions_completed; +} + +//---------------------------------------------------------------- +// the_thread_interface_t::handle +// +void +the_thread_interface_t::handle(the_transaction_t * transaction, + the_transaction_t::state_t s) +{ + switch (s) + { + case the_transaction_t::SKIPPED_E: + case the_transaction_t::ABORTED_E: + case the_transaction_t::DONE_E: + delete transaction; + break; + + default: + break; + } +} + +//---------------------------------------------------------------- +// the_thread_interface_t::blab +// +void +the_thread_interface_t::blab(const char * message) const +{ + if (thread_pool_ == NULL) + { + cerr << message << endl; + } + else + { + thread_pool_->blab(message); + } +} + diff --git a/src/IRThreadPool.cxx b/src/IRThreadPool.cxx new file mode 100644 index 0000000..e45c25a --- /dev/null +++ b/src/IRThreadPool.cxx @@ -0,0 +1,735 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_thread_pool.cxx +// Author : Pavel A. Koshevoy +// Created : Wed Feb 21 09:01:00 MST 2007 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : A thread pool class. + +// local includes: +#include "IRThreadPool.h" +#include "IRTransaction.h" +#include "IRMutexInterface.h" +#include "itkIRUtils.h" + +// system includes: +#include + +// namespace access: +using std::cout; +using std::cerr; +using std::endl; + +//---------------------------------------------------------------- +// DEBUG_THREAD +// +// #define DEBUG_THREAD + + +//---------------------------------------------------------------- +// the_transaction_wrapper_t::the_transaction_wrapper_t +// +the_transaction_wrapper_t:: +the_transaction_wrapper_t(const unsigned int & num_parts, + the_transaction_t * transaction): + mutex_(the_mutex_interface_t::create()), + cb_(transaction->notify_cb_), + cb_data_(transaction->notify_cb_data_), + num_parts_(num_parts) +{ + assert(mutex_ != NULL); + + for (unsigned int i = 0; i <= the_transaction_t::DONE_E; i++) + { + notified_[i] = 0; + } + transaction->set_notify_cb(notify_cb, this); +} + +//---------------------------------------------------------------- +// the_transaction_wrapper_t::~the_transaction_wrapper_t +// +the_transaction_wrapper_t::~the_transaction_wrapper_t() +{ + mutex_->delete_this(); + mutex_ = NULL; +} + +//---------------------------------------------------------------- +// the_transaction_wrapper_t::notify_cb +// +void +the_transaction_wrapper_t::notify_cb(void * data, + the_transaction_t * t, + the_transaction_t::state_t s) +{ + the_transaction_wrapper_t * wrapper = (the_transaction_wrapper_t *)(data); + bool done = wrapper->notify(t, s); + if (done) + { + delete wrapper; + } +} + +//---------------------------------------------------------------- +// the_transaction_wrapper_t::notify +// +bool +the_transaction_wrapper_t::notify(the_transaction_t * t, + the_transaction_t::state_t s) +{ + the_lock_t locker(mutex_); + notified_[s]++; + + unsigned int num_terminal_notifications = + notified_[the_transaction_t::SKIPPED_E] + + notified_[the_transaction_t::ABORTED_E] + + notified_[the_transaction_t::DONE_E]; + + switch (s) + { + case the_transaction_t::PENDING_E: + case the_transaction_t::STARTED_E: + if (notified_[s] == 1 && num_terminal_notifications == 0 && cb_ != NULL) + { + cb_(cb_data_, t, s); + } + return false; + + case the_transaction_t::SKIPPED_E: + case the_transaction_t::ABORTED_E: + case the_transaction_t::DONE_E: + { + if (num_terminal_notifications == num_parts_) + { + // restore the transaction callback: + t->set_notify_cb(cb_, cb_data_); + + // if necessary, consolidate the transaction state into one: + if (num_terminal_notifications != + notified_[the_transaction_t::DONE_E]) + { + the_transaction_t::state_t state = + notified_[the_transaction_t::ABORTED_E] > 0 ? + the_transaction_t::ABORTED_E : + the_transaction_t::SKIPPED_E; + t->set_state(state); + } + + if (cb_ != NULL) + { + cb_(cb_data_, t, t->state()); + } + else + { + delete t; + } + + return true; + } + return false; + } + + default: + assert(0); + } + + return false; +} + + +//---------------------------------------------------------------- +// the_thread_pool_t::the_thread_pool_t +// +the_thread_pool_t::the_thread_pool_t(unsigned int num_threads): + pool_size_(num_threads) +{ + mutex_ = the_mutex_interface_t::create(); + assert(mutex_ != NULL); + + pool_ = new the_thread_pool_data_t[num_threads]; + for (unsigned int i = 0; i < num_threads; i++) + { + pool_[i].id_ = i; + pool_[i].parent_ = this; + pool_[i].thread_ = the_thread_interface_t::create(); + assert(pool_[i].thread_ != NULL); + pool_[i].thread_->set_thread_pool_cb(this, &pool_[i]); + + // mark the thread as idle, initially: + idle_.push_back(i); + } +} + +//---------------------------------------------------------------- +// the_thread_pool_t::~the_thread_pool_t +// +the_thread_pool_t::~the_thread_pool_t() +{ + mutex_->delete_this(); + mutex_ = NULL; + + delete [] pool_; + pool_ = NULL; + pool_size_ = 0; +} + +//---------------------------------------------------------------- +// the_thread_pool_t::start +// +void +the_thread_pool_t::start() +{ + if (!transactions_.empty()) + { + while (true) + { + the_lock_t locker(mutex_); + if (transactions_.empty()) return; + + if (idle_.empty()) + { +#ifdef DEBUG_THREAD + // sanity test: + for (unsigned int i = 0; i < pool_size_; i++) + { + the_lock_t locker(pool_[i].thread_->mutex()); + if (!pool_[i].thread_->has_work()) + { + cerr << "WARNING: thread " << i << ", " << pool_[i].thread_ + << " is actually idle" << endl; + } + } +#endif + return; + } + + // get the next worker thread: + unsigned int id = remove_head(idle_); + busy_.push_back(id); + + // get the next transaction: + the_transaction_t * t = remove_head(transactions_); + + // start the thread: + thread(id)->start(t); + +#ifdef DEBUG_THREAD + cerr << "starting thread " << id << ", " << thread(id) << ", " << t + << endl; + + for (std::list::const_iterator i = idle_.begin(); + i != idle_.end(); ++i) + { + cerr << "idle: thread " << *i << ", " << thread(*i) << endl; + } + + for (std::list::const_iterator i = busy_.begin(); + i != busy_.end(); ++i) + { + cerr << "busy: thread " << *i << ", " << thread(*i) << endl; + } +#endif + } + } + else + { + // Transaction list is empty, perhaps they've already + // been distributed to the threads? Just start the threads + // and let them figure it out for themselves. + for (unsigned int i = 0; i < pool_size_; i++) + { + pool_[i].thread_->start(); + } + } +} + +//---------------------------------------------------------------- +// the_thread_pool_t::set_idle_sleep_duration +// +void +the_thread_pool_t::set_idle_sleep_duration(bool enable, unsigned int microsec) +{ + the_lock_t locker(mutex_); + for (unsigned int i = 0; i < pool_size_; i++) + { + pool_[i].thread_->set_idle_sleep_duration(enable, microsec); + } +} + +//---------------------------------------------------------------- +// notify_cb +// +static void +notify_cb(void * data, + the_transaction_t * transaction, + the_transaction_t::state_t s) +{ + the_thread_pool_t * pool = (the_thread_pool_t *)(data); + pool->handle(transaction, s); +} + +//---------------------------------------------------------------- +// status_cb +// +static void +status_cb(void * data, the_transaction_t *, const char * text) +{ + the_thread_pool_t * pool = (the_thread_pool_t *)(data); + pool->blab(text); +} + +//---------------------------------------------------------------- +// wrap +// +static void +wrap(the_transaction_t * transaction, + the_thread_pool_t * pool, + bool multithreaded) +{ + if (transaction->notify_cb() == NULL) + { + // override the callback: + transaction->set_notify_cb(::notify_cb, pool); + } + + if (transaction->status_cb() == NULL) + { + // override the callback: + transaction->set_status_cb(::status_cb, pool); + } + + if (multithreaded) + { + // silence the compiler warning: + // the_transaction_wrapper_t * wrapper = + new the_transaction_wrapper_t(pool->pool_size(), transaction); + } +} + +//---------------------------------------------------------------- +// the_thread_pool_t::push_front +// +void +the_thread_pool_t::push_front(the_transaction_t * transaction, + bool multithreaded) +{ + the_lock_t locker(mutex_); + no_lock_push_front(transaction, multithreaded); +} + +//---------------------------------------------------------------- +// the_thread_pool_t::push_back +// +void +the_thread_pool_t::push_back(the_transaction_t * transaction, + bool multithreaded) +{ + the_lock_t locker(mutex_); + no_lock_push_back(transaction, multithreaded); +} + +//---------------------------------------------------------------- +// the_thread_pool_t::push_back +// +void +the_thread_pool_t::push_back(std::list & schedule, + bool multithreaded) +{ + the_lock_t locker(mutex_); + no_lock_push_back(schedule, multithreaded); +} + +//---------------------------------------------------------------- +// the_thread_pool_t::pre_distribute_work +// +void +the_thread_pool_t::pre_distribute_work() +{ + the_lock_t locker(mutex_); + std::vector > split_schedule_(pool_size_); + + while (!transactions_.empty()) + { + for (unsigned int i = 0; i < pool_size_ && !transactions_.empty(); i++) + { + the_transaction_t * job = remove_head(transactions_); + split_schedule_[i].push_back(job); + } + } + + for (unsigned int i = 0; i < pool_size_; i++) + { + pool_[i].thread_->push_back(split_schedule_[i]); + } +} + +//---------------------------------------------------------------- +// the_thread_pool_t::has_work +// +bool +the_thread_pool_t::has_work() const +{ + the_lock_t locker(mutex_); + if (!transactions_.empty()) return true; + + // make sure the busy threads have work: + for (std::list::const_iterator + iter = busy_.begin(); iter != busy_.end(); ++iter) + { + unsigned int i = *iter; + if (pool_[i].thread_->has_work()) return true; + } + + return false; +} + +//---------------------------------------------------------------- +// the_thread_pool_t::start +// +void +the_thread_pool_t::start(the_transaction_t * transaction, bool multithreaded) +{ + push_back(transaction, multithreaded); + start(); +} + +//---------------------------------------------------------------- +// the_thread_pool_t::stop +// +void +the_thread_pool_t::stop() +{ + // remove any pending transactions: + flush(); + + the_lock_t locker(mutex_); + for (unsigned int i = 0; i < pool_size_; i++) + { + pool_[i].thread_->stop(); + } +} + +//---------------------------------------------------------------- +// the_thread_pool_t::wait +// +void +the_thread_pool_t::wait() +{ + // the_lock_t locker(mutex_); + for (unsigned int i = 0; i < pool_size_; i++) + { + pool_[i].thread_->wait(); + } +} + +//---------------------------------------------------------------- +// the_thread_pool_t::flush +// +void +the_thread_pool_t::flush() +{ + the_lock_t locker(mutex_); + no_lock_flush(); +} + +//---------------------------------------------------------------- +// the_thread_pool_t::terminate_transactions +// +void +the_thread_pool_t::terminate_transactions() +{ + the_lock_t locker(mutex_); + no_lock_terminate_transactions(); +} + +//---------------------------------------------------------------- +// the_thread_pool_t::stop_and_go +// +void +the_thread_pool_t::stop_and_go(the_transaction_t * transaction, + bool multithreaded) +{ + // safely manipulate the transactions: + { + the_lock_t locker(mutex_); + no_lock_terminate_transactions(); + no_lock_push_back(transaction, multithreaded); + } + + start(); +} + +//---------------------------------------------------------------- +// the_thread_pool_t::stop_and_go +// +void +the_thread_pool_t::stop_and_go(std::list & schedule, + bool multithreaded) +{ + // safely manipulate the transactions: + { + the_lock_t locker(mutex_); + no_lock_terminate_transactions(); + no_lock_push_back(schedule, multithreaded); + } + + start(); +} + +//---------------------------------------------------------------- +// the_thread_pool_t::flush_and_go +// +void +the_thread_pool_t::flush_and_go(the_transaction_t * transaction, + bool multithreaded) +{ + // safely manipulate the transactions: + { + the_lock_t locker(mutex_); + no_lock_flush(); + no_lock_push_back(transaction, multithreaded); + } + + start(); +} + +//---------------------------------------------------------------- +// the_thread_pool_t::flush_and_go +// +void +the_thread_pool_t::flush_and_go(std::list & schedule, + bool multithreaded) +{ + // safely manipulate the transactions: + { + the_lock_t locker(mutex_); + no_lock_flush(); + no_lock_push_back(schedule, multithreaded); + } + + start(); +} + +//---------------------------------------------------------------- +// the_thread_pool_t::blab +// +void +the_thread_pool_t::handle(the_transaction_t * transaction, + the_transaction_t::state_t s) +{ + switch (s) + { + case the_transaction_t::SKIPPED_E: + case the_transaction_t::ABORTED_E: + case the_transaction_t::DONE_E: + delete transaction; + break; + + default: + break; + } +} + +//---------------------------------------------------------------- +// the_thread_pool_t::handle +// +void +the_thread_pool_t::blab(const char * message) const +{ + cerr << message << endl; +} + +//---------------------------------------------------------------- +// the_thread_pool_t::handle_thread +// +// prior to calling this function the thread interface locks +// the pool mutex and it's own mutex -- don't try locking these +// again while inside this callback, and don't call any other +// pool or thread member functions which lock either of these +// because that would result in a deadlock +// +void +the_thread_pool_t::handle_thread(the_thread_pool_data_t * data) +{ + const unsigned int & id = data->id_; + the_thread_interface_t * t = thread(id); + + if (t->stopped_) + { +#ifdef DEBUG_THREAD + cerr << "thread has stopped: " << t << endl; +#endif + + if (!has(idle_, id)) + { + idle_.push_back(id); + } + + if (has(busy_, id)) + { + busy_.remove(id); + } + + return; + } + + if (t->has_work()) + { +#ifdef DEBUG_THREAD + cerr << "thread still has work: " << t << endl; +#endif + return; + } + + if (transactions_.empty()) + { + // tell the thread to stop: + t->stopped_ = true; + + if (!has(idle_, id)) + { + idle_.push_back(id); + } + + if (has(busy_, id)) + { + busy_.remove(id); + } + +#ifdef DEBUG_THREAD + cerr << "no more work for thread: " << t << endl; + + for (std::list::const_iterator i = idle_.begin(); + i != idle_.end(); ++i) + { + cerr << "idle: thread " << *i << ", " << thread(*i) << endl; + } + + for (std::list::const_iterator i = busy_.begin(); + i != busy_.end(); ++i) + { + cerr << "busy: thread " << *i << ", " << thread(*i) << endl; + } +#endif + + return; + } + + // execute another transaction: + the_transaction_t * transaction = remove_head(transactions_); +#ifdef DEBUG_THREAD + cerr << "giving " << transaction << " to thread " << t << endl; +#endif + + t->transactions_.push_back(transaction); +} + +//---------------------------------------------------------------- +// the_thread_pool_t::no_lock_flush +// +void +the_thread_pool_t::no_lock_flush() +{ + while (!transactions_.empty()) + { + the_transaction_t * t = remove_head(transactions_); + t->notify(this, the_transaction_t::SKIPPED_E); + } +} + +//---------------------------------------------------------------- +// the_thread_pool_t::no_lock_terminate_transactions +// +void +the_thread_pool_t::no_lock_terminate_transactions() +{ + // remove any pending transactions: + no_lock_flush(); + + for (unsigned int i = 0; i < pool_size_; i++) + { + pool_[i].thread_->terminate_transactions(); + } +} + +//---------------------------------------------------------------- +// the_thread_pool_t::no_lock_push_front +// +void +the_thread_pool_t::no_lock_push_front(the_transaction_t * transaction, + bool multithreaded) +{ + wrap(transaction, this, multithreaded); + transactions_.push_front(transaction); + for (unsigned int i = 1; multithreaded && i < pool_size_; i++) + { + transactions_.push_front(transaction); + } +} + +//---------------------------------------------------------------- +// the_thread_pool_t::no_lock_push_back +// +void +the_thread_pool_t::no_lock_push_back(the_transaction_t * transaction, + bool multithreaded) +{ + wrap(transaction, this, multithreaded); + transactions_.push_back(transaction); + for (unsigned int i = 1; multithreaded && i < pool_size_; i++) + { + transactions_.push_back(transaction); + } +} + +//---------------------------------------------------------------- +// the_thread_pool_t::no_lock_push_back +// +void +the_thread_pool_t::no_lock_push_back(std::list & schedule, + bool multithreaded) +{ + // preprocess the transactions: + for (std::list::iterator i = schedule.begin(); + i != schedule.end(); i++) + { + wrap(*i, this, multithreaded); + } + + if (multithreaded) + { + while (!schedule.empty()) + { + the_transaction_t * t = remove_head(schedule); + for (unsigned int i = 0; i < pool_size_; i++) + { + transactions_.push_back(t); + } + } + } + else + { + transactions_.splice(transactions_.end(), schedule); + } +} diff --git a/src/IRThreadStorage.cxx b/src/IRThreadStorage.cxx new file mode 100644 index 0000000..3142631 --- /dev/null +++ b/src/IRThreadStorage.cxx @@ -0,0 +1,102 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_thread_storage.cxx +// Author : Pavel A. Koshevoy +// Created : Fri Jan 9 12:27:00 MDT 2007 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : A thread storage abstract interface class + +// local includes: +#include +#include + + +//---------------------------------------------------------------- +// the_dummy_terminators_t +// +class the_dummy_terminators_t : public the_terminators_t +{ +public: + // virtual: + void lock() {} + void unlock() {} +}; + +//---------------------------------------------------------------- +// the_dummy_thread_storage_t +// +class the_dummy_thread_storage_t : public the_thread_storage_t +{ +public: + // virtual: + bool is_ready() const + { return true; } + + bool thread_stopped() const + { return false; } + + the_terminators_t & terminators() + { return terminators_; } + + unsigned int thread_id() const + { return ~0u; } + +private: + the_dummy_terminators_t terminators_; +}; + +//---------------------------------------------------------------- +// the_dummy_thread_storage +// +static the_thread_storage_t & +the_dummy_thread_storage() +{ + static the_dummy_thread_storage_t thread_storage; + return thread_storage; +} + +//---------------------------------------------------------------- +// thread_storage_provider_ +// +static the_thread_storage_provider_t +thread_storage_provider_ = the_dummy_thread_storage; + +//---------------------------------------------------------------- +// set_the_thread_storage_provider +// +the_thread_storage_provider_t +set_the_thread_storage_provider(the_thread_storage_provider_t p) +{ + the_thread_storage_provider_t old = thread_storage_provider_; + thread_storage_provider_ = p; + return old; +} + +//---------------------------------------------------------------- +// the_thread_storage +// +the_thread_storage_t & +the_thread_storage() +{ + return thread_storage_provider_(); +} diff --git a/src/IRTransaction.cxx b/src/IRTransaction.cxx new file mode 100644 index 0000000..4484874 --- /dev/null +++ b/src/IRTransaction.cxx @@ -0,0 +1,183 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_transaction.cxx +// Author : Pavel A. Koshevoy +// Created : Fri Feb 16 09:52:00 MST 2007 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : A thread transaction class. + +// system includes: +#include +#include + +// local includes: +#include "IRTransaction.h" +#include "IRThreadInterface.h" +#include "IRMutexInterface.h" + + +//---------------------------------------------------------------- +// the_transaction_t::the_transaction_t +// +the_transaction_t::the_transaction_t(): + mutex_(the_mutex_interface_t::create()), + request_(NOTHING_E), + state_(PENDING_E), + notify_cb_(NULL), + notify_cb_data_(NULL), + status_cb_(NULL), + status_cb_data_(NULL) +{} + +//---------------------------------------------------------------- +// the_transaction_t::~the_transaction_t +// +the_transaction_t::~the_transaction_t() +{ + if (mutex_ != NULL) + { + mutex_->delete_this(); + mutex_ = NULL; + } +} + +//---------------------------------------------------------------- +// the_transaction_t::notify +// +void +the_transaction_t::notify(the_transaction_handler_t * handler, + state_t s, + const char * message) +{ + set_state(s); + blab(handler, message); + + if (notify_cb_ == NULL) + { + handler->handle(this, s); + } + else + { + notify_cb_(notify_cb_data_, this, s); + } +} + +//---------------------------------------------------------------- +// the_transaction_t::blab +// +void +the_transaction_t::blab(the_transaction_handler_t * handler, + const char * message) +{ + if (message == NULL) return; + + if (status_cb_ == NULL) + { + handler->blab(message); + } + else + { + status_cb_(status_cb_data_, this, message); + } +} + +//---------------------------------------------------------------- +// the_transaction_t::callback_request +// +bool +the_transaction_t::callback_request() +{ + if (status_cb_ == NULL) + { + return false; + } + + // change the request state: + { + the_lock_t locker(mutex_); + request_ = WAITING_E; + } + + // execute the status callback: + status_cb_(status_cb_data_, this, NULL); + + while (true) + { + sleep_msec(100); + + // check the request state: + the_lock_t locker(mutex_); + if (request_ == NOTHING_E) + { + break; + } + } + + return true; +} + +//---------------------------------------------------------------- +// the_transaction_t::callback +// +void +the_transaction_t::callback() +{ + // remove the callback request: + the_lock_t locker(mutex_); + request_ = NOTHING_E; +} + +//---------------------------------------------------------------- +// operator << +// +std::ostream & +operator << (std::ostream & so, const the_transaction_t::state_t & state) +{ + switch (state) + { + case the_transaction_t::PENDING_E: + so << "pending"; + return so; + + case the_transaction_t::SKIPPED_E: + so << "skipped"; + return so; + + case the_transaction_t::STARTED_E: + so << "started"; + return so; + + case the_transaction_t::ABORTED_E: + so << "aborted"; + return so; + + case the_transaction_t::DONE_E: + so << "done"; + return so; + + default: + so << int(state); + assert(0); + return so; + } +} diff --git a/src/itkIRCommon.cxx b/src/itkIRCommon.cxx new file mode 100644 index 0000000..5221b68 --- /dev/null +++ b/src/itkIRCommon.cxx @@ -0,0 +1,1599 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : common.cxx +// Author : Pavel A. Koshevoy +// Created : 2005/03/24 16:53 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : Helper functions for mosaicing, image warping, +// image preprocessing, convenience wrappers for +// ITK file and ITK filters. + +// ITK includes: +#include +#include +#include + +// local includes: +#include "itkIRCommon.h" +#include "itkGridTransform.h" +#include "itkMeshTransform.h" +#include "itkRBFTransform.h" +#include "itkRadialDistortionTransform.h" +#include "itkNormalizeImageFilterWithMask.h" + +// system includes: +#include + +// namespace access: +using std::endl; +using std::flush; +using std::cerr; +using std::ios; +using std::setw; + +#if defined(__APPLE__) +# include // hack necessary to get omp to link even when there aren't any omp pragmas around. Osx-only. See http://stackoverflow.com/questions/8983038/linker-errors-after-enabling-openmp-on-mac. +pthread_attr_t gomp_thread_attr; +#endif + +//---------------------------------------------------------------- +// BREAK +// +int +BREAK(unsigned int i) +{ + printf("\n\n\nBREAK BREAK BREAK BREAK BREAK BREAK BREAK: %i\n\n\n\n", i); + return 0; +} + +//---------------------------------------------------------------- +// register_transforms +// +static void +register_transforms() +{ + // try to avoid re-registering the same transforms over and over again: + static bool transforms_registered = false; + if (transforms_registered) + return; + + // make sure the transforms I care about are known to the object factory: + itk::TransformFactoryBase::RegisterDefaultTransforms(); + + itk::TransformFactory>::RegisterTransform(); + + itk::TransformFactory>::RegisterTransform(); + + itk::TransformFactory>::RegisterTransform(); + + itk::TransformFactory>::RegisterTransform(); + + itk::TransformFactory>::RegisterTransform(); + + itk::TransformFactory>::RegisterTransform(); + + itk::TransformFactory>::RegisterTransform(); + + itk::TransformFactory>::RegisterTransform(); + + itk::TransformFactory::RegisterTransform(); + + itk::TransformFactory::RegisterTransform(); + + itk::TransformFactory::RegisterTransform(); + + itk::TransformFactory>::RegisterTransform(); + + transforms_registered = true; +} + +//---------------------------------------------------------------- +// save_transform +// +void +save_transform(std::ostream & so, const itk::TransformBase * t) +{ + typedef itk::TransformBase::ParametersType params_t; + + // make sure the transforms I care about are known to the object factory: + register_transforms(); + + // save the transform type: + so << t->GetTransformTypeAsString(); + + // get the stream precision: + int prec = so.precision(); + + // save the variable parameters: + params_t vp = t->GetParameters(); + const unsigned int num_vp = vp.size(); + so << " vp " << num_vp; + for (unsigned int i = 0; i < num_vp; i++) + { + so << ' ' << setw(prec + 7) << vp[i]; + } + + // save the fixed parameters: + try + { + params_t fp = t->GetFixedParameters(); + + const unsigned int num_fp = fp.size(); + so << " fp " << num_fp; + for (unsigned int i = 0; i < num_fp; i++) + { + so << ' ' << setw(prec + 7) << fp[i]; + } + } + catch (itk::ExceptionObject &) + { + so << " no_fp"; + } +} + +//---------------------------------------------------------------- +// load_transform +// +// Load an ITK transform of specified type from a stream. +// +itk::TransformBase::Pointer +load_transform(std::istream & si, const std::string & transform_type) +{ + typedef itk::TransformBase::ParametersType params_t; + + // make sure the transforms I care about are known to the object factory: + register_transforms(); + + // FIXME: +#if 0 + std::list names = + itk::TransformFactoryBase::GetFactory()->GetClassOverrideWithNames(); + for (std::list::iterator it = names.begin(); + it != names.end(); + ++it) + { + cerr << "FIXME: " << *it << endl; + } +#endif + + itk::LightObject::Pointer tmp = itk::ObjectFactoryBase::CreateInstance(transform_type.c_str()); + + itk::TransformBase::Pointer t = dynamic_cast(tmp.GetPointer()); + + if (t.GetPointer() == NULL) + { + cerr << "could not instantiate " << transform_type << ", giving up ..." << endl; + return t; + } + t->Register(); + + // load the variable parameters: + params_t vp; + std::string vp_token; + si >> vp_token; + if (vp_token != "vp") + { + cerr << "expected vp, received '" << vp_token << "', aborting ..." << endl; + assert(false); + } + + unsigned int num_vp = 0; + si >> num_vp; + vp.SetSize(num_vp); + + for (unsigned int i = 0; i < num_vp; i++) + { +#ifdef __APPLE__ + // NOTE: OSX std::iostream is broken -- it can write out doubles + // so small that it can't read them back in. The workaround is + // to read them in as long doubles, and then cast them down to + // a double: + long double tmp = 0; + si >> tmp; + if (si.fail()) + si.clear(); + vp[i] = double(tmp); +#else + si >> vp[i]; +#endif + } + + // load the fixed parameters: + params_t fp; + std::string fp_token; + si >> fp_token; + if (fp_token == "fp") + { + fp = t->GetFixedParameters(); + unsigned int num_fp = 0; + si >> num_fp; + + if (num_fp != fp.size()) + { + // some transforms (RBF) have variable number of fixed parameters: + fp.SetSize(num_fp); + } + + for (unsigned int i = 0; i < num_fp; i++) + { +#ifdef __APPLE__ + // NOTE: OSX std::iostream is broken -- it can write out doubles + // so small that it can't read them back in. The workaround is + // to read them in as long doubles, and then cast them down to + // a double: + long double tmp = 0; + si >> tmp; + if (si.fail()) + si.clear(); + fp[i] = double(tmp); +#else + si >> fp[i]; +#endif + } + } + else if (fp_token != "no_fp") + { + cerr << "unexpected token: '" << fp_token << "', aborting ..." << endl; + assert(false); + } + + // set the fixed parameters first: + try + { + t->SetFixedParameters(fp); + } + catch (itk::ExceptionObject &) + {} + + // set the variable parameters: + t->SetParametersByValue(vp); + + return t; +} + +//---------------------------------------------------------------- +// load_transform +// +itk::TransformBase::Pointer +load_transform(std::istream & si) +{ + // load the transform type: + std::string transform_type; + si >> transform_type; + + return load_transform(si, transform_type); +} + + +//---------------------------------------------------------------- +// save_mosaic +// +// Save image filenames and associated ITK transforms to a stream. +// +void +save_mosaic(std::ostream & so, + const unsigned int & num_images, + const double & pixel_spacing, + const bool & use_std_mask, + const the_text_t * image, + const itk::TransformBase ** transform) +{ + ios::fmtflags old_flags = so.setf(ios::scientific); + int old_precision = so.precision(); + so.precision(12); + + so << "format_version_number: " << 1 << endl + << "number_of_images: " << num_images << endl + << "pixel_spacing: " << pixel_spacing << endl + << "use_std_mask: " << use_std_mask << endl; + + for (unsigned int i = 0; i < num_images; i++) + { + // Find just the filename to save out to the mosaic. + so << "image:" << endl << image[i] << endl; + save_transform(so, transform[i]); + so << endl; + } + + so.setf(old_flags); + so.precision(old_precision); +} + +//---------------------------------------------------------------- +// load_mosaic +// +// Load image filenames and associated ITK transforms from a stream. +// +void +load_mosaic(std::istream & si, + double & pixel_spacing, + bool & use_std_mask, + std::vector & image, + std::vector & transform) +{ + // make sure the transforms I care about are known to the object factory: + register_transforms(); + + unsigned int num_images = ~0; + unsigned int i = 0; + unsigned int version = 0; + + // for backwards compatibility assume the standard mask is used: + use_std_mask = true; + + while (si.eof() == false && num_images != i) + { + std::string token; + si >> token; + + if (token == "format_version_number:") + { + si >> version; + } + else if (token == "number_of_images:") + { + si >> num_images; + image.resize(num_images); + transform.resize(num_images); + } + else if (token == "pixel_spacing:") + { + si >> pixel_spacing; + } + else if (token == "use_std_mask:") + { + si >> use_std_mask; + } + else if (token == "image:") + { + if (version == 0) + { + si >> image[i]; + } + else + { + si >> std::ws; + getline(si, image[i]); + } + + // the next token should be the transform type string: + std::string next_token; + si >> next_token; + + if (version == 0) + { + // Original .mosaic file format kept the filename + // and the transform type string on the same line, + // which made filenames with spaces difficult to handle: + + while (si.eof() == false) + { + itk::LightObject::Pointer tmp = itk::ObjectFactoryBase::CreateInstance(next_token.c_str()); + + if (tmp.GetPointer()) + { + break; + } + + // NOTE: this is a dirty hack to work around filenames with + // spaces problem in the original file format. + + // assume the string that was just read in is a part of filename: + image[i] += the_text_t(" "); + image[i] += the_text_t(next_token.c_str()); + si >> next_token; + } + } + + transform[i] = load_transform(si, next_token); + i++; + } + else + { + cerr << "WARNING: unknown token: '" << token << "', ignoring ..." << endl; + } + } +} + +//---------------------------------------------------------------- +// is_empty_bbox +// +// Test whether a bounding box is empty (min > max) +// +bool +is_empty_bbox(const pnt2d_t & min, const pnt2d_t & max) +{ + return min[0] > max[0] || min[1] > max[1]; +} + +//---------------------------------------------------------------- +// is_singular_bbox +// +// Test whether a bounding box is singular (min == max) +// +bool +is_singular_bbox(const pnt2d_t & min, const pnt2d_t & max) +{ + return min == max; +} + +//---------------------------------------------------------------- +// clamp_bbox +// +// Restrict a bounding box to be within given limits. +// +void +clamp_bbox(const pnt2d_t & confines_min, const pnt2d_t & confines_max, pnt2d_t & min, pnt2d_t & max) +{ + if (!is_empty_bbox(confines_min, confines_max)) + { + min[0] = std::min(confines_max[0], std::max(confines_min[0], min[0])); + min[1] = std::min(confines_max[1], std::max(confines_min[1], min[1])); + + max[0] = std::min(confines_max[0], std::max(confines_min[0], max[0])); + max[1] = std::min(confines_max[1], std::max(confines_min[1], max[1])); + } +} + + +//---------------------------------------------------------------- +// clip_histogram +// +// This is used by CLAHE to limit the contrast ratio slope. +// +void +clip_histogram(const double & clipping_height, + const unsigned int & pdf_size, + const unsigned int * pdf, + double * clipped_pdf) +{ + // count the number of pixels exceeding the clipping height: + double excess = double(0); + double deficit = double(0); + for (unsigned int i = 0; i < pdf_size; i++) + { + if (double(pdf[i]) <= clipping_height) + { + deficit += clipping_height - double(pdf[i]); + clipped_pdf[i] = double(pdf[i]); + } + else + { + excess += double(pdf[i]) - clipping_height; + clipped_pdf[i] = clipping_height; + } + } + + const double height_increment = excess / deficit; + for (unsigned int i = 0; i < pdf_size; i++) + { + if (clipped_pdf[i] >= clipping_height) + continue; + + double d = clipping_height - clipped_pdf[i]; + clipped_pdf[i] += d * height_increment; + } +} + +//---------------------------------------------------------------- +// std_mask +// +// Given image dimensions, generate a mask image. +// use_std_mask -- the standard mask for the Robert Marc Lab data. +// +mask_t::Pointer +std_mask(const itk::Point & origin, + const itk::Vector & spacing, + const itk::Size<2> & sz, + bool use_std_mask) +{ + // setup the mask: + mask_t::Pointer mask = make_image(spacing, sz, 255); + mask->SetOrigin(origin); + + if (use_std_mask) + { + unsigned int roi_x = 0; + unsigned int roi_y = (unsigned int)(0.97 * sz[1]); + unsigned int roi_w = (unsigned int)(0.3025 * sz[0]); + unsigned int roi_h = sz[1] - roi_y; + ::fill(mask, roi_x, roi_y, roi_w, roi_h, 0); + + /* + roi_y = (unsigned int)(0.98 * sz[1]); + roi_w = sz[0]; + roi_h = sz[1] - roi_y; + fill(mask, roi_x, roi_y, roi_w, roi_h, 0); + */ + } + + return mask; +} + + +//---------------------------------------------------------------- +// to_wcs +// +inline static vec2d_t +to_wcs(const vec2d_t & u_axis, const vec2d_t & v_axis, const double & u, const double & v) +{ + return u_axis * u + v_axis * v; +} + + +//---------------------------------------------------------------- +// to_wcs +// +inline static pnt2d_t +to_wcs(const pnt2d_t & origin, const vec2d_t & u_axis, const vec2d_t & v_axis, const double & u, const double & v) +{ + return origin + to_wcs(u_axis, v_axis, u, v); +} + +//---------------------------------------------------------------- +// calc_tile_mosaic_bbox +// +bool +calc_tile_mosaic_bbox(const base_transform_t * mosaic_to_tile, + + // image space bounding boxes of the tile: + const pnt2d_t & tile_min, + const pnt2d_t & tile_max, + + // mosaic space bounding boxes of the tile: + pnt2d_t & mosaic_min, + pnt2d_t & mosaic_max, + + // sample points along the image edges: + const unsigned int np) +{ + // initialize an empty bounding box: + mosaic_min[0] = std::numeric_limits::max(); + mosaic_min[1] = mosaic_min[0]; + mosaic_max[0] = -mosaic_min[0]; + mosaic_max[1] = -mosaic_min[0]; + + // it happens: + if (tile_min[0] == std::numeric_limits::max() || !mosaic_to_tile) + { + return true; + } + + base_transform_t::Pointer tile_to_mosaic; + mosaic_to_tile->GetInverse(tile_to_mosaic); + if (tile_to_mosaic.GetPointer() == nullptr) + { + return false; + } + + double W = tile_max[0] - tile_min[0]; + double H = tile_max[1] - tile_min[1]; + + // a temporary vector to hold the sample points: + std::vector xy((np + 1) * 4); + + // corner points: + xy[0] = pnt2d(tile_min[0], tile_min[1]); + xy[1] = pnt2d(tile_min[0], tile_max[1]); + xy[2] = pnt2d(tile_max[0], tile_min[1]); + xy[3] = pnt2d(tile_max[0], tile_max[1]); + + // edge points: + for (unsigned int j = 0; j < np; j++) + { + const double t = double(j + 1) / double(np + 1); + double x = tile_min[0] + t * W; + double y = tile_min[1] + t * H; + + unsigned int offset = (j + 1) * 4; + xy[offset + 0] = pnt2d(x, tile_min[1]); + xy[offset + 1] = pnt2d(x, tile_max[1]); + xy[offset + 2] = pnt2d(tile_min[0], y); + xy[offset + 3] = pnt2d(tile_max[0], y); + } + + // find the inverse mapping for each point, if possible: + std::list uv_list; + for (unsigned int j = 0; j < xy.size(); j++) + { + pnt2d_t uv; + if (find_inverse(tile_min, tile_max, mosaic_to_tile, tile_to_mosaic.GetPointer(), xy[j], uv)) + { + uv_list.push_back(uv); + } + } + + // calculate the bounding box of the inverse-mapped points: + for (std::list::const_iterator iter = uv_list.begin(); iter != uv_list.end(); ++iter) + { + const pnt2d_t & uv = *iter; + mosaic_min[0] = std::min(mosaic_min[0], uv[0]); + mosaic_max[0] = std::max(mosaic_max[0], uv[0]); + mosaic_min[1] = std::min(mosaic_min[1], uv[1]); + mosaic_max[1] = std::max(mosaic_max[1], uv[1]); + } + + return !uv_list.empty(); +} + + +//---------------------------------------------------------------- +// make_colors +// +// Generate a scrambled rainbow colormap. +// +void +make_colors(const unsigned int & num_colors, std::vector & color) +{ + static const xyz_t EAST = xyz(1, 0, 0); + static const xyz_t NORTH = xyz(0, 1, 0); + static const xyz_t WEST = xyz(0, 0, 1); + static const xyz_t SOUTH = xyz(0, 0, 0); + + color.resize(num_colors); + + for (unsigned int i = 0; i < num_colors; i++) + { +#if 1 + double t = fmod(double(i % 2) / 2.0 + double(i) / double(num_colors - 1), 1.0); +#else + double t = double(i) / double(num_colors); +#endif + + double s = 0.5 + 0.5 * fmod(double((i + 1) % 3) / 3.0 + double(i) / double(num_colors - 1), 1.0); + color[i] = hsv_to_rgb(xyz(t, s, 1.0)) * 255.0; + } +} + +//---------------------------------------------------------------- +// find_inverse +// +// Given a forward transform and image space coordinates, +// find mosaic space coordinates. +// +bool +find_inverse(const pnt2d_t & tile_min, // tile space + const pnt2d_t & tile_max, // tile space + const base_transform_t * mosaic_to_tile, + const pnt2d_t & xy, // tile space + pnt2d_t & uv, // mosaic space + const unsigned int max_iterations, + const double min_step_scale, + const double min_error_sqrd, + const unsigned int pick_up_pace_steps) +{ + base_transform_t::Pointer tile_to_mosaic; + mosaic_to_tile->GetInverse(tile_to_mosaic); + return find_inverse(tile_min, + tile_max, + mosaic_to_tile, + tile_to_mosaic.GetPointer(), + xy, + uv, + max_iterations, + min_step_scale, + min_error_sqrd); +} + +//---------------------------------------------------------------- +// find_inverse +// +// Given a forward transform, an approximate inverse transform, +// and image space coordinates, find mosaic space coordinates. +// +bool +find_inverse(const pnt2d_t & tile_min, // tile space + const pnt2d_t & tile_max, // tile space + const base_transform_t * mosaic_to_tile, + const base_transform_t * tile_to_mosaic, + const pnt2d_t & xy, // tile space + pnt2d_t & uv, // mosaic space + const unsigned int max_iterations, + const double min_step_scale, + const double min_error_sqrd, + const unsigned int pick_up_pace_steps) +{ + // #define DEBUG_FIND_INVERSE + if (tile_to_mosaic == NULL) + { + return false; + } + + const itk::GridTransform * gridTransform = dynamic_cast(tile_to_mosaic); + if (gridTransform != NULL) + { + // special case for the grid transform -- the inverse is either exact + // or it doesn't exist (maps to extreme coordinates): + pnt2d_t xy_plus; + + // Convert image space to mosaic space. + xy_plus[0] = xy[0] + gridTransform->transform_.tile_min_[0]; + xy_plus[1] = xy[1] + gridTransform->transform_.tile_min_[1]; + uv = tile_to_mosaic->TransformPoint(xy_plus); + return uv[0] != std::numeric_limits::max(); + } + + // the local coordinate system, at the center of the tile: + pnt2d_t p00 = tile_min + (tile_max - tile_min) * 0.5; + pnt2d_t p10 = p00; + pnt2d_t p01 = p00; + + double x_unit = 1.0; + double y_unit = 1.0; + p10[0] += x_unit; + p01[1] += y_unit; + + vec2d_t x_axis = p10 - p00; + vec2d_t y_axis = p01 - p00; + + // FIXME: this may fail, because inverse may be unstable: + // the same coordinate system in the mosaic space: + pnt2d_t q00 = tile_to_mosaic->TransformPoint(p00); + pnt2d_t q10 = tile_to_mosaic->TransformPoint(p10); + pnt2d_t q01 = tile_to_mosaic->TransformPoint(p01); + + vec2d_t u_axis = q10 - q00; + vec2d_t v_axis = q01 - q00; + +#ifdef DEBUG_FIND_INVERSE + cerr << endl + << "x_axis: " << x_axis << endl + << "u_axis: " << u_axis << endl + << endl + << "y_axis: " << y_axis << endl + << "v_axis: " << v_axis << endl + << endl + << "p00: " << p00 << endl + << "q00: " << q00 << endl + << endl; +#endif + + // initial guess: + uv = to_wcs(q00, u_axis, v_axis, (xy[0] - p00[0]) / x_unit, (xy[1] - p00[1]) / y_unit); + + // estimate the error: + const pnt2d_t & p0 = xy; + const pnt2d_t p1 = mosaic_to_tile->TransformPoint(uv); + vec2d_t er = p1 - p0; + +#ifdef DEBUG_FIND_INVERSE + cerr << "initial error: " << er.GetSquaredNorm() << ", dx: " << er[0] << ", dy: " << er[1] << endl; +#endif + + const double max_step_scale = 1.0; + double step_scale = max_step_scale; + unsigned int iteration = 0; + unsigned int successful_steps = 0; + + double e0_sqrd = er.GetSquaredNorm(); + + // don't try to improve samples with poor initialization (map to NaN): + if (e0_sqrd != e0_sqrd) + return false; + + while (true) + { + const vec2d_t uv_correction = to_wcs(u_axis, v_axis, -er[0] / x_unit, -er[1] / y_unit); + + pnt2d_t q = uv; + q[0] += step_scale * uv_correction[0]; + q[1] += step_scale * uv_correction[1]; + + pnt2d_t p = mosaic_to_tile->TransformPoint(q); + vec2d_t e = p - p0; + double e1_sqrd = e.GetSquaredNorm(); + +#ifdef DEBUG_FIND_INVERSE + cerr << setw(2) << iteration << ": " << e0_sqrd << " vs " << e1_sqrd << " -- "; +#endif + if (e1_sqrd < e0_sqrd) + { +#ifdef DEBUG_FIND_INVERSE + cerr << "ok" << endl; +#endif + uv = q; + er = e; + e0_sqrd = e1_sqrd; + successful_steps++; + + if (successful_steps % pick_up_pace_steps == 0) + { + step_scale = std::min(max_step_scale, step_scale * 2.0); + +#ifdef DEBUG_FIND_INVERSE + cerr << successful_steps << " successful steps -- increasing pace, new step length: " << step_scale << endl; +#endif + } + } + else + { + step_scale /= 2.0; + successful_steps = 0; +#ifdef DEBUG_FIND_INVERSE + cerr << "relaxing and backtracking, new step length: " << step_scale << endl; +#endif + } + + iteration++; + if (iteration >= max_iterations) + break; + if (e0_sqrd < min_error_sqrd) + break; + if (step_scale < min_step_scale) + break; + } +#ifdef DEBUG_FIND_INVERSE + cerr << endl; +#endif + + return true; +} + +//---------------------------------------------------------------- +// find_inverse +// +// Given a forward transform, an approximate inverse transform, +// and image space coordinates, find mosaic space coordinates. +// +bool +find_inverse(const base_transform_t * mosaic_to_tile, + const base_transform_t * tile_to_mosaic, + const pnt2d_t & xy, // tile space + pnt2d_t & uv, // mosaic space + const unsigned int max_iterations, + const double min_step_scale, + const double min_error_sqrd, + const unsigned int pick_up_pace_steps) +{ + // #define DEBUG_FIND_INVERSE + if (tile_to_mosaic == NULL) + { + return false; + } + + if (dynamic_cast(tile_to_mosaic) != NULL || + dynamic_cast(tile_to_mosaic) != NULL) + { + // special case for the grid transform -- the inverse is either exact + // or it doesn't exist (maps to extreme coordinates): + uv = tile_to_mosaic->TransformPoint(xy); + return uv[0] != std::numeric_limits::max(); + } + + // initial guess: + uv = tile_to_mosaic->TransformPoint(xy); + + // calculate initial error: + const pnt2d_t & p0 = xy; + pnt2d_t p1 = mosaic_to_tile->TransformPoint(uv); + vec2d_t er = p1 - p0; + +#ifdef DEBUG_FIND_INVERSE + cerr << "initial error: " << er.GetSquaredNorm() << ", dx: " << er[0] << ", dy: " << er[1] << endl; +#endif + + const double max_step_scale = 1.0; + double step_scale = max_step_scale; + unsigned int iteration = 0; + unsigned int successful_steps = 0; + + double e0_sqrd = er.GetSquaredNorm(); + + // don't try to improve samples with poor initialization (map to NaN): + if (e0_sqrd != e0_sqrd) + return false; + + while (true) + { + pnt2d_t uv1 = tile_to_mosaic->TransformPoint(xy - er); + const vec2d_t uv_correction = uv - uv1; + + pnt2d_t q = uv; + q[0] += step_scale * uv_correction[0]; + q[1] += step_scale * uv_correction[1]; + + pnt2d_t p = mosaic_to_tile->TransformPoint(q); + vec2d_t e = p - p0; + double e1_sqrd = e.GetSquaredNorm(); + +#ifdef DEBUG_FIND_INVERSE + cerr << setw(2) << iteration << ": " << e0_sqrd << " vs " << e1_sqrd << " -- "; +#endif + if (e1_sqrd < e0_sqrd) + { +#ifdef DEBUG_FIND_INVERSE + cerr << "ok" << endl; +#endif + uv = q; + er = e; + e0_sqrd = e1_sqrd; + successful_steps++; + + if (successful_steps % pick_up_pace_steps == 0) + { + step_scale = std::min(max_step_scale, step_scale * 2.0); + +#ifdef DEBUG_FIND_INVERSE + cerr << successful_steps << " successful steps -- increasing pace, new step length: " << step_scale << endl; +#endif + } + } + else + { + step_scale /= 2.0; + successful_steps = 0; +#ifdef DEBUG_FIND_INVERSE + cerr << "relaxing and backtracking, new step length: " << step_scale << endl; +#endif + } + + iteration++; + if (iteration >= max_iterations) + break; + if (e0_sqrd < min_error_sqrd) + break; + if (step_scale < min_step_scale) + break; + } +#ifdef DEBUG_FIND_INVERSE + cerr << endl; +#endif + + return true; +} + +//---------------------------------------------------------------- +// generate_landmarks_v1 +// +// Given a forward transform, generate a set of image space +// coordinates and find their matching mosaic space coordinates. +// +// version 1 -- uniform jittered sampling over the tile +// +bool +generate_landmarks_v1(const pnt2d_t & tile_min, + const pnt2d_t & tile_max, + const mask_t * tile_mask, + const base_transform_t * mosaic_to_tile, + const unsigned int samples, + std::vector & xy, + std::vector & uv, + bool refine) +{ + // #define DEBUG_LANDMARKS + + base_transform_t::Pointer tile_to_mosaic; + mosaic_to_tile->GetInverse(tile_to_mosaic); + if (tile_to_mosaic.GetPointer() == nullptr) + { + return false; + } + + // the local coordinate system, at the center of the tile: + pnt2d_t p00 = tile_min + (tile_max - tile_min) * 0.5; + pnt2d_t p10 = p00; + pnt2d_t p01 = p00; + + double W = tile_max[0] - tile_min[0]; + double H = tile_max[1] - tile_min[1]; + double x_unit = 1.0; + double y_unit = 1.0; + p10[0] += x_unit; + p01[1] += y_unit; + + vec2d_t x_axis = p10 - p00; + vec2d_t y_axis = p01 - p00; + + // FIXME: this may fail, because tile_to_mosaic may be unstable: + // the same coordinate system in the mosaic space: + pnt2d_t q00 = tile_to_mosaic->TransformPoint(p00); + pnt2d_t q10 = tile_to_mosaic->TransformPoint(p10); + pnt2d_t q01 = tile_to_mosaic->TransformPoint(p01); + + vec2d_t u_axis = q10 - q00; + vec2d_t v_axis = q01 - q00; + +#ifdef DEBUG_LANDMARKS + cerr << endl + << "x_axis: " << x_axis << endl + << "u_axis: " << u_axis << endl + << endl + << "y_axis: " << y_axis << endl + << "v_axis: " << v_axis << endl + << endl + << "p00: " << p00 << endl + << "q00: " << q00 << endl + << endl; +#endif + + std::list xy_list; + std::list uv_list; + std::list er_list; + + // sample the transform space: + for (unsigned int i = 0; i < samples; i++) + { + for (unsigned int j = 0; j < samples; j++) + { + const double s = (double(i) + drand()) / double(samples); + const double t = (double(j) + drand()) / double(samples); + + double x = tile_min[0] + s * W; + double y = tile_min[1] + t * H; + pnt2d_t pt_xy = pnt2d(x, y); + + // check to make sure this point is actually inside the tile/mask: + if (!pixel_in_mask(tile_mask, pt_xy)) + continue; + + // map (approximately) into the mosaic space: + pnt2d_t pt_uv = to_wcs(q00, u_axis, v_axis, (pt_xy[0] - p00[0]) / x_unit, (pt_xy[1] - p00[1]) / y_unit); + + // estimate the error: + const pnt2d_t & p0 = pt_xy; + const pnt2d_t p1 = mosaic_to_tile->TransformPoint(pt_uv); + vec2d_t pt_er = p1 - p0; +#ifdef DEBUG_LANDMARKS + cerr << "initial error: " << pt_er.GetSquaredNorm() << ", dx: " << p1[0] - p0[0] << ", dy: " << p1[1] - p0[1] + << endl; +#endif + + xy_list.push_back(pt_xy); + uv_list.push_back(pt_uv); + er_list.push_back(pt_er); + } + } + + // try to refine the boundary: + xy.assign(xy_list.begin(), xy_list.end()); + uv.assign(uv_list.begin(), uv_list.end()); + std::vector er(er_list.begin(), er_list.end()); + + if (refine) + { + for (unsigned int j = 0; j < xy.size(); j++) + { + const unsigned int max_iterations = 100; + const double min_step_scale = 1e-12; + const double min_error_sqrd = 1e-16; + const unsigned int pick_up_pace_steps = 5; + const double max_step_scale = 1.0; + + double step_scale = max_step_scale; + unsigned int iteration = 0; + unsigned int successful_steps = 0; + + const pnt2d_t & p0 = xy[j]; + double e0_sqrd = er[j].GetSquaredNorm(); + + // don't try to improve samples that fail: + if (e0_sqrd != e0_sqrd) + continue; + + while (true) + { + const vec2d_t uv_correction = to_wcs(u_axis, v_axis, -er[j][0] / x_unit, -er[j][1] / y_unit); + + pnt2d_t q = uv[j]; + q[0] += step_scale * uv_correction[0]; + q[1] += step_scale * uv_correction[1]; + + pnt2d_t p = mosaic_to_tile->TransformPoint(q); + vec2d_t e = p - p0; + double e1_sqrd = e.GetSquaredNorm(); + +#ifdef DEBUG_LANDMARKS + cerr << setw(3) << j << " " << setw(2) << iteration << ": " << e0_sqrd << " vs " << e1_sqrd << " -- "; +#endif + if (e1_sqrd < e0_sqrd) + { +#ifdef DEBUG_LANDMARKS + cerr << "ok" << endl; +#endif + uv[j] = q; + er[j] = e; + e0_sqrd = e1_sqrd; + successful_steps++; + + if (successful_steps % pick_up_pace_steps == 0) + { + step_scale = std::min(max_step_scale, step_scale * 2.0); + +#ifdef DEBUG_LANDMARKS + cerr << successful_steps << " successful steps -- increasing pace, new step length: " << step_scale << endl; +#endif + } + } + else + { + step_scale /= 2.0; + successful_steps = 0; +#ifdef DEBUG_LANDMARKS + cerr << "relaxing and backtracking, new step length: " << step_scale << endl; +#endif + } + + iteration++; + if (iteration >= max_iterations) + break; + if (e0_sqrd < min_error_sqrd) + break; + if (step_scale < min_step_scale) + break; + } +#ifdef DEBUG_LANDMARKS + cerr << endl; +#endif + } + } + + // clean up -- remove failed samples: + xy_list.clear(); + uv_list.clear(); + for (unsigned int j = 0; j < er.size(); j++) + { + if (er[j] != er[j]) + continue; + + xy_list.push_back(xy[j]); + uv_list.push_back(uv[j]); + } + + xy.assign(xy_list.begin(), xy_list.end()); + uv.assign(uv_list.begin(), uv_list.end()); + + return true; +} + + +//---------------------------------------------------------------- +// generate_landmarks_v2 +// +// Given a forward transform, generate a set of image space +// coordinates and find their matching mosaic space coordinates. +// +// version 2 -- non-uniform sampling skewed towards the center +// of the tile radially. This may be useful when +// the transform is ill-behaved at the tile edges. +// +bool +generate_landmarks_v2(const pnt2d_t & tile_min, + const pnt2d_t & tile_max, + const mask_t * tile_mask, + const base_transform_t * mosaic_to_tile, + const unsigned int samples, + std::vector & xy, + std::vector & uv, + bool refine) +{ + // #define DEBUG_LANDMARKS + + base_transform_t::Pointer tile_to_mosaic; + mosaic_to_tile->GetInverse(tile_to_mosaic); + if (tile_to_mosaic.GetPointer() == nullptr) + { + return false; + } + + // the local coordinate system, at the center of the tile: + pnt2d_t p00 = tile_min + (tile_max - tile_min) * 0.5; + pnt2d_t p10 = p00; + pnt2d_t p01 = p00; + + double W = tile_max[0] - tile_min[0]; + double H = tile_max[1] - tile_min[1]; + double x_unit = 1.0; + double y_unit = 1.0; + p10[0] += x_unit; + p01[1] += y_unit; + + vec2d_t x_axis = p10 - p00; + vec2d_t y_axis = p01 - p00; + + // FIXME: this may fail, because tile_to_mosaic may be unstable: + // the same coordinate system in the mosaic space: + pnt2d_t q00 = tile_to_mosaic->TransformPoint(p00); + pnt2d_t q10 = tile_to_mosaic->TransformPoint(p10); + pnt2d_t q01 = tile_to_mosaic->TransformPoint(p01); + + vec2d_t u_axis = q10 - q00; + vec2d_t v_axis = q01 - q00; + +#ifdef DEBUG_LANDMARKS + cerr << endl + << "x_axis: " << x_axis << endl + << "u_axis: " << u_axis << endl + << endl + << "y_axis: " << y_axis << endl + << "v_axis: " << v_axis << endl + << endl + << "p00: " << p00 << endl + << "q00: " << q00 << endl + << endl; +#endif + + std::list xy_list; + std::list uv_list; + std::list er_list; + + // sample the transform space (stay away from the corners): + for (unsigned int i = 0; i < samples; i++) + { + for (unsigned int j = 0; j < samples; j++) + { + // non-uniform sampling of the radius: + const double r = sqrt(drand()); + + // uniform sampling of the theta angle: + const double t = TWO_PI * (double(i) + drand()) / double(samples); + + // convert the polar coordinates into cartesian coordinates: + const double x = p00[0] + 0.7 * r * cos(t) * W / 2.0; + const double y = p00[1] + 0.7 * r * sin(t) * H / 2.0; + + pnt2d_t pt_xy = pnt2d(x, y); + + // check to make sure this point is actually inside the tile/mask: + if (!pixel_in_mask(tile_mask, pt_xy)) + continue; + + // map (approximately) into the mosaic space: + pnt2d_t pt_uv = to_wcs(q00, u_axis, v_axis, (pt_xy[0] - p00[0]) / x_unit, (pt_xy[1] - p00[1]) / y_unit); + + if (refine) + { + // estimate the error: + const pnt2d_t & p0 = pt_xy; + const pnt2d_t p1 = mosaic_to_tile->TransformPoint(pt_uv); + vec2d_t pt_er = p1 - p0; +#ifdef DEBUG_LANDMARKS + cerr << "initial error: " << pt_er.GetSquaredNorm() << ", dx: " << p1[0] - p0[0] << ", dy: " << p1[1] - p0[1] + << endl; +#endif + + xy_list.push_back(pt_xy); + uv_list.push_back(pt_uv); + er_list.push_back(pt_er); + } + else + { + pt_xy = mosaic_to_tile->TransformPoint(pt_uv); + xy_list.push_back(pt_xy); + uv_list.push_back(pt_uv); + } + } + } + + // try to refine the boundary: + xy.assign(xy_list.begin(), xy_list.end()); + uv.assign(uv_list.begin(), uv_list.end()); + + if (refine) + { + std::vector er(er_list.begin(), er_list.end()); + + for (unsigned int j = 0; j < xy.size(); j++) + { + const unsigned int max_iterations = 100; + const double min_step_scale = 1e-12; + const double min_error_sqrd = 1e-16; + const unsigned int pick_up_pace_steps = 5; + const double max_step_scale = 1.0; + + double step_scale = max_step_scale; + unsigned int iteration = 0; + unsigned int successful_steps = 0; + + const pnt2d_t & p0 = xy[j]; + double e0_sqrd = er[j].GetSquaredNorm(); + + while (true) + { + const vec2d_t uv_correction = to_wcs(u_axis, v_axis, -er[j][0] / x_unit, -er[j][1] / y_unit); + + pnt2d_t q = uv[j]; + q[0] += step_scale * uv_correction[0]; + q[1] += step_scale * uv_correction[1]; + + pnt2d_t p = mosaic_to_tile->TransformPoint(q); + vec2d_t e = p - p0; + double e1_sqrd = e.GetSquaredNorm(); + +#ifdef DEBUG_LANDMARKS + cerr << setw(3) << j << " " << setw(2) << iteration << ": " << e0_sqrd << " vs " << e1_sqrd << " -- "; +#endif + if (e1_sqrd < e0_sqrd) + { +#ifdef DEBUG_LANDMARKS + cerr << "ok" << endl; +#endif + uv[j] = q; + er[j] = e; + e0_sqrd = e1_sqrd; + successful_steps++; + + if (successful_steps % pick_up_pace_steps == 0) + { + step_scale = std::min(max_step_scale, step_scale * 2.0); + +#ifdef DEBUG_LANDMARKS + cerr << successful_steps << " successful steps -- increasing pace, new step length: " << step_scale << endl; +#endif + } + } + else + { + step_scale /= 2.0; + successful_steps = 0; +#ifdef DEBUG_LANDMARKS + cerr << "relaxing and backtracking, new step length: " << step_scale << endl; +#endif + } + + iteration++; + if (iteration >= max_iterations) + break; + if (e0_sqrd < min_error_sqrd) + break; + if (step_scale < min_step_scale) + break; + } +#ifdef DEBUG_LANDMARKS + cerr << endl; +#endif + } + } + + return true; +} + +//---------------------------------------------------------------- +// generate_landmarks +// +// Given a forward transform, generate a set of image space +// coordinates and find their matching mosaic space coordinates. +// +// version 1 -- uniform jittered sampling over the tile +// version 2 -- non-uniform sampling skewed towards the center +// of the tile radially. This may be useful when +// the transform is ill-behaved at the tile edges. +// +bool +generate_landmarks(const pnt2d_t & tile_min, + const pnt2d_t & tile_max, + const mask_t * tile_mask, + const base_transform_t * mosaic_to_tile, + const unsigned int samples, + std::vector & xy, + std::vector & uv, + const unsigned int version, + const bool refine) +{ + switch (version) + { + case 1: + return generate_landmarks_v1(tile_min, tile_max, tile_mask, mosaic_to_tile, samples, xy, uv, refine); + + case 2: + return generate_landmarks_v2(tile_min, tile_max, tile_mask, mosaic_to_tile, samples, xy, uv, refine); + } + + return false; +} + +//---------------------------------------------------------------- +// generate_landmarks +// +// Given a forward transform, generate a set of image space +// coordinates and find their matching mosaic space coordinates. +// +// version 1 -- uniform jittered sampling over the tile +// version 2 -- non-uniform sampling skewed towards the center +// of the tile radially. This may be useful when +// the transform is ill-behaved at the tile edges. +// +bool +generate_landmarks(const image_t * tile, + const mask_t * mask, + const base_transform_t * mosaic_to_tile, + const unsigned int samples, + std::vector & xy, + std::vector & uv, + const unsigned int version, + const bool refine) +{ + image_t::SpacingType sp = tile->GetSpacing(); + image_t::SizeType sz = tile->GetLargestPossibleRegion().GetSize(); + const pnt2d_t min = tile->GetOrigin(); + const pnt2d_t max = min + vec2d(sp[0] * double(sz[0]), sp[1] * double(sz[1])); + + switch (version) + { + case 1: + return generate_landmarks_v1(min, max, mask, mosaic_to_tile, samples, xy, uv, refine); + + case 2: + return generate_landmarks_v2(min, max, mask, mosaic_to_tile, samples, xy, uv, refine); + } + + return false; +} + +//---------------------------------------------------------------- +// calc_size +// +// Given a bounding box expressed in the image space and +// pixel spacing, calculate corresponding image size. +// +image_t::SizeType +calc_size(const pnt2d_t & min, const pnt2d_t & max, const double & spacing) +{ + image_t::SizeType sz; + sz[0] = (unsigned int)((ceilf(max[0]) - floor(min[0])) / spacing); + sz[1] = (unsigned int)((ceilf(max[1]) - floor(min[1])) / spacing); + return sz; +} + + +the_thread_pool_t * save_mosaic::_pthread_pool; + + +//---------------------------------------------------------------- +// hsv_to_rgb +// +// Convenience function for converting between HSV/RGB color +// spaces. This is used for colormapping. +// +xyz_t +hsv_to_rgb(const xyz_t & HSV) +{ + double H = HSV[0]; + double S = HSV[1]; + double V = HSV[2]; + + xyz_t RGB; + double & R = RGB[0]; + double & G = RGB[1]; + double & B = RGB[2]; + + if (S == 0.0) + { + // monochromatic: + R = V; + G = V; + B = V; + return RGB; + } + + H *= 6.0; + double i = floor(H); + double f = H - i; + + double p = V * (1.0 - S); + double q = V * (1.0 - S * f); + double t = V * (1.0 - S * (1 - f)); + + if (i == 0.0) + { + R = V; + G = t; + B = p; + } + else if (i == 1.0) + { + R = q; + G = V; + B = p; + } + else if (i == 2.0) + { + R = p; + G = V; + B = t; + } + else if (i == 3.0) + { + R = p; + G = q; + B = V; + } + else if (i == 4.0) + { + R = t; + G = p; + B = V; + } + else + { + // i == 5.0 + R = V; + G = p; + B = q; + } + + return RGB; +} + +//---------------------------------------------------------------- +// rgb_to_hsv +// +// Convenience function for converting between RGB/HSV color +// spaces. This is used for colormapping. +// +xyz_t +rgb_to_hsv(const xyz_t & RGB) +{ + double R = RGB[0]; + double G = RGB[1]; + double B = RGB[2]; + + xyz_t HSV; + double & H = HSV[0]; + double & S = HSV[1]; + double & V = HSV[2]; + + double min = std::min(R, std::min(G, B)); + double max = std::max(R, std::max(G, B)); + V = max; + + double delta = max - min; + if (max == 0) + { + S = 0; + H = -1; + } + else + { + S = delta / max; + + if (delta == 0) + { + delta = 1; + } + + if (R == max) + { + // between yellow & magenta + H = (G - B) / delta; + } + else if (G == max) + { + // between cyan & yellow + H = (B - R) / delta + 2; + } + else + { + // between magenta & cyan + H = (R - G) / delta + 4; + } + + H /= 6.0; + + if (H < 0.0) + { + H = H + 1.0; + } + } + + return HSV; +} \ No newline at end of file diff --git a/src/itkIRText.cxx b/src/itkIRText.cxx new file mode 100644 index 0000000..3c9479a --- /dev/null +++ b/src/itkIRText.cxx @@ -0,0 +1,580 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_text.hxx +// Author : Pavel A. Koshevoy +// Created : Sun Aug 29 14:53:00 MDT 2004 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : ASCII text convenience class. + +// local includes: +#include "itkIRText.h" + +// system includes: +#include +#include +#include +#include +#include +#include +#include +#include + + +//---------------------------------------------------------------- +// the_text_t::the_text_t +// +the_text_t::the_text_t(const char * text): + text_(NULL), + size_(0) +{ + assign(text); +} + +//---------------------------------------------------------------- +// the_text_t::the_text_t +// +the_text_t::the_text_t(const char * text, const size_t & size): + text_(NULL), + size_(0) +{ + assign(text, size); +} + +//---------------------------------------------------------------- +// the_text_t::the_text_t +// +the_text_t::the_text_t(const the_text_t & text): + text_(NULL), + size_(0) +{ + assign(text.text_, text.size_); +} + +//---------------------------------------------------------------- +// the_text_t::the_text_t +// +the_text_t::the_text_t(const std::list & text): + text_(NULL), + size_(text.size()) +{ + text_ = new char [size_ + 1]; + size_t j = 0; + for (std::list::const_iterator i = text.begin(); + i != text.end(); ++i, ++j) + { + text_[j] = *i; + } +} + +//---------------------------------------------------------------- +// the_text_t::~the_text_t +// +the_text_t::~the_text_t() +{ + clear(); +} + +//---------------------------------------------------------------- +// the_text_t::assign +// +void +the_text_t::assign(const char * text, const size_t & text_size) +{ + char * text_copy = new char [text_size + 1]; + for (size_t i = 0; i < text_size; i++) + { + text_copy[i] = text[i]; + } + + text_copy[text_size] = '\0'; + + delete [] text_; + text_ = text_copy; + size_ = text_size; +} + +//---------------------------------------------------------------- +// the_text_t::append +// +void +the_text_t::append(const char * text, const size_t & text_size) +{ + char * text_copy = new char [size_ + text_size + 1]; + + for (size_t i = 0; i < size_; i++) + { + text_copy[i] = text_[i]; + } + + for (size_t i = 0; i < text_size; i++) + { + text_copy[i + size_] = text[i]; + } + + text_copy[size_ + text_size] = '\0'; + + delete [] text_; + text_ = text_copy; + size_ += text_size; +} + +//---------------------------------------------------------------- +// the_text_t::replace +// replaces all occurances of one character with another. +void +the_text_t::replace(const char find, const char replace) +{ + char * cursor = text_; + while (*cursor != '\0') + { + if ( *cursor == find ) + *cursor = replace; + cursor++; + } +} + +//---------------------------------------------------------------- +// the_text_t::toShort +// +short int +the_text_t::toShort(bool * ok, int base) const +{ + long int num = toULong(ok, base); + if (ok != NULL) *ok &= ((num >= SHRT_MIN) && (num <= SHRT_MAX)); + return (short int)(num); +} + +//---------------------------------------------------------------- +// the_text_t::toUShort +// +unsigned short int +the_text_t::toUShort(bool * ok, int base) const +{ + unsigned long int num = toULong(ok, base); + if (ok != NULL) *ok &= (num <= USHRT_MAX); + return (unsigned short int)(num); +} + +//---------------------------------------------------------------- +// the_text_t::toInt +// +int +the_text_t::toInt(bool * ok, int base) const +{ + long int num = toULong(ok, base); + if (ok != NULL) *ok &= ((num >= INT_MIN) && (num <= INT_MAX)); + return int(num); +} + +//---------------------------------------------------------------- +// the_text_t::toUInt +// +unsigned int +the_text_t::toUInt(bool * ok, int base) const +{ + unsigned long int num = toULong(ok, base); + if (ok != NULL) *ok &= (num <= UINT_MAX); + return (unsigned int)(num); +} + +//---------------------------------------------------------------- +// the_text_t::toLong +// +long int +the_text_t::toLong(bool * ok, int base) const +{ + char * endptr = NULL; + long int num = strtol(text_, &endptr, base); + if (ok != NULL) *ok = !(text_ == endptr || errno == ERANGE); + return num; +} + +//---------------------------------------------------------------- +// the_text_t::toULong +// +unsigned long int +the_text_t::toULong(bool * ok, int base) const +{ + char * endptr = NULL; + unsigned long int num = strtoul(text_, &endptr, base); + if (ok != NULL) *ok = !(text_ == endptr || errno == ERANGE); + return num; +} + +//---------------------------------------------------------------- +// the_text_t::toFloat +// +float +the_text_t::toFloat(bool * ok) const +{ + double num = toDouble(ok); + return float(num); +} + +//---------------------------------------------------------------- +// the_text_t::toDouble +// +double +the_text_t::toDouble(bool * ok) const +{ + char * endptr = NULL; + double num = strtod(text_, &endptr); + if (ok != NULL) *ok = !(text_ == endptr || errno == ERANGE); + return num; +} + +//---------------------------------------------------------------- +// the_text_t::to_ascii +// +void +the_text_t::to_ascii() +{ + for (size_t i = 0; i < size_; i++) + { + text_[i] = ((size_t)(text_[i]) < 128) ? text_[i] : '?'; + } +} + +//---------------------------------------------------------------- +// the_text_t::to_lower +// +void +the_text_t::to_lower() +{ + for (size_t i = 0; i < size_; i++) + { + text_[i] = tolower(text_[i]); + } +} + +//---------------------------------------------------------------- +// the_text_t::to_upper +// +void +the_text_t::to_upper() +{ + for (size_t i = 0; i < size_; i++) + { + text_[i] = toupper(text_[i]); + } +} + +//---------------------------------------------------------------- +// the_text_t::fill +// +void +the_text_t::fill(const char & c, const size_t size) +{ + char * text = new char [size + 1]; + for (size_t i = 0; i < size; i++) + { + text[i] = c; + } + text[size] = '\0'; + + delete [] text_; + text_ = text; + size_ = size; +} + +//---------------------------------------------------------------- +// the_text_t::match_head +// +bool +the_text_t::match_head(const the_text_t & t, bool ignore_case) const +{ + if (t.size_ > size_) return false; + return match_text(t, 0, ignore_case); +} + +//---------------------------------------------------------------- +// the_text_t::match_tail +// +bool +the_text_t::match_tail(const the_text_t & t, bool ignore_case) const +{ + if (t.size_ > size_) return false; + return match_text(t, size_ - t.size_, ignore_case); +} + +//---------------------------------------------------------------- +// the_text_t::match_text +// +bool +the_text_t::match_text(const the_text_t & t, + const size_t & start, + bool ignore_case) const +{ + size_t end = start + t.size_; + if (end > size_) return false; + + for (size_t i = start; i < end; i++) + { + char a = text_[i]; + char b = t.text_[i - start]; + + if (ignore_case) + { + a = tolower(a); + b = tolower(b); + } + + if (a != b) return false; + } + + return true; +} + +//---------------------------------------------------------------- +// the_text_t::simplify_ws +// +the_text_t +the_text_t::simplify_ws() const +{ + // find the first non-whitespace character: + int start = 0; + for (; start < int(size_) && isspace(text_[start]); start++) + { + // skipping whitespace... + } + + // NOTE: an all-whitespace string will simplify to an empty string: + if (start == int(size_)) + { + return the_text_t(""); + } + + // find the last non-whitespace character: + int finish = int(size_) - 1; + for (; finish >= start && isspace(text_[finish]); finish--); + + // intermediate storage: + the_text_t tmp; + tmp.fill('\0', size_t((finish + 1) - start)); + + size_t j = 0; + bool prev_ws = false; + for (int i = start; i <= finish; i++) + { + char c = isspace(text_[i]) ? ' ' : text_[i]; + if (c == ' ' && prev_ws) continue; + + prev_ws = (c == ' '); + tmp.text_[j] = c; + j++; + } + + the_text_t out(tmp.text(), j); + return out; +} + +//---------------------------------------------------------------- +// the_text_t::split +// +size_t +the_text_t::split(std::vector & tokens, + const char & separator, + const bool & empty_ok) const +{ + if (size_ == 0) + { + tokens.resize(0); + return 0; + } + + // find the separators: + typedef std::list list_t; + list_t separators; + for (size_t i = 0; i < size_; i++) + { + if (text_[i] != separator) continue; + separators.push_back(i); + } + separators.push_back(size_); + + std::list tmp; + + typedef std::list::iterator iter_t; + size_t a = 0; + for (iter_t i = separators.begin(); i != separators.end(); ++i) + { + size_t b = *i; + + if (b - a == 0 && empty_ok) + { + tmp.push_back(the_text_t("")); + } + else if (b - a > 0) + { + tmp.push_back(the_text_t(&text_[a], b - a)); + } + + a = b + 1; + } + + tokens.resize(tmp.size()); + a = 0; + for (std::list::iterator i = tmp.begin(); i != tmp.end(); ++i) + { + tokens[a] = *i; + a++; + } + + return tmp.size(); +} + +//---------------------------------------------------------------- +// the_text_t::splitAt +// Splits the string around the n'th occurance of split_char +// if n > size_, it is split around the last occurance. +std::vector the_text_t::splitAt(const char split_char, + unsigned int n) const +{ + char * cursor = text_; + + // Find the desired character. + unsigned int position = 0; + unsigned int found_pos = std::numeric_limits::max(); + while (*cursor != '\0') + { + if ( *cursor == split_char ) + { + n--; + found_pos = position; + if ( n == 0 ) + break; + } + cursor++; + position++; + } + + std::vector str_parts; + str_parts.resize(2); + if ( found_pos != std::numeric_limits::max() ) + { + // Split point found, do split. + the_text_t left(&text_[0], found_pos); + the_text_t right(&text_[found_pos + 1], size_ - found_pos - 1); + + str_parts[0] = left; + str_parts[1] = right; + } + else + { + // No split point found. + str_parts[0] = ""; + str_parts[1] = the_text_t( text_ ); + } + + return str_parts; +} + +//---------------------------------------------------------------- +// the_text_t::contains +// +size_t +the_text_t::contains(const char & symbol) const +{ + size_t count = 0; + for (size_t i = 0; i < size_; i++) + { + if (text_[i] != symbol) continue; + count++; + } + + return count; +} + + +//---------------------------------------------------------------- +// operator << +// +std::ostream & +operator << (std::ostream & out, const the_text_t & text) +{ + std::string tmp(text.text(), text.size()); + out << tmp; + return out; +} + +//---------------------------------------------------------------- +// operator >> +// +std::istream & +operator >> (std::istream & in, the_text_t & text) +{ + std::string tmp; + in >> tmp; + text.assign(tmp.data(), tmp.size()); + return in; +} + +//---------------------------------------------------------------- +// getline +// +std::istream & +getline(std::istream & in, the_text_t & text) +{ + std::string tmp; + getline(in, tmp); + if (!tmp.empty()) + { + std::size_t len = tmp.size(); + if (tmp[len - 1] == '\r') + { + // truncate the \r character + tmp.resize(len - 1); + } + } + + text.assign(tmp.data(), tmp.size()); + return in; +} + +//---------------------------------------------------------------- +// to_binary +// +the_text_t +to_binary(const unsigned char & byte, bool lsb_first) +{ + the_text_t str; + str.fill('0', 8); + + unsigned char mask = 1; + if (lsb_first) + { + for (int i = 0; i < 8; i++, mask *= 2) + { + str[i] = (byte & mask) ? '1' : '0'; + } + } + else + { + for (int i = 7; i > -1; i--, mask *= 2) + { + str[i] = (byte & mask) ? '1' : '0'; + } + } + + return str; +} diff --git a/src/itkIRUtils.cxx b/src/itkIRUtils.cxx new file mode 100644 index 0000000..db4e4ab --- /dev/null +++ b/src/itkIRUtils.cxx @@ -0,0 +1,314 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : the_utils.cxx +// Author : Pavel A. Koshevoy +// Created : Tue Nov 4 20:32:04 MST 2008 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : utility functions + +// system includes: +#ifndef _USE_MATH_DEFINES +#define _USE_MATH_DEFINES +#endif + +// system includes: +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#include +#else +#include +#include +#include +#include +#endif +#include + +// local includes: +#include "itkIRUtils.h" + + +//---------------------------------------------------------------- +// sleep_msec +// +void +sleep_msec(size_t msec) +{ +#ifdef WIN32 + Sleep((DWORD)(msec)); +#else + usleep(msec * 1000); +#endif +} + +//---------------------------------------------------------------- +// restore_console_stdio +// +bool +restore_console_stdio() +{ +#ifdef _WIN32 + AllocConsole(); + +#pragma warning(push) +#pragma warning(disable: 4996) + + freopen("conin$", "r", stdin); + freopen("conout$", "w", stdout); + freopen("conout$", "w", stderr); + +#pragma warning(pop) + + HANDLE std_out_handle = GetStdHandle(STD_OUTPUT_HANDLE); + if (std_out_handle == INVALID_HANDLE_VALUE) + { + return false; + } + + COORD console_buffer_size; + console_buffer_size.X = 80; + console_buffer_size.Y = 9999; + SetConsoleScreenBufferSize(std_out_handle, + console_buffer_size); +#endif + + return true; +} + + +namespace the +{ +#ifdef _WIN32 + //---------------------------------------------------------------- + // utf8_to_utf16 + // + static void + utf8_to_utf16(const char * utf8, wchar_t *& utf16) + { + int wcs_size = + MultiByteToWideChar(CP_UTF8, // encoding (ansi, utf, etc...) + 0, // flags (precomposed, composite,... ) + utf8, // source multi-byte character string + -1, // number of bytes in the source string + NULL, // wide-character destination + 0); // destination buffer size + + utf16 = new wchar_t[wcs_size + 1]; + MultiByteToWideChar(CP_UTF8, + 0, + utf8, + -1, + utf16, + wcs_size); + } +#endif + + //---------------------------------------------------------------- + // open_utf8 + // + int + open_utf8(const char * filename_utf8, int oflag, int pmode) + { + int fd = -1; + +#ifdef _WIN32 + // on windows utf-8 has to be converted to utf-16 + wchar_t * filename_utf16 = 0; + utf8_to_utf16(filename_utf8, filename_utf16); + + int sflag = _SH_DENYNO; + _wsopen_s(&fd, filename_utf16, oflag, sflag, pmode); + delete [] filename_utf16; + +#else + // assume utf-8 is supported natively: + fd = open(filename_utf8, oflag, pmode); +#endif + + return fd; + } + + //---------------------------------------------------------------- + // open_utf8 + // + void + open_utf8(std::fstream & fstream_to_open, + const char * filename_utf8, + std::ios_base::openmode mode) + { +#ifdef _WIN32 + // on windows utf-8 has to be converted to utf-16 + wchar_t * filename_utf16 = 0; + utf8_to_utf16(filename_utf8, filename_utf16); + + fstream_to_open.open(filename_utf16, mode); + delete [] filename_utf16; + +#else + // assume utf-8 is supported natively: + fstream_to_open.open(filename_utf8, mode); +#endif + } + + //---------------------------------------------------------------- + // fopen_utf8 + // + FILE * + fopen_utf8(const char * filename_utf8, const char * mode) + { + FILE * file = NULL; + +#ifdef _WIN32 + wchar_t * filename_utf16 = NULL; + utf8_to_utf16(filename_utf8, filename_utf16); + + wchar_t * mode_utf16 = NULL; + utf8_to_utf16(mode, mode_utf16); + + _wfopen_s(&file, filename_utf16, mode_utf16); + delete [] filename_utf16; + delete [] mode_utf16; +#else + file = fopen(filename_utf8, mode); +#endif + + return file; + } + + //---------------------------------------------------------------- + // rename_utf8 + // + int + rename_utf8(const char * old_utf8, const char * new_utf8) + { +#ifdef _WIN32 + wchar_t * old_utf16 = NULL; + utf8_to_utf16(old_utf8, old_utf16); + + wchar_t * new_utf16 = NULL; + utf8_to_utf16(new_utf8, new_utf16); + + int ret = _wrename(old_utf16, new_utf16); + + delete [] old_utf16; + delete [] new_utf16; +#else + + int ret = rename(old_utf8, new_utf8); +#endif + + return ret; + } + + //---------------------------------------------------------------- + // remove_utf8 + // + int + remove_utf8(const char * filename_utf8) + { +#ifdef _WIN32 + wchar_t * filename_utf16 = NULL; + utf8_to_utf16(filename_utf8, filename_utf16); + + int ret = _wremove(filename_utf16); + delete [] filename_utf16; +#else + + int ret = remove(filename_utf8); +#endif + + return ret; + } + + //---------------------------------------------------------------- + // rmdir_utf8 + // + int + rmdir_utf8(const char * dir_utf8) + { +#ifdef _WIN32 + wchar_t * dir_utf16 = NULL; + utf8_to_utf16(dir_utf8, dir_utf16); + + int ret = _wrmdir(dir_utf16); + delete [] dir_utf16; +#else + + int ret = remove(dir_utf8); +#endif + + return ret; + } + + //---------------------------------------------------------------- + // mkdir_utf8 + // + int + mkdir_utf8(const char * path_utf8) + { +#ifdef _WIN32 + wchar_t * path_utf16 = NULL; + utf8_to_utf16(path_utf8, path_utf16); + + int ret = _wmkdir(path_utf16); + delete [] path_utf16; +#else + + int ret = mkdir(path_utf8, S_IRWXU); +#endif + + return ret; + } + + //---------------------------------------------------------------- + // fseek64 + // + int + fseek64(FILE * file, off_t offset, int whence) + { +#ifdef _WIN32 + int ret = _fseeki64(file, offset, whence); +#else + int ret = fseeko(file, offset, whence); +#endif + + return ret; + } + + //---------------------------------------------------------------- + // ftell64 + // + off_t + ftell64(const FILE * file) + { +#ifdef _WIN32 + off_t pos = _ftelli64(const_cast(file)); +#else + off_t pos = ftello(const_cast(file)); +#endif + + return pos; + } +} diff --git a/src/itkMinimalStandardRandomVariateGenerator.cxx b/src/itkMinimalStandardRandomVariateGenerator.cxx deleted file mode 100644 index a34635f..0000000 --- a/src/itkMinimalStandardRandomVariateGenerator.cxx +++ /dev/null @@ -1,52 +0,0 @@ -/*========================================================================= - * - * Copyright NumFOCUS - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0.txt - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - *=========================================================================*/ -#include "itkMinimalStandardRandomVariateGenerator.h" - -namespace itk -{ -namespace Statistics -{ - -MinimalStandardRandomVariateGenerator ::MinimalStandardRandomVariateGenerator() -{ - this->m_NormalGenerator = NormalGeneratorType::New(); - this->Initialize(1); -} - -void -MinimalStandardRandomVariateGenerator ::Initialize(int randomSeed) -{ - this->m_NormalGenerator->Initialize(randomSeed); -} - - -double -MinimalStandardRandomVariateGenerator ::GetVariate() -{ - return this->m_NormalGenerator->GetVariate(); -} - - -void -MinimalStandardRandomVariateGenerator ::PrintSelf(std::ostream & os, Indent indent) const -{ - Superclass::PrintSelf(os, indent); -} - -} // end namespace Statistics -} // end namespace itk diff --git a/src/itkRBFTransform.cxx b/src/itkRBFTransform.cxx new file mode 100644 index 0000000..d87bea1 --- /dev/null +++ b/src/itkRBFTransform.cxx @@ -0,0 +1,429 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : itkRBFTransform.cxx +// Author : Pavel A. Koshevoy +// Created : 2007/01/23 10:15 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : A Thin Plate Spline transform. + +// local includes: +#include "itkRBFTransform.h" + +// ITK includes: +#include + +// system includes: +#include + +// namespace access: +using std::cerr; +using std::endl; + + +namespace itk +{ +#if 0 +} +#endif + +//---------------------------------------------------------------- +// RBFTransform::RBFTransform +// +RBFTransform::RBFTransform() +{ + this->m_FixedParameters.SetSize(4); + this->m_Parameters.SetSize(6); + + double & Xmax = this->m_FixedParameters[0]; + double & Ymax = this->m_FixedParameters[1]; + Xmax = 1; + Ymax = 1; + + double & uc = this->m_FixedParameters[2]; + double & vc = this->m_FixedParameters[3]; + uc = 0; + vc = 0; + + double * ab_vec = &(this->m_Parameters[index_a(0)]); + ab_vec[0] = 0; + ab_vec[1] = 0; + ab_vec[2] = 1; + ab_vec[3] = 0; + ab_vec[4] = 0; + ab_vec[5] = 1; +} + +//---------------------------------------------------------------- +// RBFTransform::TransformPoint +// +RBFTransform::OutputPointType +RBFTransform::TransformPoint(const InputPointType & uv) const +{ + OutputPointType xy; + + const double & Xmax = GetXmax(); + const double & Ymax = GetYmax(); + + const double & uc = GetUc(); + const double & vc = GetVc(); + + const double * uv_vec = this->uv(0); + const double * fg_vec = &(this->f(0)); + const double * ab_vec = &(this->a(0)); + + const double & u = uv[0]; + const double & v = uv[1]; + + // calculate the summation terms: + double F = 0; + double G = 0; + { + unsigned int num_pts = num_points(); + for (unsigned int i = 0; i < num_pts; i++) + { + unsigned int offset = i * 2; + + // calculate the kernel term: + double Q = kernel(&(uv[0]), uv_vec + offset, Xmax, Ymax); + F += Q * fg_vec[offset]; + G += Q * fg_vec[offset + 1]; + } + } + + double A = (u - uc) / Xmax; + double B = (v - vc) / Ymax; + xy[0] = Xmax * (ab_vec[0] + ab_vec[2] * A + ab_vec[4] * B + F); + xy[1] = Ymax * (ab_vec[1] + ab_vec[3] * A + ab_vec[5] * B + G); + + return xy; +} + +//---------------------------------------------------------------- +// RBFTransform::GetInverse +// +RBFTransform::InverseTransformPointer +RBFTransform::GetInverse() const +{ + unsigned int num_pts = num_points(); + std::vector xy_vec(num_pts); + std::vector uv_vec(num_pts); + + for (unsigned int i = 0; i < num_pts; i++) + { + const double * uv = this->uv(i); + uv_vec[i][0] = uv[0]; + uv_vec[i][1] = uv[1]; + + // find where a given mosaic space control point maps to in the tile: + xy_vec[i] = TransformPoint(uv_vec[i]); + } + + // NOTE: this is not an exact inverse, some form of an iterative + // inverse mapping calculation will be required (like find_inverse): + OutputPointType tile_min; + OutputPointType tile_max; + tile_min[0] = 0; + tile_min[1] = 0; + tile_max[0] = GetXmax() * 2; + tile_max[1] = GetYmax() * 2; + + RBFTransform::Pointer inverse = RBFTransform::New(); + inverse->setup(tile_min, tile_max, num_pts, num_pts ? &(xy_vec[0]) : NULL, num_pts ? &(uv_vec[0]) : NULL); + + return inverse.GetPointer(); +} + +//---------------------------------------------------------------- +// RBFTransform::setup +// +void +RBFTransform::setup( // image bounding box expressed in the image space, + // defines transform normalization parameters: + const OutputPointType & tile_min, // tile space + const OutputPointType & tile_max, // tile space + + // landmark correspondences: + const unsigned int num_pts, // number of pairs + const InputPointType * uv, // mosaic space + const OutputPointType * xy) // tile space +{ + this->m_FixedParameters.SetSize(4 + num_pts * 2); + this->m_Parameters.SetSize(6 + num_pts * 2); + + // setup the normalization parameters: + double & Xmax = this->m_FixedParameters[0]; + double & Ymax = this->m_FixedParameters[1]; + Xmax = (tile_max[0] - tile_min[0]) / 2.0; + Ymax = (tile_max[1] - tile_min[1]) / 2.0; + + // store the control points and calculate the center of mass: + double * uv_vec = &(this->m_FixedParameters[4]); + double u_sum = 0; + double v_sum = 0; + for (unsigned int i = 0; i < num_pts; i++) + { + unsigned int offset = i * 2; + uv_vec[offset] = uv[i][0]; + uv_vec[offset + 1] = uv[i][1]; + + u_sum += uv[i][0]; + v_sum += uv[i][1]; + } + + // setup the center of rotation: + double & uc = this->m_FixedParameters[2]; + double & vc = this->m_FixedParameters[3]; + uc = num_pts == 0 ? 0 : u_sum / double(num_pts); + vc = num_pts == 0 ? 0 : v_sum / double(num_pts); + + if (num_pts == 0) + { + double * ab_vec = &(this->m_Parameters[index_a(0)]); + ab_vec[0] = 0; + ab_vec[1] = 0; + ab_vec[2] = 1; + ab_vec[3] = 0; + ab_vec[4] = 0; + ab_vec[5] = 1; + } + else + { + // setup the linear system to solve for transform parameters: + vnl_matrix M(num_pts + 3, num_pts + 3, 0); + vnl_vector bx(num_pts + 3, 0); + vnl_vector by(num_pts + 3, 0); + + for (unsigned int r = 0; r < num_pts; r++) + { + bx[r] = xy[r][0] / Xmax; + by[r] = xy[r][1] / Ymax; + + // kernel components: + for (unsigned int c = 0; c < num_pts; c++) + { + M(r, c) = kernel(&(uv[r][0]), &(uv[c][0]), Xmax, Ymax); + } + + // polynomial components: + M(r, num_pts) = 1; + M(r, num_pts + 1) = (uv[r][0] - uc) / Xmax; + M(r, num_pts + 2) = (uv[r][1] - vc) / Ymax; + + M(num_pts, r) = 1; + M(num_pts + 1, r) = (uv[r][0] - uc) / Xmax; + M(num_pts + 2, r) = (uv[r][1] - vc) / Ymax; + } + + // FIXME: +#if 0 + cerr << "M: " << M << endl + << "bx: " << bx << endl + << "by: " << by << endl; +#endif + + // use SVD to solve the linear system: + vnl_svd svd(M); + vnl_vector ax = svd.solve(bx); + vnl_vector ay = svd.solve(by); + + // store the polynomial coefficients into the parameter vector: + double * ab_vec = &(this->m_Parameters[index_a(0)]); + ab_vec[0] = ax[num_pts]; + ab_vec[1] = ay[num_pts]; + ab_vec[2] = ax[num_pts + 1]; + ab_vec[3] = ay[num_pts + 1]; + ab_vec[4] = ax[num_pts + 2]; + ab_vec[5] = ay[num_pts + 2]; + + // store the Radial Basis Function coefficients and mosaic space + // landmark coordinates into the transform parameter vector: + double * fg_vec = &(this->m_Parameters[index_f(0)]); + for (unsigned int i = 0; i < num_pts; i++) + { + unsigned int offset = i * 2; + fg_vec[offset] = ax[i]; + fg_vec[offset + 1] = ay[i]; + } + } + + // FIXME: PrintSelf(cerr, itk::Indent(0)); +} + +//---------------------------------------------------------------- +// RBFTransform::GetJacobian +// +void +RBFTransform::ComputeJacobianWithRespectToParameters(const InputPointType & point, + JacobianType & jacobian) const +{ + // shortcuts: + const double & Xmax = GetXmax(); + const double & Ymax = GetYmax(); + const double & uc = GetUc(); + const double & vc = GetVc(); + + const double & u = point[0]; + const double & v = point[1]; + + const double A = (u - uc) / Xmax; + const double B = (v - vc) / Ymax; + + jacobian(0, index_a(0)) = Xmax; + jacobian(0, index_a(1)) = Xmax * A; + jacobian(0, index_a(2)) = Xmax * B; + + jacobian(1, index_b(0)) = Ymax; + jacobian(1, index_b(1)) = Ymax * A; + jacobian(1, index_b(2)) = Ymax * B; + + const unsigned int num_pts = num_points(); + const double * uv_vec = &(this->m_FixedParameters[index_uv(0)]); + + for (unsigned int i = 0; i < num_pts; i++) + { + unsigned int offset = i * 2; + double U = (u - uv_vec[offset]) / Xmax; + double V = (v - uv_vec[offset + 1]) / Ymax; + + double R = U * U + V * V; + double lR = R == 0 ? 0 : log(R); + double Q = R * lR; + + jacobian(0, index_f(i)) = Xmax * Q; + jacobian(1, index_g(i)) = Ymax * Q; + } +} + +#if 0 +//---------------------------------------------------------------- +// RBFTransform::eval +// +void +RBFTransform::eval(const std::vector & point, + std::vector & function, + std::vector > & jacobian) const +{ + // shortcuts: + const double & Xmax = GetXmax(); + const double & Ymax = GetYmax(); + const double & uc = GetUc(); + const double & vc = GetVc(); + + const double & u = point[0]; + const double & v = point[1]; + + const double A = (u - uc) / Xmax; + const double B = (v - vc) / Ymax; + + const unsigned int num_pts = num_points(); + + double F = 0; + double G = 0; + double dF_du = 0; + double dF_dv = 0; + double dG_du = 0; + double dG_dv = 0; + + for (unsigned int i = 0; i < num_pts; i++) + { + const double * fg = &(this->m_Parameters[index_f(i)]); + const double & fi = fg[0]; + const double & gi = fg[1]; + + const double * uv = &(this->m_FixedParameters[index_uv(i)]); + const double & ui = uv[0]; + const double & vi = uv[1]; + + double U = (u - ui) / Xmax; + double V = (v - vi) / Ymax; + double R = U * U + V * V; + double r = sqrt(R); + double lR = R == 0 ? 0 : log(R); + double Q = R * lR; + + F += fi * Q; + G += gi * Q; + + dF_du += fi * (u - ui) * (lnR + 1); + dF_dv += fi * (v - vi) * (lnR + 1); + dG_du += gi * (u - ui) * (lnR + 1); + dG_dv += gi * (v - vi) * (lnR + 1); + } + + dF_du *= 2 / (Xmax * Xmax); + dF_dv *= 2 / (Ymax * Ymax); + dG_du *= 2 / (Xmax * Xmax); + dG_dv *= 2 / (Ymax * Ymax); + + function[0] = Xmax * (a(0) + a(1) * A + a(2) * B + F); + function[1] = Ymax * (b(0) + b(1) * A + b(2) * B + G); + + // dx/du: + jacobian[0][0] = Xmax * (a(1) + dF_du); + + // dx/dv: + jacobian[0][1] = Xmax * (a(2) + dF_dv); + + // dy/du: + jacobian[1][0] = Ymax * (b(1) + dG_du); + + // dy/dv: + jacobian[1][1] = Ymax * (b(2) + dG_dv); +} +#endif + +//---------------------------------------------------------------- +// RBFTransform::PrintSelf +// +void +RBFTransform::PrintSelf(std::ostream & os, Indent indent) const +{ + Superclass::PrintSelf(os, indent); + + unsigned int num_pts = num_points(); + + os << indent << "Xmax = " << GetXmax() << endl + << indent << "Ymax = " << GetYmax() << endl + << indent << "uc = " << GetUc() << endl + << indent << "vc = " << GetVc() << endl; + + os << indent << "a0 = " << a(0) << endl << indent << "a1 = " << a(1) << endl << indent << "a2 = " << a(2) << endl; + + for (unsigned int i = 0; i < num_pts; i++) + { + os << indent << 'f' << i << " = " << f(i) << endl; + } + + os << indent << "b0 = " << b(0) << endl << indent << "b1 = " << b(1) << endl << indent << "b2 = " << b(2) << endl; + + for (unsigned int i = 0; i < num_pts; i++) + { + os << indent << 'g' << i << " = " << g(i) << endl; + } +} + + +#if 0 +{ +#endif +} // namespace itk diff --git a/src/itkRegularStepGradientDescentOptimizer2.cxx b/src/itkRegularStepGradientDescentOptimizer2.cxx new file mode 100644 index 0000000..ff0dc62 --- /dev/null +++ b/src/itkRegularStepGradientDescentOptimizer2.cxx @@ -0,0 +1,335 @@ +// -*- Mode: c++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: t -*- +// NOTE: the first line of this file sets up source code indentation rules +// for Emacs; it is also a hint to anyone modifying this file. + +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +// File : itkRegularStepGradientDescentOptimizer2.cxx +// Author : Pavel A. Koshevoy, Tolga Tasdizen +// Created : 2005/11/11 14:54 +// Copyright : (C) 2004-2008 University of Utah +// License : GPLv2 +// Description : An enhanced version of the +// itk::RegularStepGradientDescentOptimizer +// fixing a bug with relaxation and adding support for +// step size increase (pick_up_pace), back tracking and +// keeping track of the best metric value +// and associated parameters. + +#ifndef _itkRegularStepGradientDescentOptimizer2_txx +#define _itkRegularStepGradientDescentOptimizer2_txx + +// local includes: +#include "itkRegularStepGradientDescentOptimizer2.h" + +// ITK includes: +#include +#include +#include + +namespace itk +{ +#if 0 +} +#endif + +//---------------------------------------------------------------- +// RegularStepGradientDescentOptimizer2::RegularStepGradientDescentOptimizer2 +// +RegularStepGradientDescentOptimizer2::RegularStepGradientDescentOptimizer2() + : m_Maximize(false) + , m_Value(0.0) + , m_PreviousValue(0.0) + , m_GradientMagnitudeTolerance(1e-4) + , m_MaximumStepLength(1.0) + , m_MinimumStepLength(1e-3) + , m_CurrentStepLength(0.0) + , m_RelaxationFactor(0.5) + , m_StopCondition(MaximumNumberOfIterations) + , m_NumberOfIterations(100) + , m_CurrentIteration(0) + , m_BestValue(0.0) + , m_BackTracking(false) + , m_PickUpPaceSteps(1000000) + , log_(cerr_log()) +{ + itkDebugMacro("Constructor"); + m_CostFunction = NULL; +} + +//---------------------------------------------------------------- +// RegularStepGradientDescentOptimizer2::StartOptimization +// +// Start the optimization +// +void +RegularStepGradientDescentOptimizer2::StartOptimization() +{ + itkDebugMacro("StartOptimization"); + const unsigned int spaceDimension = m_CostFunction->GetNumberOfParameters(); + + if (m_RelaxationFactor < 0.0) + { + itkExceptionMacro(<< "Relaxation factor must be positive. Current value is " << m_RelaxationFactor); + return; + } + + if (m_RelaxationFactor >= 1.0) + { + itkExceptionMacro(<< "Relaxation factor must less than 1.0. Current value is " << m_RelaxationFactor); + return; + } + + // Make sure the scales have been set properly + const unsigned int scalesSize = this->GetScales().size(); + if (scalesSize != spaceDimension) + { + itkExceptionMacro(<< "The size of Scales is " << scalesSize + << ", but the NumberOfParameters for the CostFunction is " << spaceDimension << "."); + } + + m_CurrentStepLength = m_MaximumStepLength; + m_CurrentIteration = 0; + + m_BestValue = this->m_Maximize ? -std::numeric_limits::max() : std::numeric_limits::max(); + m_Value = m_BestValue; + m_PreviousValue = m_BestValue; + + m_Gradient = DerivativeType(spaceDimension); + m_Gradient.Fill(0.0f); + + m_PreviousGradient = DerivativeType(spaceDimension); + m_PreviousGradient.Fill(0.0f); + + this->SetCurrentPosition(GetInitialPosition()); + this->ResumeOptimization(); +} + +//---------------------------------------------------------------- +// RegularStepGradientDescentOptimizer2::ResumeOptimization +// +// Resume the optimization +// +void +RegularStepGradientDescentOptimizer2::ResumeOptimization() +{ + // keep track of the number of sequential steps that + // resulted in function optimization: + unsigned int successful_steps = 0; + + itkDebugMacro("ResumeOptimization"); + this->InvokeEvent(StartEvent()); + + m_Stop = false; + while (!m_Stop) + { + m_PreviousGradient = m_Gradient; + m_PreviousValue = m_Value; + + ParametersType currentPosition = this->GetCurrentPosition(); + m_CostFunction->GetValueAndDerivative(currentPosition, m_Value, m_Gradient); + + if ((this->m_Maximize && m_Value < m_PreviousValue) || (!this->m_Maximize && m_Value > m_PreviousValue)) + { + // relax the step size: + m_CurrentStepLength *= m_RelaxationFactor; + + if (m_BackTracking) + { + // FIXME: + *log_ << m_Value << " vs " << m_BestValue + << " -- relaxing and backtracking, new step length: " << m_CurrentStepLength << std::endl; + + // backtrack to the previous position: + this->SetCurrentPosition(m_BestParams); + currentPosition = this->GetCurrentPosition(); + m_CostFunction->GetValueAndDerivative(currentPosition, m_Value, m_Gradient); + } + else + { + // FIXME: + *log_ << m_Value << " vs " << m_PreviousValue << " -- relaxing, new step length: " << m_CurrentStepLength + << std::endl; + } + + successful_steps = 0; + } + else + { + successful_steps++; + if (successful_steps % m_PickUpPaceSteps == 0) + { + // pick up the pace after N successful steps: + m_CurrentStepLength = std::min(m_MaximumStepLength, m_CurrentStepLength / m_RelaxationFactor); + + // FIXME: + *log_ << successful_steps << " successful steps -- increasing pace, new step length: " << m_CurrentStepLength + << std::endl; + } + } + + if (m_CurrentIteration == m_NumberOfIterations) + { + m_Stop = true; + m_StopCondition = MaximumNumberOfIterations; + this->StopOptimization(); + } + + if ((this->m_Maximize && m_Value > m_BestValue) || (!this->m_Maximize && m_Value < m_BestValue)) + { + m_BestValue = m_Value; + m_BestParams = currentPosition; + } + + if (!m_Stop) + { + this->AdvanceOneStep(); + m_CurrentIteration++; + } + } +} + +//---------------------------------------------------------------- +// RegularStepGradientDescentOptimizer2::StopOptimization +// +// Stop optimization +// +void +RegularStepGradientDescentOptimizer2::StopOptimization() +{ + itkDebugMacro("StopOptimization"); + m_Stop = true; + this->InvokeEvent(EndEvent()); +} + +//---------------------------------------------------------------- +// RegularStepGradientDescentOptimizer2::AdvanceOneStep +// +// Advance one Step following the gradient direction +// +void +RegularStepGradientDescentOptimizer2::AdvanceOneStep() +{ + itkDebugMacro("AdvanceOneStep"); + + const unsigned int spaceDimension = m_CostFunction->GetNumberOfParameters(); + + DerivativeType transformedGradient(spaceDimension); + DerivativeType previousTransformedGradient(spaceDimension); + const ScalesType & scales = this->GetScales(); + + for (unsigned int i = 0; i < spaceDimension; i++) + { + transformedGradient[i] = m_Gradient[i] / scales[i]; + previousTransformedGradient[i] = m_PreviousGradient[i] / scales[i]; + } + + double magnitudeSquared = 0.0; + for (unsigned int dim = 0; dim < spaceDimension; dim++) + { + const double weighted = transformedGradient[dim]; + magnitudeSquared += weighted * weighted; + } + + const double gradientMagnitude = std::sqrt(magnitudeSquared); + if (gradientMagnitude < m_GradientMagnitudeTolerance) + { + m_StopCondition = GradientMagnitudeTolerance; + this->StopOptimization(); + return; + } + + if (m_CurrentStepLength < m_MinimumStepLength) + { + m_StopCondition = StepTooSmall; + this->StopOptimization(); + return; + } + + const double direction = this->m_Maximize ? 1.0 : -1.0; + const double step_scale = direction * m_CurrentStepLength; + + // FIXME: + // *log_ << "gradientMagnitude: " << gradientMagnitude << std::endl; + + this->StepAlongGradient(step_scale, transformedGradient); + this->InvokeEvent(IterationEvent()); +} + +//---------------------------------------------------------------- +// RegularStepGradientDescentOptimizer2::StepAlongGradient +// +// Advance one Step following the gradient direction +// This method will be overridden in non-vector spaces +// +void +RegularStepGradientDescentOptimizer2::StepAlongGradient(double factor, const DerivativeType & transformedGradient) +{ + itkDebugMacro(<< "factor = " << factor << " transformedGradient = " << transformedGradient); + + const unsigned int spaceDimension = m_CostFunction->GetNumberOfParameters(); + + ParametersType newPosition(spaceDimension); + ParametersType currentPosition = this->GetCurrentPosition(); + + for (unsigned int j = 0; j < spaceDimension; j++) + { + newPosition[j] = currentPosition[j] + transformedGradient[j] * factor; + } + + itkDebugMacro(<< "new position = " << newPosition); + this->SetCurrentPosition(newPosition); +} + +//---------------------------------------------------------------- +// RegularStepGradientDescentOptimizer2::PrintSelf +// +void +RegularStepGradientDescentOptimizer2::PrintSelf(std::ostream & os, Indent indent) const +{ + Superclass::PrintSelf(os, indent); + os << indent << "MaximumStepLength: " << m_MaximumStepLength << std::endl; + os << indent << "MinimumStepLength: " << m_MinimumStepLength << std::endl; + os << indent << "RelaxationFactor: " << m_RelaxationFactor << std::endl; + os << indent << "GradientMagnitudeTolerance: " << m_GradientMagnitudeTolerance << std::endl; + os << indent << "NumberOfIterations: " << m_NumberOfIterations << std::endl; + os << indent << "CurrentIteration: " << m_CurrentIteration << std::endl; + os << indent << "Value: " << m_Value << std::endl; + os << indent << "Maximize: " << m_Maximize << std::endl; + + if (m_CostFunction) + { + os << indent << "CostFunction: " << &m_CostFunction << std::endl; + } + else + { + os << indent << "CostFunction: " << "(None)" << std::endl; + } + + os << indent << "CurrentStepLength: " << m_CurrentStepLength << std::endl; + os << indent << "StopCondition: " << m_StopCondition << std::endl; + os << indent << "Gradient: " << m_Gradient << std::endl; +} + +#if 0 +{ +#endif +} // end namespace itk + + +#endif // _itkRegularStepGradientDescentOptimizer2_txx diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c5e76b3..660a61b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,16 +1,11 @@ itk_module_test() set(NornirTests - itkMinimalStandardRandomVariateGeneratorTest.cxx itkIRRefineGridTest.cxx ) CreateTestDriver(Nornir "${Nornir-Test_LIBRARIES}" "${NornirTests}") -itk_add_test(NAME itkMinimalStandardRandomVariateGeneratorTest - COMMAND NornirTestDriver itkMinimalStandardRandomVariateGeneratorTest - ) - itk_add_test(NAME itkIRRefineGridTest COMMAND NornirTestDriver --compare diff --git a/test/itkMinimalStandardRandomVariateGeneratorTest.cxx b/test/itkMinimalStandardRandomVariateGeneratorTest.cxx deleted file mode 100644 index a7494a6..0000000 --- a/test/itkMinimalStandardRandomVariateGeneratorTest.cxx +++ /dev/null @@ -1,37 +0,0 @@ -/*========================================================================= - * - * Copyright NumFOCUS - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0.txt - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - *=========================================================================*/ - -#include "itkMinimalStandardRandomVariateGenerator.h" - -#include "itkTestingMacros.h" -#include "itkMath.h" - -int -itkMinimalStandardRandomVariateGeneratorTest(int, char *[]) -{ - typedef itk::Statistics::MinimalStandardRandomVariateGenerator GeneratorType; - GeneratorType::Pointer generator = GeneratorType::New(); - - ITK_EXERCISE_BASIC_OBJECT_METHODS(generator, MinimalStandardRandomVariateGenerator, RandomVariateGeneratorBase); - - generator->Initialize(324); - - ITK_TEST_EXPECT_TRUE(itk::Math::FloatAlmostEqual(generator->GetVariate(), 1.35581, 4, 0.0001)); - - return EXIT_SUCCESS; -} diff --git a/wrapping/itkMinimalStandardRandomVariateGenerator.wrap b/wrapping/itkMinimalStandardRandomVariateGenerator.wrap deleted file mode 100644 index e13dd73..0000000 --- a/wrapping/itkMinimalStandardRandomVariateGenerator.wrap +++ /dev/null @@ -1 +0,0 @@ -itk_wrap_simple_class("itk::Statistics::MinimalStandardRandomVariateGenerator" POINTER)