diff --git a/Modules/Filtering/RLEImage/CMakeLists.txt b/Modules/Filtering/RLEImage/CMakeLists.txt new file mode 100644 index 000000000000..7d96cc88a269 --- /dev/null +++ b/Modules/Filtering/RLEImage/CMakeLists.txt @@ -0,0 +1,7 @@ +project(RLEImage) + +if(POLICY CMP0135) + cmake_policy(SET CMP0135 NEW) +endif() + +itk_module_impl() diff --git a/Modules/Filtering/RLEImage/LICENSE b/Modules/Filtering/RLEImage/LICENSE new file mode 100644 index 000000000000..9b259bdfcf90 --- /dev/null +++ b/Modules/Filtering/RLEImage/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Modules/Filtering/RLEImage/README.md b/Modules/Filtering/RLEImage/README.md new file mode 100644 index 000000000000..fd0a0d31ec58 --- /dev/null +++ b/Modules/Filtering/RLEImage/README.md @@ -0,0 +1,30 @@ +# RLEImage + +In-tree ITK module providing a run-length-encoded image type for +memory-efficient storage of large label volumes (segmentation masks). +The flagship class `itk::RLEImage` +is a drop-in replacement for `itk::Image` in many label-image +workflows, with iterators and filters that operate directly on the +encoded representation. + +## Origin + +Ingested from the standalone remote module +[**KitwareMedical/ITKRLEImage**](https://github.com/KitwareMedical/ITKRLEImage) +on 2026-05-04 via the v4 ingestion pipeline. The upstream repository +will be archived read-only after this PR merges; it remains +reachable at the URL above for historical reference. + +## Companion module + +`itk::MorphologicalContourInterpolation` (in +`Modules/Filtering/MorphologicalContourInterpolation/`) has an +optional `TEST_DEPENDS` on this module — the +`itkMorphologicalContourInterpolationTestWithRLEImage` test +exercises that integration. + +## References + +- Zukić D., Vicory J., McCormick M., Wisse L., Gerig G., Yushkevich P., + Aylward S. *ND morphological contour interpolation.* The Insight + Journal. 2016. diff --git a/Modules/Filtering/RLEImage/include/itkRLEImage.h b/Modules/Filtering/RLEImage/include/itkRLEImage.h new file mode 100644 index 000000000000..718ff633facd --- /dev/null +++ b/Modules/Filtering/RLEImage/include/itkRLEImage.h @@ -0,0 +1,308 @@ +/*========================================================================= + * + * 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 itkRLEImage_h +#define itkRLEImage_h + +#include +#include +#include // std::pair +#include + +namespace itk +{ +/** \class RLEImage + * + * \brief Run-Length Encoded image. + * It saves memory for label images at the expense of processing times. + * Unsuitable for ordinary images (in which case it is counterproductive). + * + * \par Details + * BufferedRegion must include complete run-length lines (along X index axis). + * BufferedRegion can be smaller than LargestPossibleRegion along other axes. + * + * It is best if pixel type and counter type have the same byte size + * (for memory alignment purposes). + * + * \par OnTheFlyCleanup + * Should same-valued segments be merged on the fly? + * On the fly merging usually provides better performance. Default: On. + * + * Acknowledgement: + * This work is supported by NIH grant R01 EB014346, "Continued development + * and maintenance of the ITK-SNAP 3D image segmentation software." + * + * \ingroup RLEImage + */ +template +class RLEImage : public itk::ImageBase +{ +public: + ITK_DISALLOW_COPY_AND_MOVE(RLEImage); + + /** Standard class type alias */ + using Self = RLEImage; + using Superclass = itk::ImageBase; + using Pointer = itk::SmartPointer; + using ConstPointer = itk::SmartPointer; + using ConstWeakPointer = itk::WeakPointer; + + /** Method for creation through the object factory. */ + itkNewMacro(Self); + + /** Run-time type information (and related methods). */ + itkOverrideGetNameOfClassMacro(RLEImage); + + /** Pixel type alias support. Used to declare pixel type in filters + * or other operations. */ + using PixelType = TPixel; + + using RLCounterType = CounterType; + + /** Typedef alias for PixelType */ + using ValueType = TPixel; + + /** First element is count of repetitions, + * second element is the pixel value. */ + using RLSegment = std::pair; + + /** A Run-Length encoded line of pixels. */ + using RLLine = std::vector; + + /** Internal Pixel representation. Used to maintain a uniform API + * with Image Adaptors and allow to keep a particular internal + * representation of data while showing a different external + * representation. */ + using InternalPixelType = RLLine; + + // using IOPixelType = PixelType; + + /** Dimension of the image. This constant is used by functions that are + * templated over image type (as opposed to being templated over pixel type + * and dimension) when they need compile time access to the dimension of + * the image. */ + static constexpr unsigned int ImageDimension = VImageDimension; + + /** Index type alias support. An index is used to access pixel values. */ + using IndexType = typename Superclass::IndexType; + using IndexValueType = typename Superclass::IndexValueType; + + /** Offset type alias support. An offset is used to access pixel values. */ + using OffsetType = typename Superclass::OffsetType; + + /** Size type alias support. A size is used to define region bounds. */ + using SizeType = typename Superclass::SizeType; + using SizeValueType = typename Superclass::SizeValueType; + + /** Direction type alias support. A matrix of direction cosines. */ + using DirectionType = typename Superclass::DirectionType; + + /** Region type alias support. A region is used to specify a subset of an image. + */ + using RegionType = typename Superclass::RegionType; + + /** Spacing type alias support. Spacing holds the size of a pixel. The + * spacing is the geometric distance between image samples. */ + using SpacingType = typename Superclass::SpacingType; + using SpacingValueType = typename Superclass::SpacingValueType; + + /** Origin type alias support. The origin is the geometric coordinates + * of the index (0,0). */ + using PointType = typename Superclass::PointType; + + /** Offset type alias (relative position between indices) */ + using OffsetValueType = typename Superclass::OffsetValueType; + + /** Allocate the image memory. The size of the image must + * already be set, e.g. by calling SetRegions(). + * Pixel values are initialized using default constructor. */ + void + Allocate(bool initialize = false) override; + + /** Restore the data object to its initial state. This means releasing + * memory. */ + void + Initialize() override + { + // Call the superclass which should initialize the BufferedRegion ivar. + Superclass::Initialize(); + m_OnTheFlyCleanup = true; + m_Buffer = BufferType::New(); + } + + /** Fill the image buffer with a value. Be sure to call Allocate() + * first. */ + void + FillBuffer(const TPixel & value); + + void + SetLargestPossibleRegion(const RegionType & region) override + { + Superclass::SetLargestPossibleRegion(region); + m_Buffer->SetLargestPossibleRegion(region.Slice(0)); + } + + void + SetBufferedRegion(const RegionType & region) override + { + Superclass::SetBufferedRegion(region); + m_Buffer->SetBufferedRegion(region.Slice(0)); + } + + using ImageBase::SetRequestedRegion; + + void + SetRequestedRegion(const RegionType & region) override + { + Superclass::SetRequestedRegion(region); + m_Buffer->SetRequestedRegion(region.Slice(0)); + } + + /** \brief Set a pixel value. + * + * Allocate() needs to have been called first -- for efficiency, + * this function does not check that the image has actually been + * allocated yet. SLOW -> Use iterators instead. */ + void + SetPixel(const IndexType & index, const TPixel & value); + + /** Set a pixel value in the given line and updates segmentRemainder + * and m_RealIndex to refer to the same pixel. + * Returns difference in line length which happens due to merging or splitting segments. + * This method is used by iterators directly. */ + int + SetPixel(RLLine & line, IndexValueType & segmentRemainder, SizeValueType & m_RealIndex, const TPixel & value); + + /** \brief Get a pixel. SLOW! Better use iterators for pixel access. */ + const TPixel & + GetPixel(const IndexType & index) const; + + ///** Get a reference to a pixel. Chaning it changes the whole RLE segment! */ + // TPixel & GetPixel(const IndexType & index); + + ///** \brief Access a pixel. Chaning it changes the whole RLE segment! */ + // TPixel & operator[](const IndexType & index) + // { + // return this->GetPixel(index); + // } + + /** \brief Access a pixel. This version can only be an rvalue. + * SLOW -> Use iterators instead. */ + const TPixel & + operator[](const IndexType & index) const + { + return this->GetPixel(index); + } + + unsigned int + GetNumberOfComponentsPerPixel() const override + { + // use the GetLength() method which works with variable length arrays, + // to make it work with as much pixel types as possible + return itk::NumericTraits::GetLength({}); + } + + /** Typedef for the internally used buffer. */ + using BufferType = typename itk::Image; + + /** We need to allow itk-style iterators to be constructed. */ + typename BufferType::Pointer + GetBuffer() + { + return m_Buffer; + } + + /** We need to allow itk-style const iterators to be constructed. */ + typename BufferType::ConstPointer + GetBuffer() const + { + return m_Buffer; + } + + /** Returns N-1-dimensional index, the remainder after 0-index is removed. */ + static inline typename BufferType::IndexType + truncateIndex(const IndexType & index); + + /** Merges adjacent segments with duplicate values. + * Automatically called when turning on OnTheFlyCleanup. */ + void + CleanUp() const; + + /** Should same-valued segments be merged on the fly? + * On the fly merging usually provides better performance. */ + bool + GetOnTheFlyCleanup() const + { + return m_OnTheFlyCleanup; + } + + /** Should same-valued segments be merged on the fly? + * On the fly merging usually provides better performance. */ + void + SetOnTheFlyCleanup(bool value) + { + if (value == m_OnTheFlyCleanup) + { + return; + } + m_OnTheFlyCleanup = value; + if (m_OnTheFlyCleanup) + { + CleanUp(); // put the image into a clean state + } + } + +protected: + RLEImage() + : itk::ImageBase() + + { + m_Buffer = BufferType::New(); + } + + void + PrintSelf(std::ostream & os, itk::Indent indent) const override; + + ~RLEImage() override = default; + /** Compute helper matrices used to transform Index coordinates to + * PhysicalPoint coordinates and back. This method is virtual and will be + * overloaded in derived classes in order to provide backward compatibility + * behavior in classes that did not used to take image orientation into + * account. */ + void + ComputeIndexToPhysicalPointMatrices() override + { + this->Superclass::ComputeIndexToPhysicalPointMatrices(); + } + + /** Merges adjacent segments with duplicate values in a single line. */ + void + CleanUpLine(RLLine & line) const; + +private: + bool m_OnTheFlyCleanup{ true }; // should same-valued segments be merged on the fly + + /** Memory for the current buffer. */ + mutable typename BufferType::Pointer m_Buffer; +}; +} // namespace itk + +#ifndef ITK_MANUAL_INSTANTIATION +# include "itkRLEImage.hxx" +#endif + +#endif // itkRLEImage_h diff --git a/Modules/Filtering/RLEImage/include/itkRLEImage.hxx b/Modules/Filtering/RLEImage/include/itkRLEImage.hxx new file mode 100644 index 000000000000..b70cca5870b6 --- /dev/null +++ b/Modules/Filtering/RLEImage/include/itkRLEImage.hxx @@ -0,0 +1,282 @@ +/*========================================================================= + * + * 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 itkRLEImage_hxx +#define itkRLEImage_hxx + +#include "itkImageRegionIterator.h" // for underlying buffer + +// include all specializations of iterators and filters +#include "itkRLEImageRegionIterator.h" +#include "itkRLEImageScanlineIterator.h" +#include "itkRLERegionOfInterestImageFilter.h" + +namespace itk +{ +template +inline auto +RLEImage::truncateIndex(const IndexType & index) -> typename BufferType::IndexType +{ + typename BufferType::IndexType result; + for (IndexValueType i = 0; i < VImageDimension - 1; i++) + { + result[i] = index[i + 1]; + } + return result; +} + +template +void +RLEImage::Allocate(bool itkNotUsed(initialize)) +{ + itkAssertOrThrowMacro(this->GetBufferedRegion().GetSize(0) == this->GetLargestPossibleRegion().GetSize(0), + "BufferedRegion must contain complete run-length lines!"); + itkAssertOrThrowMacro(this->GetLargestPossibleRegion().GetSize(0) <= + itk::SizeValueType(std::numeric_limits::max()), + "CounterType is not large enough to support image's X dimension!"); + this->ComputeOffsetTable(); + // SizeValueType num = static_cast(this->GetOffsetTable()[VImageDimension]); + m_Buffer->Allocate(false); + // if (initialize) //there is assumption that the image is fully formed after a call to allocate + { + RLSegment segment(CounterType(this->GetBufferedRegion().GetSize(0)), TPixel()); + RLLine line(1); + line[0] = segment; + m_Buffer->FillBuffer(line); + } +} + +template +void +RLEImage::FillBuffer(const TPixel & value) +{ + RLSegment segment(CounterType(this->GetBufferedRegion().GetSize(0)), value); + RLLine line(1); + + line[0] = segment; + m_Buffer->FillBuffer(line); +} + +template +void +RLEImage::CleanUpLine(RLLine & line) const +{ + CounterType x = 0; + RLLine out; + + out.reserve(this->GetLargestPossibleRegion().GetSize(0)); + + do + { + out.push_back(line[x]); + while (++x < line.size() && line[x].second == line[x - 1].second) + { + out.back().first += line[x].first; + } + } while (x < line.size()); + + out.swap(line); +} + +template +void +RLEImage::CleanUp() const +{ + assert(m_Buffer->GetBufferedRegion().GetNumberOfPixels() > 0); + if (this->GetLargestPossibleRegion().GetSize(0) == 0) + { + return; + } + + itk::ImageRegionIterator it(m_Buffer, m_Buffer->GetBufferedRegion()); + while (!it.IsAtEnd()) + { + CleanUpLine(it.Value()); + ++it; + } +} + +template +int +RLEImage::SetPixel(RLLine & line, + IndexValueType & segmentRemainder, + SizeValueType & m_RealIndex, + const TPixel & value) +{ + // complete Run-Length Lines have to be buffered + itkAssertOrThrowMacro(this->GetBufferedRegion().GetSize(0) == this->GetLargestPossibleRegion().GetSize(0), + "BufferedRegion must contain complete run-length lines!"); + if (line[m_RealIndex].second == value) // already correct value + { + return 0; + } + else if (line[m_RealIndex].first == 1) // single pixel segment + { + line[m_RealIndex].second = value; + if (m_OnTheFlyCleanup) // now see if we can merge it into adjacent segments + { + if (m_RealIndex > 0 && m_RealIndex < line.size() - 1 && line[m_RealIndex + 1].second == value && + line[m_RealIndex - 1].second == value) + { + // merge these 3 segments + line[m_RealIndex - 1].first += 1 + line[m_RealIndex + 1].first; + segmentRemainder += line[m_RealIndex + 1].first; + line.erase(line.begin() + m_RealIndex, line.begin() + m_RealIndex + 2); + m_RealIndex--; + return -2; + } + if (m_RealIndex > 0 && line[m_RealIndex - 1].second == value) + { + // merge into previous + line[m_RealIndex - 1].first++; + line.erase(line.begin() + m_RealIndex); + m_RealIndex--; + assert(segmentRemainder == 1); + return -1; + } + else if (m_RealIndex < line.size() - 1 && line[m_RealIndex + 1].second == value) + { + // merge into next + segmentRemainder = ++(line[m_RealIndex + 1].first); + line.erase(line.begin() + m_RealIndex); + return -1; + } + } + return 0; + } + else if (segmentRemainder == 1 && m_RealIndex < line.size() - 1 && line[m_RealIndex + 1].second == value) + { + // shift this pixel to next segment + line[m_RealIndex].first--; + segmentRemainder = ++(line[m_RealIndex + 1].first); + m_RealIndex++; + return 0; + } + else if (m_RealIndex > 0 && segmentRemainder == line[m_RealIndex].first && line[m_RealIndex - 1].second == value) + { + // shift this pixel to previous segment + line[m_RealIndex].first--; + line[m_RealIndex - 1].first++; + m_RealIndex--; + segmentRemainder = 1; + return 0; + } + else if (segmentRemainder == 1) // insert after + { + line[m_RealIndex].first--; + line.insert(line.begin() + m_RealIndex + 1, RLSegment(1, value)); + m_RealIndex++; + return +1; + } + else if (segmentRemainder == line[m_RealIndex].first) // insert before + { + line[m_RealIndex].first--; + line.insert(line.begin() + m_RealIndex, RLSegment(1, value)); + segmentRemainder = 1; + return +1; + } + else // general case: split a segment into 3 segments + { + // first take care of values + line.insert(line.begin() + m_RealIndex + 1, 2, RLSegment(1, value)); + line[m_RealIndex + 2].second = line[m_RealIndex].second; + + // now take care of counts + line[m_RealIndex].first -= segmentRemainder; + line[m_RealIndex + 2].first = segmentRemainder - 1; + m_RealIndex++; + segmentRemainder = 1; + return +2; + } +} // >::SetPixel + +template +void +RLEImage::SetPixel(const IndexType & index, const TPixel & value) +{ + // complete Run-Length Lines have to be buffered + itkAssertOrThrowMacro(this->GetBufferedRegion().GetSize(0) == this->GetLargestPossibleRegion().GetSize(0), + "BufferedRegion must contain complete run-length lines!"); + IndexValueType bri0 = this->GetBufferedRegion().GetIndex(0); + typename BufferType::IndexType bi = truncateIndex(index); + RLLine & line = m_Buffer->GetPixel(bi); + IndexValueType t = 0; + for (SizeValueType x = 0; x < line.size(); x++) + { + t += line[x].first; + if (t > index[0] - bri0) + { + t -= index[0] - bri0; // we need to supply a reference + SetPixel(line, t, x, value); + return; + } + } + throw itk::ExceptionObject(__FILE__, __LINE__, "Reached past the end of Run-Length line!", __FUNCTION__); +} // >::SetPixel + +template +const TPixel & +RLEImage::GetPixel(const IndexType & index) const +{ + // complete Run-Length Lines have to be buffered + itkAssertOrThrowMacro(this->GetBufferedRegion().GetSize(0) == this->GetLargestPossibleRegion().GetSize(0), + "BufferedRegion must contain complete run-length lines!"); + IndexValueType bri0 = this->GetBufferedRegion().GetIndex(0); + typename BufferType::IndexType bi = truncateIndex(index); + RLLine & line = m_Buffer->GetPixel(bi); + IndexValueType t = 0; + for (SizeValueType x = 0; x < line.size(); x++) + { + t += line[x].first; + if (t > index[0] - bri0) + { + return line[x].second; + } + } + throw itk::ExceptionObject(__FILE__, __LINE__, "Reached past the end of Run-Length line!", __FUNCTION__); +} // >::GetPixel + +template +void +RLEImage::PrintSelf(std::ostream & os, itk::Indent indent) const +{ + Superclass::PrintSelf(os, indent); + os << indent << "Internal image (for storage of RLLine-s): " << std::endl; + m_Buffer->Print(os, indent.GetNextIndent()); + + itk::SizeValueType c = 0; + itk::SizeValueType pixelCount = this->GetOffsetTable()[VImageDimension]; + + itk::ImageRegionConstIterator it(m_Buffer, m_Buffer->GetBufferedRegion()); + while (!it.IsAtEnd()) + { + c += it.Get().capacity(); + ++it; + } + + itk::SizeValueType memUsed = + c * sizeof(RLSegment) + sizeof(std::vector) * (pixelCount / this->GetOffsetTable()[1]); + double cr = double(memUsed) / (pixelCount * sizeof(PixelType)); + + os << indent << "OnTheFlyCleanup: " << (m_OnTheFlyCleanup ? "On" : "Off") << std::endl; + os << indent << "RLSegment count: " << c << std::endl; + int prec = os.precision(3); + os << indent << "Compressed size in relation to original size: " << cr * 100 << "%" << std::endl; + os.precision(prec); +} // >::PrintSelf +} // end namespace itk +#endif // itkRLEImage_hxx diff --git a/Modules/Filtering/RLEImage/include/itkRLEImageConstIterator.h b/Modules/Filtering/RLEImage/include/itkRLEImageConstIterator.h new file mode 100644 index 000000000000..d12272e5fe85 --- /dev/null +++ b/Modules/Filtering/RLEImage/include/itkRLEImageConstIterator.h @@ -0,0 +1,480 @@ +/*========================================================================= + * + * 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 itkRLEImageConstIterator_h +#define itkRLEImageConstIterator_h + +#include "itkImage.h" +#include "itkImageConstIterator.h" +#include "itkImageConstIteratorWithIndex.h" +#include "itkImageConstIteratorWithOnlyIndex.h" +#include "itkImageRegionIterator.h" +#include "itkIndex.h" +#include "itkNumericTraits.h" +#include "itkRLEImage.h" + +class MultiLabelMeshPipeline; + +namespace itk +{ +/** \class ImageConstIterator + * \brief A multi-dimensional image iterator templated over image type. + * Specialized for RLEImage. + * \ingroup RLEImage + * \ingroup ITKCommon + */ + +template +class ImageConstIterator> +{ + friend class ::MultiLabelMeshPipeline; + +public: + /** Standard class type alias. */ + using Self = ImageConstIterator; + + /** Dimension of the image the iterator walks. This constant is needed so + * functions that are templated over image iterator type (as opposed to + * being templated over pixel type and dimension) can have compile time + * access to the dimension of the image that the iterator walks. */ + static constexpr unsigned int ImageIteratorDimension = VImageDimension; + + /** Run-time type information (and related methods). */ + itkVirtualGetNameOfClassMacro(ImageConstIterator); + + /** Image type alias support. */ + using ImageType = RLEImage; + + /** Run-Length Line (we iterate along it). */ + using RLLine = typename ImageType::RLLine; + + /** Buffer Type used. */ + using BufferType = typename ImageType::BufferType; + + /** Type for the internal buffer iterator. */ + using BufferIterator = ImageRegionIterator; + + /** Index type alias support. */ + using IndexType = typename ImageType::IndexType; + + /** Index type alias support. */ + using IndexValueType = typename ImageType::IndexValueType; + + /** Size type alias support. */ + using SizeType = typename ImageType::SizeType; + + /** Offset type alias support. */ + using OffsetType = typename ImageType::OffsetType; + + /** Region type alias support. */ + using RegionType = typename ImageType::RegionType; + + /** Internal Pixel Type */ + using InternalPixelType = typename ImageType::InternalPixelType; + + /** External Pixel Type */ + using PixelType = typename ImageType::PixelType; + + /** Default Constructor. Need to provide a default constructor since we + * provide a copy constructor. */ + ImageConstIterator() + : m_RunLengthLine(nullptr) + , m_Buffer(nullptr) + { + m_Image = nullptr; + m_Index0 = 0; + m_BeginIndex0 = 0; + m_EndIndex0 = 0; + m_RealIndex = 0; + m_SegmentRemainder = 0; + } + + /** Default Destructor. */ + virtual ~ImageConstIterator() = default; + /** Copy Constructor. The copy constructor is provided to make sure the + * handle to the image is properly reference counted. */ + ImageConstIterator(const Self & it) + : m_Buffer(const_cast(it.GetImage())->GetBuffer()) + { + m_RunLengthLine = it.m_RunLengthLine; + m_Image = it.m_Image; // copy the smart pointer + m_Index0 = it.m_Index0; + this->m_BI = it.m_BI; + + m_RealIndex = it.m_RealIndex; + m_SegmentRemainder = it.m_SegmentRemainder; + m_BeginIndex0 = it.m_BeginIndex0; + m_EndIndex0 = it.m_EndIndex0; + } + + /** Constructor establishes an iterator to walk a particular image and a + * particular region of that image. */ + ImageConstIterator(const ImageType * ptr, const RegionType & region) + : m_Buffer(const_cast(ptr)->GetBuffer()) + { + m_Image = ptr; + SetRegion(region); + } + + /** operator= is provided to make sure the handle to the image is properly + * reference counted. */ + Self & + operator=(const Self & it) + { + if (this != &it) + { + m_Buffer = it.m_Buffer; + m_RunLengthLine = it.m_RunLengthLine; + m_Image = it.m_Image; // copy the smart pointer + m_Index0 = it.m_Index0; + m_BI = it.m_BI; + + m_RealIndex = it.m_RealIndex; + m_SegmentRemainder = it.m_SegmentRemainder; + m_BeginIndex0 = it.m_BeginIndex0; + m_EndIndex0 = it.m_EndIndex0; + } + return *this; + } + + /** Set the region of the image to iterate over. */ + virtual void + SetRegion(const RegionType & region) + { + // m_Region = region; + + if (region.GetNumberOfPixels() > 0) // If region is non-empty + { + const RegionType & bufferedRegion = m_Image->GetBufferedRegion(); + itkAssertOrThrowMacro((bufferedRegion.IsInside(region)), + "Region " << region << " is outside of buffered region " << bufferedRegion); + } + + m_BI = BufferIterator(m_Buffer, region.Slice(0)); + m_Index0 = region.GetIndex(0); + m_BeginIndex0 = m_Index0 - m_Image->GetBufferedRegion().GetIndex(0); + m_EndIndex0 = m_BeginIndex0 + region.GetSize(0); + SetIndexInternal(m_BeginIndex0); // sets m_RealIndex and m_SegmentRemainder + } + + /** Get the dimension (size) of the index. */ + static unsigned int + GetImageIteratorDimension() + { + return VImageDimension; + } + + /** Comparison operator. Two iterators are the same if they "point to" the + * same memory location */ + bool + operator!=(const Self & it) const + { + return m_BI != it.m_BI || m_Index0 + m_BeginIndex0 != it.m_Index0 + it.m_BeginIndex0; + } + + /** Comparison operator. Two iterators are the same if they "point to" the + * same memory location */ + bool + operator==(const Self & it) const + { + return m_BI == it.m_BI && m_Index0 + m_BeginIndex0 == it.m_Index0 + it.m_BeginIndex0; + } + + /** Comparison operator. An iterator is "less than" another if it "points to" + * a lower memory location. */ + bool + operator<=(const Self & it) const + { + if (m_BI < it.m_BI) + { + return true; + } + else if (m_BI > it.m_BI) + { + return false; + } + return m_Index0 + m_BeginIndex0 <= it.m_Index0 + it.m_BeginIndex0; + } + + /** Comparison operator. An iterator is "less than" another if it "points to" + * a lower memory location. */ + bool + operator<(const Self & it) const + { + if (m_BI < it.m_BI) + { + return true; + } + else if (m_BI > it.m_BI) + { + return false; + } + return m_Index0 + m_BeginIndex0 < it.m_Index0 + it.m_BeginIndex0; + } + + /** Comparison operator. An iterator is "greater than" another if it + * "points to" a higher location. */ + bool + operator>=(const Self & it) const + { + if (m_BI > it.m_BI) + { + return true; + } + else if (m_BI < it.m_BI) + { + return false; + } + return m_Index0 + m_BeginIndex0 >= it.m_Index0 + it.m_BeginIndex0; + } + + /** Comparison operator. An iterator is "greater than" another if it + * "points to" a higher location. */ + bool + operator>(const Self & it) const + { + if (m_BI > it.m_BI) + { + return true; + } + else if (m_BI < it.m_BI) + { + return false; + } + return m_Index0 + m_BeginIndex0 > it.m_Index0 + it.m_BeginIndex0; + } + + /** Get the index. This provides a read only copy of the index. */ + const IndexType + GetIndex() const + { + IndexType indR(m_Image->GetBufferedRegion().GetIndex()); + + indR[0] += m_Index0; + typename BufferType::IndexType bufInd = m_BI.GetIndex(); + for (IndexValueType i = 1; i < VImageDimension; i++) + { + indR[i] = bufInd[i - 1]; + } + return indR; + } + + /** Sets the image index. No bounds checking is performed. */ + virtual void + SetIndex(const IndexType & ind) + { + typename BufferType::IndexType bufInd; + for (IndexValueType i = 1; i < VImageDimension; i++) + { + bufInd[i - 1] = ind[i]; + } + m_BI.SetIndex(bufInd); + SetIndexInternal(ind[0] - m_Image->GetBufferedRegion().GetIndex(0)); + } + + /** Get the region that this iterator walks. ImageConstIterators know the + * beginning and the end of the region of the image to iterate over. */ + const RegionType + GetRegion() const + { + RegionType r; + + r.SetIndex(0, m_BeginIndex0 + m_Image->GetBufferedRegion().GetIndex(0)); + r.SetSize(0, m_EndIndex0 - m_BeginIndex0); + typename BufferType::RegionType ir = m_BI.GetRegion(); + for (IndexValueType i = 1; i < VImageDimension; i++) + { + r.SetIndex(i, ir.GetIndex(i - 1)); + r.SetSize(i, ir.GetSize(i - 1)); + } + return r; + } + + /** Get the image that this iterator walks. */ + const ImageType * + GetImage() const + { + return m_Image.GetPointer(); + } + + /** Get the pixel value */ + PixelType + Get() const + { + return Value(); + } + + /** Return a const reference to the pixel + * This method will provide the fastest access to pixel + * data, but it will NOT support ImageAdaptors. */ + const PixelType & + Value() const + { + RLLine & line = const_cast(this)->m_BI.Value(); + + return line[m_RealIndex].second; + } + + /** Move an iterator to the beginning of the region. "Begin" is + * defined as the first pixel in the region. */ + void + GoToBegin() + { + m_BI.GoToBegin(); + SetIndexInternal(m_BeginIndex0); + } + + /** Move an iterator to the end of the region. "End" is defined as + * one pixel past the last pixel of the region. */ + void + GoToEnd() + { + m_BI.GoToEnd(); + m_Index0 = m_BeginIndex0; + } + + /** Is the iterator at the beginning of the region? "Begin" is defined + * as the first pixel in the region. */ + bool + IsAtBegin() const + { + return m_Index0 == m_BeginIndex0 && m_BI.IsAtBegin(); + } + + /** Is the iterator at the end of the region? "End" is defined as one + * pixel past the last pixel of the region. */ + bool + IsAtEnd() const + { + return m_Index0 == m_BeginIndex0 && m_BI.IsAtEnd(); + } + +protected: // made protected so other iterators can access + /** Set the internal index, m_RealIndex and m_SegmentRemainder. */ + virtual void + SetIndexInternal(const IndexValueType ind0) + { + m_Index0 = ind0; + m_RunLengthLine = &m_BI.Value(); + + CounterType t = 0; + SizeValueType x = 0; + for (; x < (*m_RunLengthLine).size(); x++) + { + t += (*m_RunLengthLine)[x].first; + if (t > m_Index0) + { + break; + } + } + m_RealIndex = x; + m_SegmentRemainder = t - m_Index0; + } // SetIndexInternal + + typename ImageType::ConstWeakPointer m_Image; + + IndexValueType m_Index0; // index into the RLLine + + const RLLine * m_RunLengthLine; + + mutable SizeValueType m_RealIndex; // index into line's segment + mutable IndexValueType m_SegmentRemainder; // how many pixels remain in current segment + + IndexValueType m_BeginIndex0; // index to first pixel in region in relation to buffer start + IndexValueType m_EndIndex0; // index to one pixel past last pixel in region in relation to buffer start + BufferIterator m_BI; // iterator over internal buffer image + + typename BufferType::Pointer m_Buffer; +}; + +template +class ImageConstIteratorWithIndex> + : public ImageConstIterator> +{ + // just inherit constructors + +public: + /** Image type alias support. */ + using ImageType = RLEImage; + + using RegionType = typename itk::ImageConstIterator>::RegionType; + + void + GoToReverseBegin() + { + this->m_BI.GoToReverseBegin(); + this->m_Index0 = this->m_EndIndex0 - 1; + SetIndexInternal(this->m_Index0); + } + + bool + IsAtReverseEnd() + { + return this->m_BI.IsAtReverseEnd(); + } + + /** Default Constructor. Need to provide a default constructor since we + * provide a copy constructor. */ + ImageConstIteratorWithIndex() + : ImageConstIterator() + {} + + /** Copy Constructor. The copy constructor is provided to make sure the + * handle to the image is properly reference counted. */ + ImageConstIteratorWithIndex(const ImageConstIteratorWithIndex & it) { ImageConstIterator::operator=(it); } + + /** Constructor establishes an iterator to walk a particular image and a + * particular region of that image. */ + ImageConstIteratorWithIndex(const ImageType * ptr, const RegionType & region) + : ImageConstIterator(ptr, region) + {} +}; // no additional implementation required + +template +class ImageConstIteratorWithOnlyIndex> + : public ImageConstIteratorWithIndex> +{ + // just inherit constructors + +public: + /** Image type alias support. */ + using ImageType = RLEImage; + + using RegionType = typename itk::ImageConstIterator>::RegionType; + + /** Default Constructor. Need to provide a default constructor since we + * provide a copy constructor. */ + ImageConstIteratorWithOnlyIndex() + : ImageConstIterator() + {} + + /** Copy Constructor. The copy constructor is provided to make sure the + * handle to the image is properly reference counted. */ + ImageConstIteratorWithOnlyIndex(const ImageConstIteratorWithOnlyIndex & it) + { + ImageConstIterator::operator=(it); + } + + /** Constructor establishes an iterator to walk a particular image and a + * particular region of that image. */ + ImageConstIteratorWithOnlyIndex(const ImageType * ptr, const RegionType & region) + : ImageConstIterator(ptr, region) + {} +}; // no additional implementation required +} // end namespace itk + +#endif // itkRLEImageConstIterator_h diff --git a/Modules/Filtering/RLEImage/include/itkRLEImageIterator.h b/Modules/Filtering/RLEImage/include/itkRLEImageIterator.h new file mode 100644 index 000000000000..b1bfbcbfb376 --- /dev/null +++ b/Modules/Filtering/RLEImage/include/itkRLEImageIterator.h @@ -0,0 +1,179 @@ +/*========================================================================= + * + * 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 itkRLEImageIterator_h +#define itkRLEImageIterator_h + +#include "itkImageIteratorWithIndex.h" +#include "itkRLEImageConstIterator.h" + +namespace itk +{ +/** + * \class ImageIterator + * \brief A multi-dimensional iterator templated over image type. + * + * Read-write access. Specialized for RLEImage. + * \ingroup RLEImage + * \ingroup ITKCommon + */ + +template +class ImageIterator> + : public ImageConstIterator> +{ +public: + /** Standard class type alias. */ + using Self = ImageIterator; + + /** Dimension of the image the iterator walks. This constant is needed so + * functions that are templated over image iterator type (as opposed to + * being templated over pixel type and dimension) can have compile time + * access to the dimension of the image that the iterator walks. */ + static constexpr unsigned int ImageIteratorDimension = VImageDimension; + + /** Define the superclass */ + using Superclass = ImageConstIterator>; + + /** Inherit types from the superclass */ + using IndexType = typename Superclass::IndexType; + using SizeType = typename Superclass::SizeType; + using OffsetType = typename Superclass::OffsetType; + using RegionType = typename Superclass::RegionType; + using ImageType = typename Superclass::ImageType; + using InternalPixelType = typename Superclass::InternalPixelType; + using PixelType = typename Superclass::PixelType; + + /** Default Constructor. Need to provide a default constructor since we + * provide a copy constructor. */ + ImageIterator() = default; + /** Default Destructor */ + ~ImageIterator() override = default; + /** Copy Constructor. The copy constructor is provided to make sure the + * handle to the image is properly reference counted. */ + ImageIterator(const Self & it) + : ImageConstIterator(it) + {} + + /** Constructor establishes an iterator to walk a particular image and a + * particular region of that image. */ + ImageIterator(ImageType * ptr, const RegionType & region) + : ImageConstIterator(ptr, region) + {} + + /** operator= is provided to make sure the handle to the image is properly + * reference counted. */ + Self & + operator=(const Self & it) + { + ImageConstIterator::operator=(it); + return *this; + } + + /** Set the pixel value. + * Changing the RLE structure invalidates all other iterators (except this one). */ + void + Set(const PixelType & value) const + { + const_cast(this->m_Image.GetPointer()) + ->SetPixel(*const_cast(this->m_RunLengthLine), + this->m_SegmentRemainder, + this->m_RealIndex, + value); + } + + ///** Return a reference to the pixel + // * Setting this value would change value of the whole run-length segment. + // * If we wanted to safely enable it, + // * we would isolate this pixel into its own segment. */ + // PixelType & Value() + // { + // return m_Buffer[m_Index[2]][m_Index[1]][m_RealIndex].second; + // } + + /** Get the image that this iterator walks. */ + ImageType * + GetImage() const + { + // const_cast is needed here because m_Image is declared as a const pointer + // in the base class which is the ConstIterator. + return const_cast(this->m_Image.GetPointer()); + } + +protected: + /** This constructor is declared protected in order to enforce + const-correctness */ + ImageIterator(const ImageConstIterator & it) + : ImageConstIterator(it) + {} + Self & + operator=(const ImageConstIterator & it) + { + ImageConstIterator::operator=(it); + return *this; + } +}; + +template +class ImageIteratorWithIndex> + : public ImageConstIteratorWithIndex> +{ +public: + using ImageType = RLEImage; + + using RegionType = typename itk::ImageConstIterator>::RegionType; + + /** Default Constructor. Need to provide a default constructor since we + * provide a copy constructor. */ + ImageIteratorWithIndex() + : ImageConstIteratorWithIndex() + {} + + /** Copy Constructor. The copy constructor is provided to make sure the + * handle to the image is properly reference counted. */ + ImageIteratorWithIndex(const ImageIteratorWithIndex & it) { ImageIterator::operator=(it); } + + /** Constructor establishes an iterator to walk a particular image and a + * particular region of that image. */ + ImageIteratorWithIndex(const ImageType * ptr, const RegionType & region) + : ImageConstIteratorWithIndex(ptr, region) + {} + + /** Set the pixel value. + * Changing the RLE structure invalidates all other iterators (except this one). */ + void + Set(const TPixel & value) const + { + const_cast(this->m_Image.GetPointer()) + ->SetPixel(*const_cast(this->m_RunLengthLine), + this->m_SegmentRemainder, + this->m_RealIndex, + value); + } + + /** Get the image that this iterator walks. */ + ImageType * + GetImage() const + { + // const_cast is needed here because m_Image is declared as a const pointer + // in the base class which is the ConstIterator. + return const_cast(this->m_Image.GetPointer()); + } +}; // no additional implementation required +} // end namespace itk + +#endif // itkRLEImageIterator_h diff --git a/Modules/Filtering/RLEImage/include/itkRLEImageRegionConstIterator.h b/Modules/Filtering/RLEImage/include/itkRLEImageRegionConstIterator.h new file mode 100644 index 000000000000..ed847242ad99 --- /dev/null +++ b/Modules/Filtering/RLEImage/include/itkRLEImageRegionConstIterator.h @@ -0,0 +1,261 @@ +/*========================================================================= + * + * 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 itkRLEImageRegionConstIterator_h +#define itkRLEImageRegionConstIterator_h + +#include "itkImageRegionConstIterator.h" +#include "itkImageRegionConstIteratorWithIndex.h" +#include "itkImageRegionConstIteratorWithOnlyIndex.h" +#include "itkRLEImageConstIterator.h" + +class MultiLabelMeshPipeline; + +namespace itk +{ +/** \class ImageRegionConstIterator + * \brief A multi-dimensional iterator templated over image type that walks a + * region of pixels. + * + * ImageRegionConstIterator provides read-only access to image data. + * It is the base class for the read/write access ImageRegionIterator. + * Specialized for RLEImage. + * + * \ingroup RLEImage + * \ingroup ITKCommon + */ +template +class ImageRegionConstIterator> + : public ImageConstIterator> +{ + friend class ::MultiLabelMeshPipeline; + +public: + /** Standard class type alias. */ + using Self = ImageRegionConstIterator>; + using Superclass = ImageConstIterator>; + + /** Dimension of the image that the iterator walks. This constant is needed so + * functions that are templated over image iterator type (as opposed to + * being templated over pixel type and dimension) can have compile time + * access to the dimension of the image that the iterator walks. */ + static constexpr unsigned int ImageIteratorDimension = VImageDimension; + + /** + * Index type alias support While these were already typdef'ed in the superclass, + * they need to be redone here for this subclass to compile properly with gcc. + */ + /** Types inherited from the Superclass */ + using IndexType = typename Superclass::IndexType; + using SizeType = typename Superclass::SizeType; + using OffsetType = typename Superclass::OffsetType; + using RegionType = typename Superclass::RegionType; + using ImageType = typename Superclass::ImageType; + using InternalPixelType = typename Superclass::InternalPixelType; + using PixelType = typename Superclass::PixelType; + + /** Run-time type information (and related methods). */ + itkOverrideGetNameOfClassMacro(ImageRegionConstIterator); + + /** Default constructor. Needed since we provide a cast constructor. */ + ImageRegionConstIterator() + : ImageConstIterator() + {} + + /** Constructor establishes an iterator to walk a particular image and a + * particular region of that image. */ + ImageRegionConstIterator(const ImageType * ptr, const RegionType & region) + : ImageConstIterator(ptr, region) + {} + + /** Constructor that can be used to cast from an ImageIterator to an + * ImageRegionConstIterator. Many routines return an ImageIterator, but for a + * particular task, you may want an ImageRegionConstIterator. Rather than + * provide overloaded APIs that return different types of Iterators, itk + * returns ImageIterators and uses constructors to cast from an + * ImageIterator to a ImageRegionConstIterator. */ + ImageRegionConstIterator(const ImageIterator & it) { ImageConstIterator::operator=(it); } + + /** Constructor that can be used to cast from an ImageConstIterator to an + * ImageRegionConstIterator. Many routines return an ImageIterator, but for a + * particular task, you may want an ImageRegionConstIterator. Rather than + * provide overloaded APIs that return different types of Iterators, itk + * returns ImageIterators and uses constructors to cast from an + * ImageIterator to a ImageRegionConstIterator. */ + ImageRegionConstIterator(const ImageConstIterator & it) { ImageConstIterator::operator=(it); } + + /** Increment (prefix) the fastest moving dimension of the iterator's index. + * This operator will constrain the iterator within the region (i.e. the + * iterator will automatically wrap from the end of the row of the region + * to the beginning of the next row of the region) up until the iterator + * tries to moves past the last pixel of the region. Here, the iterator + * will be set to be one pixel past the end of the region. + * \sa operator++(int) */ + Self & + operator++() + { + this->m_Index0++; + + if (this->m_Index0 >= this->m_EndIndex0) + { + ++(this->m_BI); + if (!this->m_BI.IsAtEnd()) + { + this->SetIndexInternal(this->m_BeginIndex0); + } + else + { + this->m_Index0 = this->m_BeginIndex0; + } + return *this; + } + + this->m_SegmentRemainder--; + if (this->m_SegmentRemainder > 0) + { + return *this; + } + + this->m_RealIndex++; + this->m_SegmentRemainder = (*this->m_RunLengthLine)[this->m_RealIndex].first; + return *this; + } // ++ + + /** Decrement (prefix) the fastest moving dimension of the iterator's index. + * This operator will constrain the iterator within the region (i.e. the + * iterator will automatically wrap from the beginning of the row of the region + * to the end of the next row of the region) up until the iterator + * tries to moves past the first pixel of the region. Here, the iterator + * will be set to be one pixel past the beginning of the region. + * \sa operator--(int) */ + Self & + operator--() + { + this->m_Index0--; + + if (this->m_Index0 < this->m_BeginIndex0) + { + --(this->m_BI); + this->SetIndexInternal(this->m_EndIndex0 - 1); + return *this; + } + + this->m_SegmentRemainder++; + if (this->m_SegmentRemainder <= (*this->m_RunLengthLine)[this->m_RealIndex].first) + { + return *this; + } + + this->m_RealIndex--; + this->m_SegmentRemainder = 1; + return *this; + } // -- +}; + +template +class ImageRegionConstIteratorWithIndex> + : public ImageRegionConstIterator> +{ +public: + using ImageType = RLEImage; + + using RegionType = typename itk::ImageConstIterator>::RegionType; + + /** Default constructor. Needed since we provide a cast constructor. */ + ImageRegionConstIteratorWithIndex() + : ImageRegionConstIterator() + {} + + /** Constructor establishes an iterator to walk a particular image and a + * particular region of that image. */ + ImageRegionConstIteratorWithIndex(const ImageType * ptr, const RegionType & region) + : ImageRegionConstIterator(ptr, region) + {} + + void + GoToReverseBegin() + { + this->m_BI.GoToEnd(); // after last pixel + --(this->m_BI); // go to last valid pixel + this->m_Index0 = this->m_EndIndex0 - 1; + this->SetIndexInternal(this->m_Index0); // valid index required + } + + bool + IsAtReverseEnd() + { + return (this->m_Index0 == this->m_BeginIndex0) && this->m_BI.IsAtBegin(); + } + + /** Constructor that can be used to cast from an ImageIterator to an + * ImageRegionConstIterator. Many routines return an ImageIterator, but for a + * particular task, you may want an ImageRegionConstIterator. Rather than + * provide overloaded APIs that return different types of Iterators, itk + * returns ImageIterators and uses constructors to cast from an + * ImageIterator to a ImageRegionConstIterator. */ + ImageRegionConstIteratorWithIndex(const ImageIterator & it) + { + ImageRegionConstIterator::operator=(it); + } +}; // no additional implementation required + +template +class ImageRegionConstIteratorWithOnlyIndex> + : public ImageRegionConstIteratorWithIndex> +{ + // just inherit constructors + +public: + using ImageType = RLEImage; + + using RegionType = typename itk::ImageConstIterator>::RegionType; + + /** Default constructor. Needed since we provide a cast constructor. */ + ImageRegionConstIteratorWithOnlyIndex() + : ImageRegionConstIterator() + {} + + /** Constructor establishes an iterator to walk a particular image and a + * particular region of that image. */ + ImageRegionConstIteratorWithOnlyIndex(const ImageType * ptr, const RegionType & region) + : ImageRegionConstIteratorWithIndex(ptr, region) + {} + + /** Constructor that can be used to cast from an ImageIterator to an + * ImageRegionConstIterator. Many routines return an ImageIterator, but for a + * particular task, you may want an ImageRegionConstIterator. Rather than + * provide overloaded APIs that return different types of Iterators, itk + * returns ImageIterators and uses constructors to cast from an + * ImageIterator to a ImageRegionConstIterator. */ + ImageRegionConstIteratorWithOnlyIndex(const ImageIterator & it) + { + ImageRegionConstIterator::operator=(it); + } +}; // no additional implementation required + +// CTAD deduction guide. C++17 generates implicit deduction guides only from +// the primary template; the RLEImage partial specialization needs an explicit +// guide so generic code calling `ImageRegionConstIterator it(img, region);` +// (e.g. itkImageAlgorithm.hxx) compiles when img is an RLEImage. +template +ImageRegionConstIterator(const RLEImage *, + const typename RLEImage::RegionType &) + -> ImageRegionConstIterator>; + +} // end namespace itk + +#endif // itkRLEImageRegionConstIterator_h diff --git a/Modules/Filtering/RLEImage/include/itkRLEImageRegionIterator.h b/Modules/Filtering/RLEImage/include/itkRLEImageRegionIterator.h new file mode 100644 index 000000000000..d4b84b056518 --- /dev/null +++ b/Modules/Filtering/RLEImage/include/itkRLEImageRegionIterator.h @@ -0,0 +1,174 @@ +/*========================================================================= + * + * 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 itkRLEImageRegionIterator_h +#define itkRLEImageRegionIterator_h + +#include "itkImageRegionIterator.h" +#include "itkImageRegionIteratorWithIndex.h" +#include "itkRLEImageIterator.h" +#include "itkRLEImageRegionConstIterator.h" + +namespace itk +{ +/** \class ImageRegionIterator + * \brief A multi-dimensional iterator templated over image type that walks a + * region of pixels. + * + * The itk::ImageRegionIterator is optimized for iteration speed and is the + * first choice for iterative, pixel-wise operations on an image. + * ImageRegionIterator is the least specialized of the ITK image iterator + * classes. ImageRegionIterator is templated over the image type, and is + * constrained to walk only within the specified region and along a line + * parallel to one of the coordinate axes, "wrapping" to the next line as it + * reaches the boundary of the image. To walk the entire image, specify the + * region to be \c image->GetRequestedRegion(). + * + * Most of the functionality is inherited from the ImageRegionConstIterator. + * The current class only adds write access to image pixels. + * Specialized for RLEImage. + * + * \ingroup RLEImage + * \ingroup ITKCommon + */ + +template +class ImageRegionIterator> + : public ImageRegionConstIterator> +{ +public: + /** Standard class type alias. */ + using Self = ImageRegionIterator; + using Superclass = ImageRegionConstIterator>; + + /** Types inherited from the Superclass */ + using IndexType = typename Superclass::IndexType; + using SizeType = typename Superclass::SizeType; + using OffsetType = typename Superclass::OffsetType; + using RegionType = typename Superclass::RegionType; + using ImageType = typename Superclass::ImageType; + using InternalPixelType = typename Superclass::InternalPixelType; + using PixelType = typename Superclass::PixelType; + + /** Default constructor. Needed since we provide a cast constructor. */ + ImageRegionIterator() + : ImageRegionConstIterator() + {} + + /** Constructor establishes an iterator to walk a particular image and a + * particular region of that image. */ + ImageRegionIterator(ImageType * ptr, const RegionType & region) + : ImageRegionConstIterator(ptr, region) + {} + + /** Constructor that can be used to cast from an ImageIterator to an + * ImageRegionIterator. Many routines return an ImageIterator but for a + * particular task, you may want an ImageRegionIterator. Rather than + * provide overloaded APIs that return different types of Iterators, itk + * returns ImageIterators and uses constructors to cast from an + * ImageIterator to a ImageRegionIterator. */ + ImageRegionIterator(const ImageIterator & it) { ImageConstIterator::operator=(it); } + + /** Set the pixel value. + * Changing the RLE structure invalidates all other iterators (except this one). */ + void + Set(const PixelType & value) const + { + const_cast(this->m_Image.GetPointer()) + ->SetPixel(*const_cast(this->m_RunLengthLine), + this->m_SegmentRemainder, + this->m_RealIndex, + value); + } + +protected: + /** the construction from a const iterator is declared protected + in order to enforce const correctness. */ + ImageRegionIterator(const ImageRegionConstIterator & it) { ImageConstIterator::operator=(it); } + + Self & + operator=(const ImageRegionConstIterator & it) + { + ImageConstIterator::operator=(it); + } +}; + +template +class ImageRegionIteratorWithIndex> + : public ImageRegionConstIteratorWithIndex> +{ +public: + using ImageType = RLEImage; + + using RegionType = typename itk::ImageConstIterator>::RegionType; + + /** Default constructor. Needed since we provide a cast constructor. */ + ImageRegionIteratorWithIndex() + : ImageRegionConstIteratorWithIndex() + {} + + /** Constructor establishes an iterator to walk a particular image and a + * particular region of that image. */ + ImageRegionIteratorWithIndex(ImageType * ptr, const RegionType & region) + : ImageRegionConstIteratorWithIndex(ptr, region) + {} + + /** Set the pixel value. + * Changing the RLE structure invalidates all other iterators (except this one). */ + void + Set(const TPixel & value) const + { + const_cast(this->m_Image.GetPointer()) + ->SetPixel(*const_cast(this->m_RunLengthLine), + this->m_SegmentRemainder, + this->m_RealIndex, + value); + } + + /** Constructor that can be used to cast from an ImageIterator to an + * ImageRegionIteratorWithIndex. Many routines return an ImageIterator, but for a + * particular task, you may want an ImageRegionConstIterator. Rather than + * provide overloaded APIs that return different types of Iterators, itk + * returns ImageIterators and uses constructors to cast from an + * ImageIterator to a ImageRegionConstIterator. */ + ImageRegionIteratorWithIndex(const ImageIterator & it) + { + ImageRegionConstIteratorWithIndex::operator=(it); + } + + /** Constructor that can be used to cast from an ImageConstIterator to an + * ImageRegionIteratorWithIndex. Many routines return an ImageIterator, but for a + * particular task, you may want an ImageRegionConstIterator. Rather than + * provide overloaded APIs that return different types of Iterators, itk + * returns ImageIterators and uses constructors to cast from an + * ImageIterator to a ImageRegionIteratorWithIndex. */ + ImageRegionIteratorWithIndex(const ImageConstIterator & it) + { + ImageRegionConstIterator::operator=(it); + } +}; // no additional implementation required + +// CTAD deduction guide. See the matching guide in +// itkRLEImageRegionConstIterator.h for rationale. +template +ImageRegionIterator(RLEImage *, + const typename RLEImage::RegionType &) + -> ImageRegionIterator>; + +} // end namespace itk + +#endif // itkRLEImageRegionIterator_h diff --git a/Modules/Filtering/RLEImage/include/itkRLEImageScanlineConstIterator.h b/Modules/Filtering/RLEImage/include/itkRLEImageScanlineConstIterator.h new file mode 100644 index 000000000000..99235ede91b5 --- /dev/null +++ b/Modules/Filtering/RLEImage/include/itkRLEImageScanlineConstIterator.h @@ -0,0 +1,195 @@ +/*========================================================================= + * + * 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 itkRLEImageScanlineConstIterator_h +#define itkRLEImageScanlineConstIterator_h + +#include "itkImageScanlineIterator.h" +#include "itkRLEImageRegionConstIterator.h" + +namespace itk +{ +/** \class ImageScanlineConstIterator + * \brief A multi-dimensional iterator templated over image type that + * walks a region of pixels, scanline by scanline or in the direction + * of the fastest axis. Specialized for RLEImage. + * \ingroup RLEImage + * \ingroup ITKCommon + */ +template +class ImageScanlineConstIterator> + : public ImageRegionConstIterator> +{ +public: + /** Standard class type alias. */ + using Self = ImageScanlineConstIterator; + using Superclass = ImageRegionConstIterator>; + + /** Dimension of the image that the iterator walks. This constant is needed so + * functions that are templated over image iterator type (as opposed to + * being templated over pixel type and dimension) can have compile time + * access to the dimension of the image that the iterator walks. */ + static constexpr unsigned int ImageIteratorDimension = VImageDimension; + + /** + * Index type alias support While these were already typdef'ed in the superclass, + * they need to be redone here for this subclass to compile properly with gcc. + */ + /** Types inherited from the Superclass */ + using IndexType = typename Superclass::IndexType; + using SizeType = typename Superclass::SizeType; + using OffsetType = typename Superclass::OffsetType; + using RegionType = typename Superclass::RegionType; + using ImageType = typename Superclass::ImageType; + using InternalPixelType = typename Superclass::InternalPixelType; + using PixelType = typename Superclass::PixelType; + + /** Run-time type information (and related methods). */ + itkOverrideGetNameOfClassMacro(ImageScanlineConstIterator); + + /** Default constructor. Needed since we provide a cast constructor. */ + ImageScanlineConstIterator() + : ImageRegionConstIterator() + {} + + /** Constructor establishes an iterator to walk a particular image and a + * particular region of that image. */ + ImageScanlineConstIterator(const ImageType * ptr, const RegionType & region) + : ImageRegionConstIterator(ptr, region) + {} + + /** Constructor that can be used to cast from an ImageIterator to an + * ImageScanlineConstIterator. Many routines return an ImageIterator, but for a + * particular task, you may want an ImageScanlineConstIterator. Rather than + * provide overloaded APIs that return different types of Iterators, itk + * returns ImageIterators and uses constructors to cast from an + * ImageIterator to a ImageScanlineConstIterator. */ + ImageScanlineConstIterator(const ImageIterator & it) + : ImageRegionConstIterator(it) + {} + + /** Constructor that can be used to cast from an ImageConstIterator to an + * ImageScanlineConstIterator. Many routines return an ImageIterator, but for a + * particular task, you may want an ImageScanlineConstIterator. Rather than + * provide overloaded APIs that return different types of Iterators, itk + * returns ImageIterators and uses constructors to cast from an + * ImageIterator to a ImageScanlineConstIterator. */ + ImageScanlineConstIterator(const ImageConstIterator & it) + { + ImageRegionConstIterator::operator=(it); + } + + /** Go to the beginning pixel of the current line. */ + void + GoToBeginOfLine() + { + this->m_Index0 = this->m_BeginIndex0; + this->m_RealIndex = 0; + this->m_SegmentRemainder = (*this->m_RunLengthLine)[this->m_RealIndex].first; + } + + /** Go to the past end pixel of the current line. */ + void + GoToEndOfLine() + { + this->m_Index0 = this->m_EndIndex0; + this->m_RealIndex = this->m_RunLengthLine->size() - 1; + this->m_SegmentRemainder = 0; + } + + /** Test if the index is at the end of line. */ + inline bool + IsAtEndOfLine() + { + return this->m_Index0 == this->m_EndIndex0; + } + + /** Go to the next line. */ + inline void + NextLine() + { + ++(this->m_BI); + if (!this->m_BI.IsAtEnd()) + { + this->SetIndexInternal(this->m_BeginIndex0); + } + else + { + this->m_Index0 = this->m_BeginIndex0; // make this iterator at end too + } + } + + /** Increment (prefix) along the scanline. + * + * If the iterator is at the end of the scanline ( one past the last + * valid element in the row ), then the results are undefined. Which + * means is may assert in debug mode or result in an undefined + * iterator which may have unknown consequences if used. + */ + Self & + operator++() + { + itkAssertInDebugAndIgnoreInReleaseMacro(!this->IsAtEndOfLine()); + this->m_Index0++; + this->m_SegmentRemainder--; + if (this->m_SegmentRemainder > 0) + { + return *this; + } + + if (this->IsAtEndOfLine()) + { + return *this; + } + this->m_RealIndex++; + this->m_SegmentRemainder = (*this->m_RunLengthLine)[this->m_RealIndex].first; + return *this; + } // ++ + + /** Decrement (prefix) along the scanline. + * + */ + Self & + operator--() + { + this->m_Index0--; + this->m_SegmentRemainder++; + if (this->m_SegmentRemainder <= (*this->m_RunLengthLine)[this->m_RealIndex].first) + { + return *this; + } + + this->m_RealIndex--; + this->m_SegmentRemainder = 1; + return *this; + } +}; + +// Deduction guide for class template argument deduction (CTAD). +template +ImageScanlineConstIterator(SmartPointer>, + const typename RLEImage::RegionType &) + -> ImageScanlineConstIterator>; + +template +ImageScanlineConstIterator(const RLEImage *, + const typename RLEImage::RegionType &) + -> ImageScanlineConstIterator>; + +} // end namespace itk + +#endif // itkRLEImageScanlineConstIterator_h diff --git a/Modules/Filtering/RLEImage/include/itkRLEImageScanlineIterator.h b/Modules/Filtering/RLEImage/include/itkRLEImageScanlineIterator.h new file mode 100644 index 000000000000..83a175c1df8d --- /dev/null +++ b/Modules/Filtering/RLEImage/include/itkRLEImageScanlineIterator.h @@ -0,0 +1,120 @@ +/*========================================================================= + * + * 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 itkRLEImageScanlineIterator_h +#define itkRLEImageScanlineIterator_h + +#include "itkImageScanlineIterator.h" +#include "itkRLEImageIterator.h" +#include "itkRLEImageScanlineConstIterator.h" + +namespace itk +{ +/** \class ImageScanlineIterator + * \brief A multi-dimensional iterator templated over image type that + * walks a region of pixels, scanline by scanline or in the direction + * of the fastest axis. Read-write access. Specialized for RLEImage. + * \ingroup RLEImage + * \ingroup ITKCommon + */ +template +class ImageScanlineIterator> + : public ImageScanlineConstIterator> +{ +public: + /** Standard class type alias. */ + using Self = ImageScanlineIterator; + using Superclass = ImageScanlineConstIterator>; + + /** Types inherited from the Superclass */ + using IndexType = typename Superclass::IndexType; + using SizeType = typename Superclass::SizeType; + using OffsetType = typename Superclass::OffsetType; + using RegionType = typename Superclass::RegionType; + using ImageType = typename Superclass::ImageType; + using InternalPixelType = typename Superclass::InternalPixelType; + using PixelType = typename Superclass::PixelType; + + /** Default constructor. Needed since we provide a cast constructor. */ + ImageScanlineIterator() + : ImageScanlineConstIterator() + {} + + /** Constructor establishes an iterator to walk a particular image and a + * particular region of that image. */ + ImageScanlineIterator(ImageType * ptr, const RegionType & region) + : ImageScanlineConstIterator(ptr, region) + {} + + /** Constructor that can be used to cast from an ImageIterator to an + * ImageScanlineIterator. Many routines return an ImageIterator but for a + * particular task, you may want an ImageScanlineIterator. Rather than + * provide overloaded APIs that return different types of Iterators, itk + * returns ImageIterators and uses constructors to cast from an + * ImageIterator to a ImageScanlineIterator. */ + ImageScanlineIterator(const ImageIterator & it) + : ImageScanlineConstIterator(it) + {} + + /** Set the pixel value */ + void + Set(const PixelType & value) const + { + const_cast(this->m_Image.GetPointer()) + ->SetPixel(*const_cast(this->m_RunLengthLine), + this->m_SegmentRemainder, + this->m_RealIndex, + value); + } + + ///** Return a reference to the pixel + // * This method will provide the fastest access to pixel + // * data, but it will NOT support ImageAdaptors. */ + // PixelType & Value() + // { + // return m_Buffer[m_Index[2]][m_Index[1]][m_RealIndex].second; + // } + +protected: + /** the construction from a const iterator is declared protected + in order to enforce const correctness. */ + ImageScanlineIterator(const ImageScanlineConstIterator & it) + : ImageScanlineConstIterator(it) + {} + Self & + operator=(const ImageScanlineConstIterator & it) + { + this->ImageScanlineConstIterator::operator=(it); + return *this; + } +}; + +// Deduction guide for class template argument deduction (CTAD). +template +ImageScanlineIterator(SmartPointer>, + const typename RLEImage::RegionType &) + -> ImageScanlineIterator>; + +// Deduction guide for class template argument deduction (CTAD). +template +ImageScanlineIterator(RLEImage *, + const typename RLEImage::RegionType &) + -> ImageScanlineIterator>; + +} // end namespace itk + +#endif // itkRLEImageScanlineIterator_h diff --git a/Modules/Filtering/RLEImage/include/itkRLERegionOfInterestImageFilter.h b/Modules/Filtering/RLEImage/include/itkRLERegionOfInterestImageFilter.h new file mode 100644 index 000000000000..b847e6070310 --- /dev/null +++ b/Modules/Filtering/RLEImage/include/itkRLERegionOfInterestImageFilter.h @@ -0,0 +1,420 @@ +/*========================================================================= + * + * 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 itkRLERegionOfInterestImageFilter_h +#define itkRLERegionOfInterestImageFilter_h + +#include "itkImageToImageFilter.h" +#include "itkRLEImage.h" +#include "itkRegionOfInterestImageFilter.h" +#include "itkSmartPointer.h" + +namespace itk +{ +/** \class RegionOfInterestImageFilter + * \brief Extract a region of interest from the input image or convert + * between itk::Image and RLEImage (a custom region can be used). + * + * This filter produces an output image of the same dimension as the input + * image. The user specifies the region of the input image that will be + * contained in the output image. The origin coordinates of the output images + * will be computed in such a way that if mapped to physical space, the output + * image will overlay the input image with perfect registration. In other + * words, a registration process between the output image and the input image + * will return an identity transform. + * + * The region to extract is set using the method SetRegionOfInterest. + * + * Specialized for RLEImage. + * + * \ingroup RLEImage + * \ingroup ITKCommon + */ +template +class RegionOfInterestImageFilter, + RLEImage> + : public ImageToImageFilter, + RLEImage> +{ +public: + ITK_DISALLOW_COPY_AND_MOVE(RegionOfInterestImageFilter); + + /** Standard class type alias. */ + using Self = RegionOfInterestImageFilter; + using RLEImageType = RLEImage; + using ImageType = RLEImageType; + using Superclass = ImageToImageFilter; + using Pointer = SmartPointer; + using ConstPointer = SmartPointer; + using InputImageRegionType = typename Superclass::InputImageRegionType; + + /** Method for creation through the object factory. */ + itkNewMacro(Self); + + /** Run-time type information (and related methods). */ + itkOverrideGetNameOfClassMacro(RegionOfInterestImageFilter); + + /** Typedef to describe the input image region types. */ + using RegionType = typename RLEImageType::RegionType; + using IndexType = typename RLEImageType::IndexType; + using SizeType = typename RLEImageType::SizeType; + + /** Typedef to describe the type of pixel. */ + using OutputImagePixelType = typename RLEImageType::PixelType; + using InputImagePixelType = typename RLEImageType::PixelType; + + /** Set/Get the output image region. */ + itkSetMacro(RegionOfInterest, RegionType); + itkGetConstMacro(RegionOfInterest, RegionType); + + /** ImageDimension enumeration */ + static constexpr unsigned int ImageDimension = VImageDimension; + static constexpr unsigned int OutputImageDimension = VImageDimension; + +#ifdef ITK_USE_CONCEPT_CHECKING + // Begin concept checking + itkConceptMacro(SameDimensionCheck, (Concept::SameDimension)); + itkConceptMacro(InputConvertibleToOutputCheck, (Concept::Convertible)); +// End concept checking +#endif + +protected: + RegionOfInterestImageFilter() { this->DynamicMultiThreadingOn(); } + ~RegionOfInterestImageFilter() override = default; + void + PrintSelf(std::ostream & os, Indent indent) const override; + + void + GenerateInputRequestedRegion() override; + + void + EnlargeOutputRequestedRegion(DataObject * output) override; + + /** RegionOfInterestImageFilter can produce an image which is a different + * size than its input image. As such, RegionOfInterestImageFilter + * needs to provide an implementation for + * GenerateOutputInformation() in order to inform the pipeline + * execution model. The original documentation of this method is + * below. + * + * \sa ProcessObject::GenerateOutputInformaton() */ + void + GenerateOutputInformation() override; + + /** RegionOfInterestImageFilter can be implemented as a multithreaded filter. + * Therefore, this implementation provides a DynamicThreadedGenerateData() + * routine which is called for each processing thread. The output + * image data is allocated automatically by the superclass prior to + * calling DynamicThreadedGenerateData(). DynamicThreadedGenerateData can only + * write to the portion of the output image specified by the + * parameter "outputRegionForThread" + * \sa ImageToImageFilter::DynamicThreadedGenerateData(), + * ImageToImageFilter::GenerateData() */ + void + DynamicThreadedGenerateData(const RegionType & outputRegionForThread) override; + +private: + RegionType m_RegionOfInterest; +}; + +template +class RegionOfInterestImageFilter, + RLEImage> + : public ImageToImageFilter, + RLEImage> +{ +public: + ITK_DISALLOW_COPY_AND_MOVE(RegionOfInterestImageFilter); + + /** Standard class type alias. */ + using Self = RegionOfInterestImageFilter; + using RLEImageTypeIn = RLEImage; + using RLEImageTypeOut = RLEImage; + using ImageType = RLEImageTypeOut; + using Superclass = ImageToImageFilter; + using Pointer = SmartPointer; + using ConstPointer = SmartPointer; + using InputImageRegionType = typename Superclass::InputImageRegionType; + + /** Method for creation through the object factory. */ + itkNewMacro(Self); + + /** Run-time type information (and related methods). */ + itkOverrideGetNameOfClassMacro(RegionOfInterestImageFilter); + + /** Typedef to describe the input image region types. */ + using RegionType = typename RLEImageTypeIn::RegionType; + using IndexType = typename RLEImageTypeIn::IndexType; + using SizeType = typename RLEImageTypeIn::SizeType; + + /** Typedef to describe the type of pixel. */ + using OutputImagePixelType = typename RLEImageTypeOut::PixelType; + using InputImagePixelType = typename RLEImageTypeIn::PixelType; + + /** Set/Get the output image region. */ + itkSetMacro(RegionOfInterest, RegionType); + itkGetConstMacro(RegionOfInterest, RegionType); + + /** ImageDimension enumeration */ + static constexpr unsigned int ImageDimension = VImageDimension; + static constexpr unsigned int OutputImageDimension = VImageDimension; + +#ifdef ITK_USE_CONCEPT_CHECKING + // Begin concept checking + itkConceptMacro(SameDimensionCheck, (Concept::SameDimension)); + itkConceptMacro(InputConvertibleToOutputCheck, (Concept::Convertible)); +// End concept checking +#endif + +protected: + RegionOfInterestImageFilter() { this->DynamicMultiThreadingOn(); } + ~RegionOfInterestImageFilter() override = default; + void + PrintSelf(std::ostream & os, Indent indent) const override; + + void + GenerateInputRequestedRegion() override; + + void + EnlargeOutputRequestedRegion(DataObject * output) override; + + /** RegionOfInterestImageFilter can produce an image which is a different + * size than its input image. As such, RegionOfInterestImageFilter + * needs to provide an implementation for + * GenerateOutputInformation() in order to inform the pipeline + * execution model. The original documentation of this method is + * below. + * + * \sa ProcessObject::GenerateOutputInformaton() */ + void + GenerateOutputInformation() override; + + /** RegionOfInterestImageFilter can be implemented as a multithreaded filter. + * Therefore, this implementation provides a DynamicThreadedGenerateData() + * routine which is called for each processing thread. The output + * image data is allocated automatically by the superclass prior to + * calling DynamicThreadedGenerateData(). DynamicThreadedGenerateData can only + * write to the portion of the output image specified by the + * parameter "outputRegionForThread" + * \sa ImageToImageFilter::ThreadedGenerateData(), + * ImageToImageFilter::GenerateData() */ + void + DynamicThreadedGenerateData(const RegionType & outputRegionForThread) override; + +private: + RegionType m_RegionOfInterest; +}; + +// not implemented on purpose, so it will produce a meaningful error message +template +class InputAndOutputImagesMustHaveSameDimension; + +// input and output images must have the same dimension (e.g. both 2D or both 3D) +// so disallow this by inheriting from unimplemented base class +template +class RegionOfInterestImageFilter, + RLEImage> + : InputAndOutputImagesMustHaveSameDimension +{}; + +template +class RegionOfInterestImageFilter, RLEImage> + : public ImageToImageFilter, RLEImage> +{ +public: + ITK_DISALLOW_COPY_AND_MOVE(RegionOfInterestImageFilter); + + /** Standard class type alias. */ + using RLEImageType = RLEImage; + + using Self = RegionOfInterestImageFilter; + using ImageType = Image; + using Superclass = ImageToImageFilter; + using Pointer = SmartPointer; + using ConstPointer = SmartPointer; + using InputImageRegionType = typename Superclass::InputImageRegionType; + + /** Method for creation through the object factory. */ + itkNewMacro(Self); + + /** Run-time type information (and related methods). */ + itkOverrideGetNameOfClassMacro(RegionOfInterestImageFilter); + + /** Typedef to describe the input image region types. */ + using RegionType = typename RLEImageType::RegionType; + using IndexType = typename RLEImageType::IndexType; + using SizeType = typename RLEImageType::SizeType; + + /** Typedef to describe the type of pixel. */ + using OutputImagePixelType = typename RLEImageType::PixelType; + using InputImagePixelType = typename RLEImageType::PixelType; + + /** Set/Get the output image region. */ + itkSetMacro(RegionOfInterest, RegionType); + itkGetConstMacro(RegionOfInterest, RegionType); + + /** ImageDimension enumeration */ + static constexpr unsigned int ImageDimension = VImageDimension; + static constexpr unsigned int OutputImageDimension = VImageDimension; + +#ifdef ITK_USE_CONCEPT_CHECKING + // Begin concept checking + itkConceptMacro(SameDimensionCheck, (Concept::SameDimension)); + itkConceptMacro(InputConvertibleToOutputCheck, (Concept::Convertible)); +// End concept checking +#endif + +protected: + RegionOfInterestImageFilter() { this->DynamicMultiThreadingOn(); } + ~RegionOfInterestImageFilter() override = default; + void + PrintSelf(std::ostream & os, Indent indent) const override; + + void + GenerateInputRequestedRegion() override; + + void + EnlargeOutputRequestedRegion(DataObject * output) override; + + /** RegionOfInterestImageFilter can produce an image which is a different + * size than its input image. As such, RegionOfInterestImageFilter + * needs to provide an implementation for + * GenerateOutputInformation() in order to inform the pipeline + * execution model. The original documentation of this method is + * below. + * + * \sa ProcessObject::GenerateOutputInformaton() */ + void + GenerateOutputInformation() override; + + /** RegionOfInterestImageFilter can be implemented as a multithreaded filter. + * Therefore, this implementation provides a DynamicThreadedGenerateData() + * routine which is called for each processing thread. The output + * image data is allocated automatically by the superclass prior to + * calling DynamicThreadedGenerateData(). DynamicThreadedGenerateData can only + * write to the portion of the output image specified by the + * parameter "outputRegionForThread" + * \sa ImageToImageFilter::ThreadedGenerateData(), + * ImageToImageFilter::GenerateData() */ + void + DynamicThreadedGenerateData(const RegionType & outputRegionForThread) override; + +private: + RegionType m_RegionOfInterest; +}; + +template +class RegionOfInterestImageFilter, Image> + : public ImageToImageFilter, Image> +{ +public: + ITK_DISALLOW_COPY_AND_MOVE(RegionOfInterestImageFilter); + + /** Standard class type alias. */ + using RLEImageType = RLEImage; + + using Self = RegionOfInterestImageFilter; + using ImageType = Image; + using Superclass = ImageToImageFilter; + using Pointer = SmartPointer; + using ConstPointer = SmartPointer; + using InputImageRegionType = typename Superclass::InputImageRegionType; + + /** Method for creation through the object factory. */ + itkNewMacro(Self); + + /** Run-time type information (and related methods). */ + itkOverrideGetNameOfClassMacro(RegionOfInterestImageFilter); + + /** Typedef to describe the input image region types. */ + using RegionType = typename RLEImageType::RegionType; + using IndexType = typename RLEImageType::IndexType; + using SizeType = typename RLEImageType::SizeType; + + /** Typedef to describe the type of pixel. */ + using OutputImagePixelType = typename RLEImageType::PixelType; + using InputImagePixelType = typename RLEImageType::PixelType; + + /** Set/Get the output image region. */ + itkSetMacro(RegionOfInterest, RegionType); + itkGetConstMacro(RegionOfInterest, RegionType); + + /** ImageDimension enumeration */ + static constexpr unsigned int ImageDimension = VImageDimension; + static constexpr unsigned int OutputImageDimension = VImageDimension; + +#ifdef ITK_USE_CONCEPT_CHECKING + // Begin concept checking + itkConceptMacro(SameDimensionCheck, (Concept::SameDimension)); + itkConceptMacro(InputConvertibleToOutputCheck, (Concept::Convertible)); +// End concept checking +#endif + +protected: + RegionOfInterestImageFilter() { this->DynamicMultiThreadingOn(); } + ~RegionOfInterestImageFilter() override = default; + void + PrintSelf(std::ostream & os, Indent indent) const override; + + void + GenerateInputRequestedRegion() override; + + void + EnlargeOutputRequestedRegion(DataObject * output) override; + + /** RegionOfInterestImageFilter can produce an image which is a different + * size than its input image. As such, RegionOfInterestImageFilter + * needs to provide an implementation for + * GenerateOutputInformation() in order to inform the pipeline + * execution model. The original documentation of this method is + * below. + * + * \sa ProcessObject::GenerateOutputInformaton() */ + void + GenerateOutputInformation() override; + + /** RegionOfInterestImageFilter can be implemented as a multithreaded filter. + * Therefore, this implementation provides a DynamicThreadedGenerateData() + * routine which is called for each processing thread. The output + * image data is allocated automatically by the superclass prior to + * calling DynamicThreadedGenerateData(). DynamicThreadedGenerateData can only + * write to the portion of the output image specified by the + * parameter "outputRegionForThread" + * \sa ImageToImageFilter::ThreadedGenerateData(), + * ImageToImageFilter::GenerateData() */ + void + DynamicThreadedGenerateData(const RegionType & outputRegionForThread) override; + +private: + RegionType m_RegionOfInterest; +}; +} // end namespace itk + +#ifndef ITK_MANUAL_INSTANTIATION +# include "itkRLERegionOfInterestImageFilter.hxx" +#endif + +#endif // itkRLERegionOfInterestImageFilter_h diff --git a/Modules/Filtering/RLEImage/include/itkRLERegionOfInterestImageFilter.hxx b/Modules/Filtering/RLEImage/include/itkRLERegionOfInterestImageFilter.hxx new file mode 100644 index 000000000000..c3fe392c486c --- /dev/null +++ b/Modules/Filtering/RLEImage/include/itkRLERegionOfInterestImageFilter.hxx @@ -0,0 +1,697 @@ +/*========================================================================= + * + * 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 itkRLERegionOfInterestImageFilter_hxx +#define itkRLERegionOfInterestImageFilter_hxx + + +#include "itkImage.h" +#include "itkImageAlgorithm.h" +#include "itkObjectFactory.h" +#include "itkRegionOfInterestImageFilter.h" +#include + +namespace itk +{ +template +void +copyImagePortion(ImageRegionConstIterator iIt, + ImageRegionIterator oIt, + IndexValueType start0, + IndexValueType end0) +{ + while (!oIt.IsAtEnd()) + { + // determine begin and end iterator and copy range + typename RLEImageTypeOut::RLLine & oLine = oIt.Value(); + oLine.clear(); + const typename RLEImageTypeIn::RLLine & iLine = iIt.Value(); + typename RLEImageTypeIn::RLCounterType t = 0; + SizeValueType x = 0; + // find start + for (; x < iLine.size(); x++) + { + t += iLine[x].first; + if (t > start0) + { + break; + } + } + assert(x < iLine.size()); + + SizeValueType begin = x; + if (t >= end0) // both begin and end are in this segment + { + oLine.push_back( + typename RLEImageTypeOut::RLSegment(typename RLEImageTypeOut::RLCounterType(end0 - start0), iLine[x].second)); + ++iIt; + ++oIt; + continue; // next line + } + else if (t - start0 < iLine[x].first) // not the first pixel in segment + { + oLine.push_back( + typename RLEImageTypeOut::RLSegment(typename RLEImageTypeOut::RLCounterType(t - start0), iLine[x].second)); + begin++; // start copying from next segment + } + for (x++; x < iLine.size(); x++) + { + t += iLine[x].first; + if (t >= end0) + { + break; + } + } + if (t == end0) + { + oLine.insert(oLine.end(), iLine.begin() + begin, iLine.begin() + x + 1); + } + else // we need to take special care of the last segment + { + oLine.insert(oLine.end(), iLine.begin() + begin, iLine.begin() + x); + oLine.push_back(typename RLEImageTypeOut::RLSegment( + typename RLEImageTypeOut::RLCounterType(end0 + iLine[x].first - t), iLine[x].second)); + } + + ++iIt; + ++oIt; + } +} // copyImagePortion + +template +void +RegionOfInterestImageFilter, + RLEImage>::PrintSelf(std::ostream & os, + Indent indent) const +{ + Superclass::PrintSelf(os, indent); + + os << indent << "RegionOfInterest: " << m_RegionOfInterest << std::endl; +} + +template +void +RegionOfInterestImageFilter, + RLEImage>::GenerateInputRequestedRegion() +{ + // call the superclass' implementation of this method + Superclass::GenerateInputRequestedRegion(); + + // get pointer to the input + typename Superclass::InputImagePointer inputPtr = const_cast(this->GetInput()); + + if (inputPtr) + { + // request the region of interest + inputPtr->SetRequestedRegion(m_RegionOfInterest); + } +} + +template +void +RegionOfInterestImageFilter, + RLEImage>::EnlargeOutputRequestedRegion(DataObject * + output) +{ + // call the superclass' implementation of this method + Superclass::EnlargeOutputRequestedRegion(output); + + // generate everything in the region of interest + output->SetRequestedRegionToLargestPossibleRegion(); +} + + +template +void +RegionOfInterestImageFilter, + RLEImage>::GenerateOutputInformation() +{ + // do not call the superclass' implementation of this method since + // this filter allows the input the output to be of different dimensions + + // get pointers to the input and output + typename Superclass::OutputImagePointer outputPtr = this->GetOutput(); + typename Superclass::InputImageConstPointer inputPtr = this->GetInput(); + + if (!outputPtr || !inputPtr) + { + return; + } + + // Set the output image size to the same value as the region of interest. + RegionType region; + IndexType start; + start.Fill(0); + + region.SetSize(m_RegionOfInterest.GetSize()); + region.SetIndex(start); + + // Copy Information without modification. + outputPtr->CopyInformation(inputPtr); + + // Adjust output region + outputPtr->SetLargestPossibleRegion(region); + + // Correct origin of the extracted region. + IndexType roiStart(m_RegionOfInterest.GetIndex()); + typename Superclass::OutputImageType::PointType outputOrigin; + inputPtr->TransformIndexToPhysicalPoint(roiStart, outputOrigin); + outputPtr->SetOrigin(outputOrigin); +} // >::GenerateOutputInformation + + +template +void +RegionOfInterestImageFilter< + RLEImage, + RLEImage>::DynamicThreadedGenerateData(const RegionType & outputRegionForThread) +{ + // Get the input and output pointers + const RLEImageType * in = this->GetInput(); + RLEImageType * out = this->GetOutput(); + + // Concurrent writing to RLLine is not supported, + // so we ignore all the output regions which do not start + // at the beginning of a line. + // But the regions which do start at the beginning of a line + // must process the whole line + RegionType reqRegion = out->GetRequestedRegion(); + if (reqRegion.GetIndex(0) != outputRegionForThread.GetIndex(0)) + { + return; // another thread will process this + } + RegionType outRegion = outputRegionForThread; + outRegion.SetSize(0, reqRegion.GetSize(0)); + + // Define the portion of the input to walk for this thread + InputImageRegionType inputRegionForThread; + + inputRegionForThread.SetSize(outRegion.GetSize()); + + IndexType start, end; + IndexType roiStart(m_RegionOfInterest.GetIndex()); + IndexType threadStart(outRegion.GetIndex()); + for (unsigned int i = 0; i < VImageDimension; i++) + { + start[i] = roiStart[i] + threadStart[i]; + end[i] = roiStart[i] + threadStart[i] + outRegion.GetSize(i); + } + inputRegionForThread.SetIndex(start); + + bool copyLines = (in->GetLargestPossibleRegion().GetSize(0) == outRegion.GetSize(0)); + typename ImageType::BufferType::RegionType oReg = outRegion.Slice(0); + typename ImageType::BufferType::RegionType iReg = inputRegionForThread.Slice(0); + ImageRegionConstIterator iIt(in->GetBuffer(), iReg); + ImageRegionIterator oIt(out->GetBuffer(), oReg); + + if (copyLines) + { + while (!oIt.IsAtEnd()) + { + oIt.Set(iIt.Get()); + + ++iIt; + ++oIt; + } + } + else + { + copyImagePortion(iIt, oIt, start[0], end[0]); + } +} // DynamicThreadedGenerateData + +template +void +RegionOfInterestImageFilter, + RLEImage>::PrintSelf(std::ostream & os, + Indent indent) const +{ + Superclass::PrintSelf(os, indent); + + os << indent << "RegionOfInterest: " << m_RegionOfInterest << std::endl; +} + +template +void +RegionOfInterestImageFilter, + RLEImage>::GenerateInputRequestedRegion() +{ + // call the superclass' implementation of this method + Superclass::GenerateInputRequestedRegion(); + + // get pointer to the input + typename Superclass::InputImagePointer inputPtr = const_cast(this->GetInput()); + + if (inputPtr) + { + // request the region of interest + inputPtr->SetRequestedRegion(m_RegionOfInterest); + } +} + +template +void +RegionOfInterestImageFilter< + RLEImage, + RLEImage>::EnlargeOutputRequestedRegion(DataObject * output) +{ + // call the superclass' implementation of this method + Superclass::EnlargeOutputRequestedRegion(output); + + // generate everything in the region of interest + output->SetRequestedRegionToLargestPossibleRegion(); +} + + +template +void +RegionOfInterestImageFilter, + RLEImage>::GenerateOutputInformation() +{ + // do not call the superclass' implementation of this method since + // this filter allows the input the output to be of different dimensions + + // get pointers to the input and output + typename Superclass::OutputImagePointer outputPtr = this->GetOutput(); + typename Superclass::InputImageConstPointer inputPtr = this->GetInput(); + + if (!outputPtr || !inputPtr) + { + return; + } + + // Set the output image size to the same value as the region of interest. + RegionType region; + IndexType start; + start.Fill(0); + + region.SetSize(m_RegionOfInterest.GetSize()); + region.SetIndex(start); + + // Copy Information without modification. + outputPtr->CopyInformation(inputPtr); + + // Adjust output region + outputPtr->SetLargestPossibleRegion(region); + + // Correct origin of the extracted region. + IndexType roiStart(m_RegionOfInterest.GetIndex()); + typename Superclass::OutputImageType::PointType outputOrigin; + inputPtr->TransformIndexToPhysicalPoint(roiStart, outputOrigin); + outputPtr->SetOrigin(outputOrigin); +} // >::GenerateOutputInformation + + +template +void +RegionOfInterestImageFilter, + RLEImage>:: + DynamicThreadedGenerateData(const RegionType & outputRegionForThread) +{ + // Get the input and output pointers + const RLEImageTypeIn * in = this->GetInput(); + RLEImageTypeOut * out = this->GetOutput(); + + // Concurrent writing to RLLine is not supported, + // so we ignore all the output regions which do not start + // at the beginning of a line. + // But the regions which do start at the beginning of a line + // must process the whole line + RegionType reqRegion = out->GetRequestedRegion(); + if (reqRegion.GetIndex(0) != outputRegionForThread.GetIndex(0)) + { + return; // another thread will process this + } + RegionType outRegion = outputRegionForThread; + outRegion.SetSize(0, reqRegion.GetSize(0)); + + // Define the portion of the input to walk for this thread + InputImageRegionType inputRegionForThread; + + inputRegionForThread.SetSize(outRegion.GetSize()); + + IndexType start, end; + IndexType roiStart(m_RegionOfInterest.GetIndex()); + IndexType threadStart(outRegion.GetIndex()); + for (unsigned int i = 0; i < VImageDimension; i++) + { + start[i] = roiStart[i] + threadStart[i]; + end[i] = roiStart[i] + threadStart[i] + outRegion.GetSize(i); + } + inputRegionForThread.SetIndex(start); + + typename RLEImageTypeIn::BufferType::RegionType iReg = inputRegionForThread.Slice(0); + typename RLEImageTypeOut::BufferType::RegionType oReg = outRegion.Slice(0); + ImageRegionConstIterator iIt(in->GetBuffer(), iReg); + ImageRegionIterator oIt(out->GetBuffer(), oReg); + + copyImagePortion(iIt, oIt, start[0], end[0]); +} // DynamicThreadedGenerateData + +template +void +RegionOfInterestImageFilter, RLEImage>::PrintSelf( + std::ostream & os, + Indent indent) const +{ + Superclass::PrintSelf(os, indent); + + os << indent << "RegionOfInterest: " << m_RegionOfInterest << std::endl; +} + +template +void +RegionOfInterestImageFilter, + RLEImage>::GenerateInputRequestedRegion() +{ + // call the superclass' implementation of this method + Superclass::GenerateInputRequestedRegion(); + + // get pointer to the input + typename Superclass::InputImagePointer inputPtr = const_cast(this->GetInput()); + + if (inputPtr) + { + // request the region of interest + inputPtr->SetRequestedRegion(m_RegionOfInterest); + } +} + +template +void +RegionOfInterestImageFilter, RLEImage>:: + EnlargeOutputRequestedRegion(DataObject * output) +{ + // call the superclass' implementation of this method + Superclass::EnlargeOutputRequestedRegion(output); + + // generate everything in the region of interest + output->SetRequestedRegionToLargestPossibleRegion(); +} + + +template +void +RegionOfInterestImageFilter, + RLEImage>::GenerateOutputInformation() +{ + // do not call the superclass' implementation of this method since + // this filter allows the input the output to be of different dimensions + + // get pointers to the input and output + typename Superclass::OutputImagePointer outputPtr = this->GetOutput(); + typename Superclass::InputImageConstPointer inputPtr = this->GetInput(); + + if (!outputPtr || !inputPtr) + { + return; + } + + // Set the output image size to the same value as the region of interest. + RegionType region; + IndexType start; + start.Fill(0); + + region.SetSize(m_RegionOfInterest.GetSize()); + region.SetIndex(start); + + // Copy Information without modification. + outputPtr->CopyInformation(inputPtr); + + // Adjust output region + outputPtr->SetLargestPossibleRegion(region); + + // Correct origin of the extracted region. + IndexType roiStart(m_RegionOfInterest.GetIndex()); + typename Superclass::OutputImageType::PointType outputOrigin; + inputPtr->TransformIndexToPhysicalPoint(roiStart, outputOrigin); + outputPtr->SetOrigin(outputOrigin); +} // >::GenerateOutputInformation + + +template +void +RegionOfInterestImageFilter, RLEImage>:: + DynamicThreadedGenerateData(const RegionType & outputRegionForThread) +{ + // Get the input and output pointers + const ImageType * in = this->GetInput(); + RLEImageType * out = this->GetOutput(); + + // Concurrent writing to RLLine is not supported, + // so we ignore all the output regions which do not start + // at the beginning of a line. + // But the regions which do start at the beginning of a line + // must process the whole line + RegionType reqRegion = out->GetRequestedRegion(); + if (reqRegion.GetIndex(0) != outputRegionForThread.GetIndex(0)) + { + return; // another thread will process this + } + RegionType outRegion = outputRegionForThread; + outRegion.SetSize(0, reqRegion.GetSize(0)); + + // Define the portion of the input to walk for this thread + InputImageRegionType inputRegionForThread; + + inputRegionForThread.SetSize(outRegion.GetSize()); + + IndexType start, end; + IndexType roiStart(m_RegionOfInterest.GetIndex()); + IndexType threadStart(outRegion.GetIndex()); + for (unsigned int i = 0; i < VImageDimension; i++) + { + start[i] = roiStart[i] + threadStart[i]; + end[i] = roiStart[i] + threadStart[i] + outRegion.GetSize(i); + } + inputRegionForThread.SetIndex(start); + + typename RLEImageType::BufferType::RegionType oReg = outRegion.Slice(0); + ImageRegionConstIterator iIt(in, inputRegionForThread); + ImageRegionIterator oIt(out->GetBuffer(), oReg); + SizeValueType size0 = outRegion.GetSize(0); + typename RLEImageType::RLLine temp; + temp.reserve(size0); // pessimistically preallocate buffer, otherwise reallocations can occur + + while (!oIt.IsAtEnd()) + { + SizeValueType x = 0; + temp.clear(); + while (x < size0) + { + typename RLEImageType::RLSegment s(0, iIt.Value()); + while (x < size0 && iIt.Value() == s.second) + { + x++; + s.first++; + ++(iIt); + } + + temp.push_back(s); + } + + oIt.Value() = temp; + ++oIt; + } +} // DynamicThreadedGenerateData + +template +void +RegionOfInterestImageFilter, Image>::PrintSelf( + std::ostream & os, + Indent indent) const +{ + Superclass::PrintSelf(os, indent); + + os << indent << "RegionOfInterest: " << m_RegionOfInterest << std::endl; +} + +template +void +RegionOfInterestImageFilter, + Image>::GenerateInputRequestedRegion() +{ + // call the superclass' implementation of this method + Superclass::GenerateInputRequestedRegion(); + + // get pointer to the input + typename Superclass::InputImagePointer inputPtr = const_cast(this->GetInput()); + + if (inputPtr) + { + // request the region of interest + inputPtr->SetRequestedRegion(m_RegionOfInterest); + } +} + +template +void +RegionOfInterestImageFilter, + Image>::EnlargeOutputRequestedRegion(DataObject * output) +{ + // call the superclass' implementation of this method + Superclass::EnlargeOutputRequestedRegion(output); + + // generate everything in the region of interest + output->SetRequestedRegionToLargestPossibleRegion(); +} + + +template +void +RegionOfInterestImageFilter, + Image>::GenerateOutputInformation() +{ + // do not call the superclass' implementation of this method since + // this filter allows the input the output to be of different dimensions + + // get pointers to the input and output + typename Superclass::OutputImagePointer outputPtr = this->GetOutput(); + typename Superclass::InputImageConstPointer inputPtr = this->GetInput(); + + if (!outputPtr || !inputPtr) + { + return; + } + + // Set the output image size to the same value as the region of interest. + RegionType region; + IndexType start; + start.Fill(0); + + region.SetSize(m_RegionOfInterest.GetSize()); + region.SetIndex(start); + + // Copy Information without modification. + outputPtr->CopyInformation(inputPtr); + + // Adjust output region + outputPtr->SetLargestPossibleRegion(region); + + // Correct origin of the extracted region. + IndexType roiStart(m_RegionOfInterest.GetIndex()); + typename Superclass::OutputImageType::PointType outputOrigin; + inputPtr->TransformIndexToPhysicalPoint(roiStart, outputOrigin); + outputPtr->SetOrigin(outputOrigin); +} // >::GenerateOutputInformation + +template +void +RegionOfInterestImageFilter, Image>:: + DynamicThreadedGenerateData(const RegionType & outputRegionForThread) +{ + // Get the input and output pointers + const RLEImageType * in = this->GetInput(); + ImageType * out = this->GetOutput(); + + // Define the portion of the input to walk for this thread + InputImageRegionType inputRegionForThread; + + inputRegionForThread.SetSize(outputRegionForThread.GetSize()); + + IndexType start, end; + IndexType roiStart(m_RegionOfInterest.GetIndex()); + IndexType threadStart(outputRegionForThread.GetIndex()); + for (unsigned int i = 0; i < VImageDimension; i++) + { + start[i] = roiStart[i] + threadStart[i]; + end[i] = roiStart[i] + threadStart[i] + outputRegionForThread.GetSize(i); + } + inputRegionForThread.SetIndex(start); + + typename RLEImageType::BufferType::RegionType iReg = inputRegionForThread.Slice(0); + ImageRegionConstIterator iIt(in->GetBuffer(), iReg); + ImageRegionIterator oIt(out, outputRegionForThread); + + while (!iIt.IsAtEnd()) + { + const typename RLEImageType::RLLine & iLine = iIt.Value(); + CounterType t = 0; + SizeValueType x = 0; + // find start + for (; x < iLine.size(); x++) + { + t += iLine[x].first; + if (t > start[0]) + { + break; + } + } + assert(x < iLine.size()); + + if (t >= end[0]) // both begin and end are in this segment + { + for (IndexValueType i = start[0]; i < end[0]; i++) + { + oIt.Set(iLine[x].second); + ++oIt; + } + ++iIt; + continue; // next line + } + // else handle the beginning segment + for (IndexValueType i = start[0]; i < t; i++) + { + oIt.Set(iLine[x].second); + ++oIt; + } + // now handle middle segments + for (x++; x < iLine.size(); x++) + { + t += iLine[x].first; + if (t >= end[0]) + { + break; + } + for (CounterType i = 0; i < iLine[x].first; i++) + { + oIt.Set(iLine[x].second); + ++oIt; + } + } + // handle the last segment + for (IndexValueType i = 0; i < end[0] + iLine[x].first - t; i++) + { + oIt.Set(iLine[x].second); + ++oIt; + } + ++iIt; + } +} // DynamicThreadedGenerateData +} // end namespace itk + +#endif // itkRLERegionOfInterestImageFilter_hxx diff --git a/Modules/Filtering/RLEImage/itk-module.cmake b/Modules/Filtering/RLEImage/itk-module.cmake new file mode 100644 index 000000000000..a4741942f2ac --- /dev/null +++ b/Modules/Filtering/RLEImage/itk-module.cmake @@ -0,0 +1,21 @@ +# the top-level README is used for describing this module, just +# re-used it for documentation here +get_filename_component(MY_CURENT_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) +file(READ "${MY_CURENT_DIR}/README.md" DOCUMENTATION) + +# itk_module() defines the module dependencies in RLEImage +# The testing module in RLEImage depends on ITKTestKernel +# By convention those modules outside of ITK are not prefixed with +# ITK + +# define the dependencies of the include module and the tests +itk_module( + RLEImage + ENABLE_SHARED + DEPENDS + ITKImageGrid + TEST_DEPENDS + ITKTestKernel + EXCLUDE_FROM_DEFAULT + DESCRIPTION "${DOCUMENTATION}" +) diff --git a/Modules/Filtering/RLEImage/test/CMakeLists.txt b/Modules/Filtering/RLEImage/test/CMakeLists.txt new file mode 100644 index 000000000000..4a142680227b --- /dev/null +++ b/Modules/Filtering/RLEImage/test/CMakeLists.txt @@ -0,0 +1,174 @@ +itk_module_test() + +set( + RLEImageTests + itkRLEImageTest.cxx + itkRegionOfInterestRLEImageFilterTest.cxx + itkIteratorTestsForRLEImage.cxx + itkRLEImageIteratorsForwardBackwardTest.cxx + itkRLEImageIteratorTest.cxx + itkRLEImageIteratorWithIndexTest.cxx + itkRLEImageRegionConstIteratorWithOnlyIndexTest.cxx + itkRLEImageRegionIteratorTest.cxx + itkRLEImageScanlineIteratorTest1.cxx + itkRLEImageMultiLabelMeshPipelineFriendTest.cxx +) + +createtestdriver( RLEImage "${RLEImage-Test_LIBRARIES}" "${RLEImageTests}" ) + +add_executable( + runFromIDE + manualTest.cxx + ${RLEImageTests} +) +target_link_libraries(runFromIDE ${RLEImage-Test_LIBRARIES}) + +add_executable(rleStats rleStats.cxx) +target_link_libraries(rleStats ${RLEImage-Test_LIBRARIES}) + +itk_add_test( + NAME itkIteratorTestsForRLEImage + COMMAND + RLEImageTestDriver + itkIteratorTestsForRLEImage +) +itk_add_test( + NAME itkRegionOfInterestRLEImageFilterTest + COMMAND + RLEImageTestDriver + itkRegionOfInterestRLEImageFilterTest +) +itk_add_test( + NAME itkRLEImageIteratorsForwardBackwardTest + COMMAND + RLEImageTestDriver + itkRLEImageIteratorsForwardBackwardTest +) +itk_add_test( + NAME itkRLEImageIteratorTest + COMMAND + RLEImageTestDriver + itkRLEImageIteratorTest +) +itk_add_test( + NAME itkRLEImageIteratorWithIndexTest + COMMAND + RLEImageTestDriver + itkRLEImageIteratorWithIndexTest +) +itk_add_test( + NAME itkRLEImageRegionConstIteratorWithOnlyIndexTest + COMMAND + RLEImageTestDriver + itkRLEImageRegionConstIteratorWithOnlyIndexTest +) +itk_add_test( + NAME itkRLEImageRegionIteratorTest + COMMAND + RLEImageTestDriver + itkRLEImageRegionIteratorTest +) +itk_add_test( + NAME itkRLEImageScanlineIteratorTest1 + COMMAND + RLEImageTestDriver + itkRLEImageScanlineIteratorTest1 +) +itk_add_test( + NAME itkRLEImageMultiLabelMeshPipelineFriendTest + COMMAND + RLEImageTestDriver + itkRLEImageMultiLabelMeshPipelineFriendTest +) + +function(ReadWriteTest ImageName Ext) # optional: big + set(outImage "${ITK_TEST_OUTPUT_DIR}/${ImageName}.${Ext}") + if("${ARGN}" STREQUAL "big") + if(DEFINED ITK_COMPUTER_MEMORY_SIZE) + if(${ITK_COMPUTER_MEMORY_SIZE} GREATER_EQUAL 16) + itk_add_test( + NAME RLEImage${ImageName} + COMMAND + RLEImageTestDriver + --compare + DATA{Input/${ImageName}.${Ext}} + ${outImage} + itkRLEImageTest + DATA{Input/${ImageName}.${Ext}} + ${outImage} + ) + set_property( + TEST + RLEImage${ImageName} + APPEND + PROPERTY + LABELS + RUNS_LONG + ) + set_property( + TEST + RLEImage${ImageName} + APPEND + PROPERTY + RUN_SERIAL + True + ) + endif() + endif() + else() + itk_add_test( + NAME RLEImage${ImageName} + COMMAND + RLEImageTestDriver + --compare + DATA{Input/${ImageName}.${Ext}} + ${outImage} + itkRLEImageTest + DATA{Input/${ImageName}.${Ext}} + ${outImage} + ) + endif() +endfunction() + +# RLEImage specific tests - quick +readwritetest(Small2D png) +readwritetest(brainParc mha) +readwritetest(vb-seg mha) +readwritetest(ws nrrd) +readwritetest(wb-crop nrrd) + +# Handcrafted tests for MorphologicalContourInterpolation +readwritetest(Empty nrrd) +readwritetest(NoSlices nrrd) +readwritetest(Micro1 nrrd) +readwritetest(SimplestOneToOne nrrd) +readwritetest(OneToOne nrrd) +readwritetest(OneToThree nrrd) +readwritetest(ExtrapolationAppearing nrrd) +readwritetest(DoubleTwoLabelBranching nrrd) +readwritetest(TwoAxisDoubleTwoLabelBC nrrd) +readwritetest(AccidentalMiddleSliceSeg nrrd) +readwritetest(ThreeAxisFourLabelConflict nrrd) +readwritetest(SevenLabels nrrd) +readwritetest(FullEnd nrrd) +readwritetest(1MN1 nrrd) +readwritetest(ManyToMany16 nrrd) +readwritetest(ManyToMany nrrd) +readwritetest(GridSeg nrrd) +readwritetest(GridSeg2 nrrd) +readwritetest(FourD nrrd) +readwritetest(2D png) +readwritetest(BigZ nrrd) +readwritetest(FaceRoI nrrd) + +# Paul's test images +readwritetest(105769moving nii.gz) +readwritetest(105769fixed nii.gz) +readwritetest(64816L_amygdala_fin nii.gz) +readwritetest(64816L_amygdala_int nii.gz) +readwritetest(65239R_amygdala_fin nii.gz) +readwritetest(65239R_amygdala_int nii.gz) + +# RLEImage specific tests - long +readwritetest(wb-seg nrrd big) +readwritetest(wb-seg32 nrrd big) diff --git a/Modules/Filtering/RLEImage/test/Input/105769fixed.nii.gz.cid b/Modules/Filtering/RLEImage/test/Input/105769fixed.nii.gz.cid new file mode 100644 index 000000000000..d53ef8ff823e --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/105769fixed.nii.gz.cid @@ -0,0 +1 @@ +bafkreib2jcz776zhw246jch4abaify6g6xhegiglyhzacenxi73tbijuxu diff --git a/Modules/Filtering/RLEImage/test/Input/105769moving.nii.gz.cid b/Modules/Filtering/RLEImage/test/Input/105769moving.nii.gz.cid new file mode 100644 index 000000000000..d1420e1c26b3 --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/105769moving.nii.gz.cid @@ -0,0 +1 @@ +bafkreick7vd27d3tqztjvn7b422wysowiixvlrj3qxkq5xop2bnyqwnaw4 diff --git a/Modules/Filtering/RLEImage/test/Input/1MN1.nrrd.cid b/Modules/Filtering/RLEImage/test/Input/1MN1.nrrd.cid new file mode 100644 index 000000000000..7b9a7b29838e --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/1MN1.nrrd.cid @@ -0,0 +1 @@ +bafkreihj3yutrrpirztddw7gy3tom2iorjilfqiw3dotwjjvsrquszzzzy diff --git a/Modules/Filtering/RLEImage/test/Input/2D.png.cid b/Modules/Filtering/RLEImage/test/Input/2D.png.cid new file mode 100644 index 000000000000..786b9b2ff8b8 --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/2D.png.cid @@ -0,0 +1 @@ +bafkreibkr5ed5dq2ug2luujh7iljj42iihyrlmb64mkzqbjk5y2kyelgca diff --git a/Modules/Filtering/RLEImage/test/Input/64816L_amygdala_fin.nii.gz.cid b/Modules/Filtering/RLEImage/test/Input/64816L_amygdala_fin.nii.gz.cid new file mode 100644 index 000000000000..d31f2507249e --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/64816L_amygdala_fin.nii.gz.cid @@ -0,0 +1 @@ +bafkreib5witrs3cvmcfe4ywniufyz37xukyaj3z6x4vhyaws3vnt7aejea diff --git a/Modules/Filtering/RLEImage/test/Input/64816L_amygdala_int.nii.gz.cid b/Modules/Filtering/RLEImage/test/Input/64816L_amygdala_int.nii.gz.cid new file mode 100644 index 000000000000..21a8137a73e7 --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/64816L_amygdala_int.nii.gz.cid @@ -0,0 +1 @@ +bafkreidod4atkopejsyqivziwskc6r5zd25vce3iavjkpuuewsqzypdoum diff --git a/Modules/Filtering/RLEImage/test/Input/65239R_amygdala_fin.nii.gz.cid b/Modules/Filtering/RLEImage/test/Input/65239R_amygdala_fin.nii.gz.cid new file mode 100644 index 000000000000..4cc58a9bb0aa --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/65239R_amygdala_fin.nii.gz.cid @@ -0,0 +1 @@ +bafkreiel2l6xyfqjt36gjmc2xbeix4vambaqhgnw3x5s4lmt7el75i5uy4 diff --git a/Modules/Filtering/RLEImage/test/Input/65239R_amygdala_int.nii.gz.cid b/Modules/Filtering/RLEImage/test/Input/65239R_amygdala_int.nii.gz.cid new file mode 100644 index 000000000000..842099cb20de --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/65239R_amygdala_int.nii.gz.cid @@ -0,0 +1 @@ +bafkreic7q4gkyokrflzxqqqitx5pbv7cxe5vrrgtwwbxrxj33hlni3v6qy diff --git a/Modules/Filtering/RLEImage/test/Input/AccidentalMiddleSliceSeg.nrrd.cid b/Modules/Filtering/RLEImage/test/Input/AccidentalMiddleSliceSeg.nrrd.cid new file mode 100644 index 000000000000..371f24a99955 --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/AccidentalMiddleSliceSeg.nrrd.cid @@ -0,0 +1 @@ +bafkreihmyn7pkuhrz7323e7rab5deqr6hs3hoxhx6bqdko5mxky4ron55y diff --git a/Modules/Filtering/RLEImage/test/Input/BigZ.nrrd.cid b/Modules/Filtering/RLEImage/test/Input/BigZ.nrrd.cid new file mode 100644 index 000000000000..d32e93144f53 --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/BigZ.nrrd.cid @@ -0,0 +1 @@ +bafkreiapuklol6iv4kaniskgwhut54g447lrkouoavtda2h4agqwoxbbxm diff --git a/Modules/Filtering/RLEImage/test/Input/DoubleTwoLabelBranching.nrrd.cid b/Modules/Filtering/RLEImage/test/Input/DoubleTwoLabelBranching.nrrd.cid new file mode 100644 index 000000000000..9dd1f2436d24 --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/DoubleTwoLabelBranching.nrrd.cid @@ -0,0 +1 @@ +bafkreigyky43pe2glq43mvxinfoxvlnv7oj26z3rqwsxe52qtuwd6gr5ci diff --git a/Modules/Filtering/RLEImage/test/Input/Empty.nrrd.cid b/Modules/Filtering/RLEImage/test/Input/Empty.nrrd.cid new file mode 100644 index 000000000000..9b596016c7fe --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/Empty.nrrd.cid @@ -0,0 +1 @@ +bafkreicgqhfbdvqkl4byv56sjfvd4tbj6d4cmkx4bsgtunfxg3gaiddaza diff --git a/Modules/Filtering/RLEImage/test/Input/ExtrapolationAppearing.nrrd.cid b/Modules/Filtering/RLEImage/test/Input/ExtrapolationAppearing.nrrd.cid new file mode 100644 index 000000000000..a9bace479a7f --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/ExtrapolationAppearing.nrrd.cid @@ -0,0 +1 @@ +bafkreihc6q2lnb46gfyu77w5y4ilsqekx3oiuggjlzce64aq3tcvxglduy diff --git a/Modules/Filtering/RLEImage/test/Input/FaceRoI.nrrd.cid b/Modules/Filtering/RLEImage/test/Input/FaceRoI.nrrd.cid new file mode 100644 index 000000000000..6a717c0134ab --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/FaceRoI.nrrd.cid @@ -0,0 +1 @@ +bafkreigv4bcueqojhouysj3lxeigd2i3f3rcvmtkypi7linrgnei4zlr6a diff --git a/Modules/Filtering/RLEImage/test/Input/FourD.nrrd.cid b/Modules/Filtering/RLEImage/test/Input/FourD.nrrd.cid new file mode 100644 index 000000000000..15d673454c50 --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/FourD.nrrd.cid @@ -0,0 +1 @@ +bafkreibmeohmkrkulrmyqqptoch3v2mwoausnhpq5btvyqnzromjmsk534 diff --git a/Modules/Filtering/RLEImage/test/Input/FullEnd.nrrd.cid b/Modules/Filtering/RLEImage/test/Input/FullEnd.nrrd.cid new file mode 100644 index 000000000000..c0f5f1383d89 --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/FullEnd.nrrd.cid @@ -0,0 +1 @@ +bafkreidjbqsw4ygy4isc6gqqpgcg5adexeoc5gnt26j7zpfm5lhhmme2ry diff --git a/Modules/Filtering/RLEImage/test/Input/GridSeg.nrrd.cid b/Modules/Filtering/RLEImage/test/Input/GridSeg.nrrd.cid new file mode 100644 index 000000000000..8eb43554aeb4 --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/GridSeg.nrrd.cid @@ -0,0 +1 @@ +bafkreia77htujqonvdzb4wdzk2c3fmmjx6s5wc4zvfkahxumcou2vtlsri diff --git a/Modules/Filtering/RLEImage/test/Input/GridSeg2.nrrd.cid b/Modules/Filtering/RLEImage/test/Input/GridSeg2.nrrd.cid new file mode 100644 index 000000000000..fd61f8909dcf --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/GridSeg2.nrrd.cid @@ -0,0 +1 @@ +bafkreihaa52jcvehe5vczpvgg634yarwvmvzmcyc2kf3mk32ng4uqbzn34 diff --git a/Modules/Filtering/RLEImage/test/Input/ManyToMany.nrrd.cid b/Modules/Filtering/RLEImage/test/Input/ManyToMany.nrrd.cid new file mode 100644 index 000000000000..21eebbb2b161 --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/ManyToMany.nrrd.cid @@ -0,0 +1 @@ +bafkreigfqxkod62sntm7orjzyyzsmtfhbk7lem6sslfa2gfq6mjg3zh4eq diff --git a/Modules/Filtering/RLEImage/test/Input/ManyToMany16.nrrd.cid b/Modules/Filtering/RLEImage/test/Input/ManyToMany16.nrrd.cid new file mode 100644 index 000000000000..3fcc91799508 --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/ManyToMany16.nrrd.cid @@ -0,0 +1 @@ +bafkreihdrdpt7wiudnannjpxlbouwipyt62zt7kh532kxc3c24nsdja4wq diff --git a/Modules/Filtering/RLEImage/test/Input/Micro1.nrrd.cid b/Modules/Filtering/RLEImage/test/Input/Micro1.nrrd.cid new file mode 100644 index 000000000000..e88961a6c3c4 --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/Micro1.nrrd.cid @@ -0,0 +1 @@ +bafkreicyvy5uecyza76g4bcr2nbswone6rrlkubeeyte6yeqe62j3u76qa diff --git a/Modules/Filtering/RLEImage/test/Input/NoSlices.nrrd.cid b/Modules/Filtering/RLEImage/test/Input/NoSlices.nrrd.cid new file mode 100644 index 000000000000..dcaa296fbd11 --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/NoSlices.nrrd.cid @@ -0,0 +1 @@ +bafkreig5ygh4vqwbhuhgv4shub6nyn5dxv2d6wtgovdwqqkv5iwn4ohf3y diff --git a/Modules/Filtering/RLEImage/test/Input/OneToOne.nrrd.cid b/Modules/Filtering/RLEImage/test/Input/OneToOne.nrrd.cid new file mode 100644 index 000000000000..b7d61ae9ead7 --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/OneToOne.nrrd.cid @@ -0,0 +1 @@ +bafkreighhapx2okkpm767f7e6eszycckghbnvv5slztxhvqtsp63iydh5a diff --git a/Modules/Filtering/RLEImage/test/Input/OneToThree.nrrd.cid b/Modules/Filtering/RLEImage/test/Input/OneToThree.nrrd.cid new file mode 100644 index 000000000000..eeb1bdc05e54 --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/OneToThree.nrrd.cid @@ -0,0 +1 @@ +bafkreicgb2kche6tvqi7hlhjv4ffeodgumsnvbk44oebqzmsjywmurxx2m diff --git a/Modules/Filtering/RLEImage/test/Input/SevenLabels.nrrd.cid b/Modules/Filtering/RLEImage/test/Input/SevenLabels.nrrd.cid new file mode 100644 index 000000000000..8c12c823abb4 --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/SevenLabels.nrrd.cid @@ -0,0 +1 @@ +bafkreibt464276qlxgdff7gbezvdsdy42lvw4xmcxbutwgaeb6ldyl2lou diff --git a/Modules/Filtering/RLEImage/test/Input/SimplestOneToOne.nrrd.cid b/Modules/Filtering/RLEImage/test/Input/SimplestOneToOne.nrrd.cid new file mode 100644 index 000000000000..ee19b5125097 --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/SimplestOneToOne.nrrd.cid @@ -0,0 +1 @@ +bafkreigesnc4q7q75tgo6ejajgie74z33q2sz6nb2tkrm7q7fmjpnlipty diff --git a/Modules/Filtering/RLEImage/test/Input/Small2D.png.cid b/Modules/Filtering/RLEImage/test/Input/Small2D.png.cid new file mode 100644 index 000000000000..b9dfa36c104a --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/Small2D.png.cid @@ -0,0 +1 @@ +bafkreigqf3mup3p6wyhgghtn7myxrc2ohzcpvnkaqcxe2uylc4lg6hhywm diff --git a/Modules/Filtering/RLEImage/test/Input/ThreeAxisFourLabelConflict.nrrd.cid b/Modules/Filtering/RLEImage/test/Input/ThreeAxisFourLabelConflict.nrrd.cid new file mode 100644 index 000000000000..015619094671 --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/ThreeAxisFourLabelConflict.nrrd.cid @@ -0,0 +1 @@ +bafkreielkkmkrrv7oseu3fwjbtu7x6haad73dp4nk2pjxwqrc6bo2imhvq diff --git a/Modules/Filtering/RLEImage/test/Input/TwoAxisDoubleTwoLabelBC.nrrd.cid b/Modules/Filtering/RLEImage/test/Input/TwoAxisDoubleTwoLabelBC.nrrd.cid new file mode 100644 index 000000000000..c1138e28409f --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/TwoAxisDoubleTwoLabelBC.nrrd.cid @@ -0,0 +1 @@ +bafkreihga2ja24eulxbuvyidzbtp5zplsts7fir7fsduuens3yvwnx5q3q diff --git a/Modules/Filtering/RLEImage/test/Input/brainParc.mha.cid b/Modules/Filtering/RLEImage/test/Input/brainParc.mha.cid new file mode 100644 index 000000000000..305376f36f8a --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/brainParc.mha.cid @@ -0,0 +1 @@ +bafkreigrhsu4viwh7twyxc2i4ftk4ba4t26zyfn3qgo66vanj4ge5j2cd4 diff --git a/Modules/Filtering/RLEImage/test/Input/vb-seg.mha.cid b/Modules/Filtering/RLEImage/test/Input/vb-seg.mha.cid new file mode 100644 index 000000000000..2af739f84556 --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/vb-seg.mha.cid @@ -0,0 +1 @@ +bafkreiapyugfzw2syniuvvbn6os7vaobplrr47clelwcwnbygbwcml5wdq diff --git a/Modules/Filtering/RLEImage/test/Input/wb-crop.nrrd.cid b/Modules/Filtering/RLEImage/test/Input/wb-crop.nrrd.cid new file mode 100644 index 000000000000..85cc0bfb0aa8 --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/wb-crop.nrrd.cid @@ -0,0 +1 @@ +bafkreibikb3v5lualk2zu5rodeobslnagtczs3e3ciqycalt7uknmf244y diff --git a/Modules/Filtering/RLEImage/test/Input/wb-seg.nrrd.cid b/Modules/Filtering/RLEImage/test/Input/wb-seg.nrrd.cid new file mode 100644 index 000000000000..b0f9ac362aa0 --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/wb-seg.nrrd.cid @@ -0,0 +1 @@ +bafkreiadn23dzqnn7hpeupwbtalhr36cyfrzvljiy3zashwar4bavrvohq diff --git a/Modules/Filtering/RLEImage/test/Input/wb-seg32.nrrd.cid b/Modules/Filtering/RLEImage/test/Input/wb-seg32.nrrd.cid new file mode 100644 index 000000000000..ae9d5845fc06 --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/wb-seg32.nrrd.cid @@ -0,0 +1 @@ +bafkreie25rdxrmj6ogkfdnfr5qfox37befaqpgzubhftsmqv3fvuogqwzy diff --git a/Modules/Filtering/RLEImage/test/Input/ws.nrrd.cid b/Modules/Filtering/RLEImage/test/Input/ws.nrrd.cid new file mode 100644 index 000000000000..938b42c3169c --- /dev/null +++ b/Modules/Filtering/RLEImage/test/Input/ws.nrrd.cid @@ -0,0 +1 @@ +bafkreig26f7jzqumejcr5o4aexda7uj572ozqnpb74zlrjhgzho6ew5dmm diff --git a/Modules/Filtering/RLEImage/test/itkIteratorTestsForRLEImage.cxx b/Modules/Filtering/RLEImage/test/itkIteratorTestsForRLEImage.cxx new file mode 100644 index 000000000000..e819b8aead63 --- /dev/null +++ b/Modules/Filtering/RLEImage/test/itkIteratorTestsForRLEImage.cxx @@ -0,0 +1,125 @@ +/*========================================================================= + * + * 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 + +#include "itkImage.h" +#include "itkRLEImage.h" +#include "itkVector.h" +#include + +int +itkIteratorTestsForRLEImage(int, char *[]) +{ + std::cout << "Creating an image" << std::endl; + using ScalarImage = itk::RLEImage; + ScalarImage::Pointer o3 = ScalarImage::New(); + + double origin3D[3] = { 5, 2.1, 8.1 }; + double spacing3D[3] = { 1.5, 2.1, 1 }; + + ScalarImage::SizeType imageSize3D = { { 100, 100, 100 } }; + ScalarImage::SizeType bufferSize3D = { { 100, 100, 100 } }; + ScalarImage::SizeType regionSize3D = { { 91, 93, 87 } }; + + ScalarImage::IndexType startIndex3D = { { 0, 0, 0 } }; + ScalarImage::IndexType bufferStartIndex3D = { { 0, 0, 0 } }; + ScalarImage::IndexType regionStartIndex3D = { { 5, 5, 5 } }; + + + ScalarImage::RegionType region; + region.SetSize(imageSize3D); + region.SetIndex(startIndex3D); + o3->SetLargestPossibleRegion(region); + region.SetSize(bufferSize3D); + region.SetIndex(bufferStartIndex3D); + o3->SetBufferedRegion(region); + region.SetSize(regionSize3D); + region.SetIndex(regionStartIndex3D); + o3->SetRequestedRegion(region); + + o3->SetOrigin(origin3D); + o3->SetSpacing(spacing3D); + + o3->Allocate(); + + // extra variables + double elapsedTime; + clock_t start, end; + unsigned long num = regionSize3D[0] * regionSize3D[1] * regionSize3D[2]; + unsigned long i; + bool passed = true; + + // ImageRegionIterator + start = clock(); + itk::ImageRegionIterator it(o3, region); + + unsigned short scalar; + scalar = 5; + + i = 0; + for (; !it.IsAtEnd(); ++it) + { + it.Set(scalar); + ++i; + } + end = clock(); + elapsedTime = (end - start) / (double)CLOCKS_PER_SEC; + + std::cout << "ImageRegionIterator" << std::endl; + std::cout << "\tTime = " << elapsedTime << std::endl; + std::cout << "\tPixels = " << i << std::endl; + + if (i != num) + { + passed = false; + } + + // ImageRegionIteratorWithIndex + start = clock(); + itk::ImageRegionIteratorWithIndex it2(o3, region); + + i = 0; + for (; !it2.IsAtEnd(); ++it2) + { + it2.Set(scalar); + ++i; + } + end = clock(); + elapsedTime = (end - start) / (double)CLOCKS_PER_SEC; + + std::cout << "ImageRegionIteratorWithIndex" << std::endl; + std::cout << "\tTime = " << elapsedTime << std::endl; + std::cout << "\tPixels = " << i << std::endl; + + if (i != num) + { + passed = false; + } + + if (passed) + { + std::cout << "Iterator tests passed" << std::endl; + return EXIT_SUCCESS; + } + else + { + std::cout << "Iterator tests failed" << std::endl; + return EXIT_FAILURE; + } +} diff --git a/Modules/Filtering/RLEImage/test/itkRLEImageIteratorTest.cxx b/Modules/Filtering/RLEImage/test/itkRLEImageIteratorTest.cxx new file mode 100644 index 000000000000..f6ddc2ee42d5 --- /dev/null +++ b/Modules/Filtering/RLEImage/test/itkRLEImageIteratorTest.cxx @@ -0,0 +1,279 @@ +/*========================================================================= + * + * 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 "itkRLEImage.h" +#include + + +// This routine is used to make sure that we call the "const" version +// of GetPixel() (via the operator[]) +template +void +TestConstPixelAccess(const itk::RLEImage & in, itk::RLEImage & out) +{ + typename itk::RLEImage::IndexType regionStartIndex3D = { { 5, 10, 15 } }; + typename itk::RLEImage::IndexType regionEndIndex3D = { { 8, 15, 17 } }; + + T vec; + + vec[0] = 5; + vec[1] = 4; + vec[2] = 3; + vec[3] = 2; + vec[4] = 1; + + out.SetPixel(regionStartIndex3D, vec); + out.SetPixel(regionEndIndex3D, in[regionStartIndex3D]); +} + + +int +itkRLEImageIteratorTest(int, char *[]) +{ + constexpr unsigned int ImageDimension = 3; + + std::cout << "Creating an image" << std::endl; + itk::RLEImage>::Pointer o3 = itk::RLEImage>::New(); + + float origin3D[ImageDimension] = { 5.f, 2.1f, 8.1f }; + float spacing3D[ImageDimension] = { 1.5f, 2.1f, 1.f }; + + itk::RLEImage>::SizeType imageSize3D = { { 20, 40, 60 } }; + + itk::RLEImage>::IndexType startIndex3D = { { 5, 4, 1 } }; + itk::RLEImage>::IndexType regionStartIndex3D = { { 6, 10, 12 } }; + itk::RLEImage>::IndexType regionEndIndex3D = { { 8, 15, 17 } }; + + + itk::RLEImage>::RegionType region; + region.SetSize(imageSize3D); + region.SetIndex(startIndex3D); + o3->SetRegions(region); + o3->SetOrigin(origin3D); + o3->SetSpacing(spacing3D); + + o3->Allocate(); + itk::Vector fillValue; + fillValue.Fill(itk::NumericTraits::max()); + o3->FillBuffer(fillValue); + + std::cout << "Setting/Getting a pixel" << std::endl; + itk::Vector vec; + + vec[0] = 5; + vec[1] = 4; + vec[2] = 3; + vec[3] = 2; + vec[4] = 1; + + (*o3).SetPixel(regionStartIndex3D, vec); + (*o3).SetPixel(regionEndIndex3D, (*o3)[regionStartIndex3D]); + TestConstPixelAccess(*o3, *o3); + + using VectorPixelType = itk::Vector; + using VectorImageType = itk::RLEImage; + + using VectorImageIterator = itk::ImageIterator; + using VectorImageConstIterator = itk::ImageConstIterator; + + VectorImageIterator itr1(o3, region); + VectorImageConstIterator itr2(o3, region); + + // Exercise copy constructor + VectorImageIterator itr3(itr1); + + // Exercise assignment operator + VectorImageIterator itr4; + itr4 = itr1; + + // Exercise operator!= + if (itr4 != itr1) + { + std::cerr << "Error in operator= or operator!=" << std::endl; + return EXIT_FAILURE; + } + + // Exercise operator== + if (!(itr4 == itr1)) + { + std::cerr << "Error in operator= or operator==" << std::endl; + return EXIT_FAILURE; + } + + // Exercise operator<= + if (!(itr4 <= itr1)) + { + std::cerr << "Error in operator= or operator<=" << std::endl; + return EXIT_FAILURE; + } + + // Exercise operator< + if (itr4 < itr1) + { + std::cerr << "Error in operator= or operator<" << std::endl; + return EXIT_FAILURE; + } + + // Exercise operator>= + if (!(itr4 >= itr1)) + { + std::cerr << "Error in operator= or operator>=" << std::endl; + return EXIT_FAILURE; + } + + // Exercise operator> + if (itr4 > itr1) + { + std::cerr << "Error in operator= or operator>" << std::endl; + return EXIT_FAILURE; + } + + // Exercise GetImageIteratorDimension() + if (itr1.GetImageIteratorDimension() != ImageDimension) + { + std::cerr << "Error in GetImageIteratorDimension" << std::endl; + return EXIT_FAILURE; + } + + // Exercise GetIndex() + VectorImageType::IndexType index1 = itr1.GetIndex(); + if (index1 != startIndex3D) + { + std::cerr << "Error in GetIndex()" << std::endl; + return EXIT_FAILURE; + } + + // Exercise SetIndex() + VectorImageType::IndexType index2 = index1; + index2[0]++; + VectorImageIterator itr5 = itr1; + itr5.SetIndex(index2); + if (itr5.GetIndex() != index2) + { + std::cerr << "Error in GetIndex() and/or SetIndex()" << std::endl; + return EXIT_FAILURE; + } + + if (itr5.GetIndex() == itr1.GetIndex()) + { + std::cerr << "Error in GetIndex() and/or SetIndex()" << std::endl; + return EXIT_FAILURE; + } + + // Exercise GetRegion() + VectorImageType::RegionType region1 = itr1.GetRegion(); + if (region1 != region) + { + std::cerr << "Error in GetRegion()" << std::endl; + return EXIT_FAILURE; + } + + // Exercise GetImage() non-const version + VectorImageType * image1 = itr1.GetImage(); + if (image1 != o3.GetPointer()) + { + std::cerr << "Error in GetImage()" << std::endl; + return EXIT_FAILURE; + } + + // Exercise GetImage() const version + const VectorImageType * image2 = itr2.GetImage(); + if (image2 != o3.GetPointer()) + { + std::cerr << "Error in GetImage()" << std::endl; + return EXIT_FAILURE; + } + + // Exercise Get() non-const and const version + { + VectorPixelType vp1 = itr1.Get(); + VectorPixelType vp2 = itr2.Get(); + std::cout << "vp1: " << vp1 << std::endl; + std::cout << "vp2: " << vp2 << std::endl; + if (vp1 != vp2) + { + std::cerr << "Error in Get()" << std::endl; + return EXIT_FAILURE; + } + // verify that the value can be modified + vp1[0]++; + itr1.Set(vp1); + itr2 = itr1; // we need to do this because Set invalidates other itarators + vp2 = itr2.Get(); + if (vp1 != vp2) + { + std::cerr << "Error in Get() and/or Set()" << std::endl; + return EXIT_FAILURE; + } + } + + + // Exercise Value() const and non-const methods + { + VectorPixelType vp1 = itr1.Value(); + VectorPixelType vp2 = itr2.Value(); + if (vp1 != vp2) + { + std::cerr << "Error in Value()" << std::endl; + return EXIT_FAILURE; + } + // verify that the value can be modified + vp1[0]++; + itr1.Set(vp1); + vp2 = itr2.Value(); + if (vp1 != vp2) + { + std::cerr << "Error in Get() and/or Set()" << std::endl; + return EXIT_FAILURE; + } + } + + // Exercise Begin(), GoToBegin(), IsAtBegin() and IsAtEnd() + { + itr1.GoToBegin(); + if (!itr1.IsAtBegin()) + { + std::cerr << "Error in Begin() and/or IsAtBegin()" << std::endl; + return EXIT_FAILURE; + } + if (itr1.IsAtEnd()) + { + std::cerr << "Error in Begin() and/or IsAtEnd()" << std::endl; + return EXIT_FAILURE; + } + } + + + // Exercise End(), GoToEnd(), IsAtBegin() and IsAtEnd() + { + itr1.GoToEnd(); + if (!itr1.IsAtEnd()) + { + std::cerr << "Error in End() and/or IsAtEnd()" << std::endl; + return EXIT_FAILURE; + } + if (itr1.IsAtBegin()) + { + std::cerr << "Error in End() and/or IsAtBegin()" << std::endl; + return EXIT_FAILURE; + } + } + + + return EXIT_SUCCESS; +} diff --git a/Modules/Filtering/RLEImage/test/itkRLEImageIteratorWithIndexTest.cxx b/Modules/Filtering/RLEImage/test/itkRLEImageIteratorWithIndexTest.cxx new file mode 100644 index 000000000000..04528da7e06a --- /dev/null +++ b/Modules/Filtering/RLEImage/test/itkRLEImageIteratorWithIndexTest.cxx @@ -0,0 +1,428 @@ +/*========================================================================= + * + * 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 + +#include "itkNumericTraits.h" +#include "itkRLEImage.h" + + +template +class itkRLEImageIteratorWithIndexTestIteratorTester +{ +public: + using PixelType = TPixelType; + + using ImageType = itk::RLEImage; + + using IteratorType = itk::ImageRegionIteratorWithIndex; + + using ConstIteratorType = itk::ImageRegionConstIteratorWithIndex; + + itkRLEImageIteratorWithIndexTestIteratorTester(const PixelType & value) + { + m_Image = ImageType::New(); + + typename ImageType::SizeType size; + size.Fill(100); + + typename ImageType::IndexType start; + start.Fill(0); + + typename ImageType::RegionType region; + region.SetSize(size); + region.SetIndex(start); + + m_Image->SetRegions(region); + m_Image->Allocate(); + + m_Image->FillBuffer(value); + } + + bool + TestIterator() + { + IteratorType it(m_Image, m_Image->GetBufferedRegion()); + it.GoToBegin(); + while (!it.IsAtEnd()) + { + PixelType value = it.Get(); + PixelType testValue = value * static_cast::ValueType>(2); + it.Set(testValue); + if (it.Get() != testValue) + { + std::cerr << "TestIterator failed!" << std::endl; + return false; + } + ++it; + } + return true; + } + + bool + TestConstIterator() + { + ConstIteratorType it(m_Image, m_Image->GetBufferedRegion()); + it.GoToBegin(); + while (!it.IsAtEnd()) + { + PixelType value = it.Get(); + if (value != it.Get()) // check repeatibility + { + std::cerr << "TestConstIterator failed!" << std::endl; + return false; + } + ++it; + } + return true; + } + + bool + TestReverseIteration() + { + ConstIteratorType it(m_Image, m_Image->GetBufferedRegion()); + it.GoToReverseBegin(); + while (!it.IsAtReverseEnd()) + { + PixelType value = it.Get(); + if (value != it.Get()) // check repeatibility + { + std::cerr << "TestReverseIteration failed!" << std::endl; + return false; + } + --it; + } + return true; + } + +private: + typename ImageType::Pointer m_Image; +}; + +int +itkRLEImageIteratorWithIndexTest(int, char *[]) +{ + bool testPassed = true; // let's be optimistic + + // Instantiate image of various types and + // test the iterators on them + + std::cout << "Testing with Image< char, 3 > " << std::endl; + itkRLEImageIteratorWithIndexTestIteratorTester TesterC(10); + if (TesterC.TestIterator() == false) + { + testPassed = false; + } + if (TesterC.TestConstIterator() == false) + { + testPassed = false; + } + if (TesterC.TestReverseIteration() == false) + { + testPassed = false; + } + + + std::cout << "Testing with Image< unsigned char, 3 > " << std::endl; + itkRLEImageIteratorWithIndexTestIteratorTester TesterUC(10); + if (TesterUC.TestIterator() == false) + { + testPassed = false; + } + if (TesterUC.TestConstIterator() == false) + { + testPassed = false; + } + if (TesterUC.TestReverseIteration() == false) + { + testPassed = false; + } + + std::cout << "Testing with Image< short, 3 > " << std::endl; + itkRLEImageIteratorWithIndexTestIteratorTester TesterS(10); + if (TesterS.TestIterator() == false) + { + testPassed = false; + } + if (TesterS.TestConstIterator() == false) + { + testPassed = false; + } + if (TesterS.TestReverseIteration() == false) + { + testPassed = false; + } + + std::cout << "Testing with Image< unsigned short, 3 > " << std::endl; + itkRLEImageIteratorWithIndexTestIteratorTester TesterUS(10); + if (TesterUS.TestIterator() == false) + { + testPassed = false; + } + if (TesterUS.TestConstIterator() == false) + { + testPassed = false; + } + if (TesterUS.TestReverseIteration() == false) + { + testPassed = false; + } + + std::cout << "Testing with Image< int, 3 > " << std::endl; + itkRLEImageIteratorWithIndexTestIteratorTester TesterI(10); + if (TesterI.TestIterator() == false) + { + testPassed = false; + } + if (TesterI.TestConstIterator() == false) + { + testPassed = false; + } + if (TesterI.TestReverseIteration() == false) + { + testPassed = false; + } + + std::cout << "Testing with Image< unsigned int, 3 > " << std::endl; + itkRLEImageIteratorWithIndexTestIteratorTester TesterUI(10); + if (TesterUI.TestIterator() == false) + { + testPassed = false; + } + if (TesterUI.TestConstIterator() == false) + { + testPassed = false; + } + if (TesterUI.TestReverseIteration() == false) + { + testPassed = false; + } + + std::cout << "Testing with Image< long long, 3 > " << std::endl; + itkRLEImageIteratorWithIndexTestIteratorTester TesterLL(10); + if (TesterLL.TestIterator() == false) + { + testPassed = false; + } + if (TesterLL.TestConstIterator() == false) + { + testPassed = false; + } + if (TesterLL.TestReverseIteration() == false) + { + testPassed = false; + } + + std::cout << "Testing with Image< unsigned long long, 3 > " << std::endl; + itkRLEImageIteratorWithIndexTestIteratorTester TesterULL(10); + if (TesterULL.TestIterator() == false) + { + testPassed = false; + } + if (TesterULL.TestConstIterator() == false) + { + testPassed = false; + } + if (TesterULL.TestReverseIteration() == false) + { + testPassed = false; + } + + std::cout << "Testing with Image< float, 3 > " << std::endl; + itkRLEImageIteratorWithIndexTestIteratorTester TesterF(10.0); + if (TesterF.TestIterator() == false) + { + testPassed = false; + } + if (TesterF.TestConstIterator() == false) + { + testPassed = false; + } + if (TesterF.TestReverseIteration() == false) + { + testPassed = false; + } + + std::cout << "Testing with Image< double, 3 > " << std::endl; + itkRLEImageIteratorWithIndexTestIteratorTester TesterD(10.0); + if (TesterD.TestIterator() == false) + { + testPassed = false; + } + if (TesterD.TestConstIterator() == false) + { + testPassed = false; + } + if (TesterD.TestReverseIteration() == false) + { + testPassed = false; + } + + std::cout << "Testing with Image< itk::Vector, 3 > " << std::endl; + using VC = itk::Vector; + VC vc; + vc.Fill(127); + itkRLEImageIteratorWithIndexTestIteratorTester TesterVC(vc); + if (TesterVC.TestIterator() == false) + { + testPassed = false; + } + if (TesterVC.TestConstIterator() == false) + { + testPassed = false; + } + if (TesterVC.TestReverseIteration() == false) + { + testPassed = false; + } + + std::cout << "Testing with Image< itk::Vector, 3 > " << std::endl; + using VUC = itk::Vector; + VUC vuc; + vuc.Fill(10); + itkRLEImageIteratorWithIndexTestIteratorTester TesterVUC(vuc); + if (TesterVUC.TestIterator() == false) + { + testPassed = false; + } + if (TesterVUC.TestConstIterator() == false) + { + testPassed = false; + } + if (TesterVUC.TestReverseIteration() == false) + { + testPassed = false; + } + + std::cout << "Testing with Image< itk::Vector, 3 > " << std::endl; + using VS = itk::Vector; + VS vs; + vs.Fill(10); + itkRLEImageIteratorWithIndexTestIteratorTester TesterVS(vs); + if (TesterVS.TestIterator() == false) + { + testPassed = false; + } + if (TesterVS.TestConstIterator() == false) + { + testPassed = false; + } + if (TesterVS.TestReverseIteration() == false) + { + testPassed = false; + } + + std::cout << "Testing with Image< itk::Vector, 3 > " << std::endl; + using VUS = itk::Vector; + VUS vus; + vus.Fill(10); + itkRLEImageIteratorWithIndexTestIteratorTester TesterVUS(vus); + if (TesterVUS.TestIterator() == false) + { + testPassed = false; + } + if (TesterVUS.TestConstIterator() == false) + { + testPassed = false; + } + if (TesterVUS.TestReverseIteration() == false) + { + testPassed = false; + } + + std::cout << "Testing with Image< itk::Vector, 3 > " << std::endl; + using VI = itk::Vector; + VI vi; + vi.Fill(10); + itkRLEImageIteratorWithIndexTestIteratorTester TesterVI(vi); + if (TesterVI.TestIterator() == false) + { + testPassed = false; + } + if (TesterVI.TestConstIterator() == false) + { + testPassed = false; + } + if (TesterVI.TestReverseIteration() == false) + { + testPassed = false; + } + + std::cout << "Testing with Image< itk::Vector, 3 > " << std::endl; + using VUI = itk::Vector; + VUI vui; + vui.Fill(10); + itkRLEImageIteratorWithIndexTestIteratorTester TesterVUI(vui); + if (TesterVUI.TestIterator() == false) + { + testPassed = false; + } + if (TesterVUI.TestConstIterator() == false) + { + testPassed = false; + } + if (TesterVUI.TestReverseIteration() == false) + { + testPassed = false; + } + + std::cout << "Testing with Image< itk::Vector, 3 > " << std::endl; + using VF = itk::Vector; + VF vf; + vf.Fill(10); + itkRLEImageIteratorWithIndexTestIteratorTester TesterVF(vf); + if (TesterVF.TestIterator() == false) + { + testPassed = false; + } + if (TesterVF.TestConstIterator() == false) + { + testPassed = false; + } + if (TesterVF.TestReverseIteration() == false) + { + testPassed = false; + } + + std::cout << "Testing with Image< itk::Vector, 3 > " << std::endl; + using VD = itk::Vector; + VD vd; + vd.Fill(10); + itkRLEImageIteratorWithIndexTestIteratorTester TesterVD(vd); + if (TesterVD.TestIterator() == false) + { + testPassed = false; + } + if (TesterVD.TestConstIterator() == false) + { + testPassed = false; + } + if (TesterVD.TestReverseIteration() == false) + { + testPassed = false; + } + + if (!testPassed) + { + std::cout << "Failed" << std::endl; + return EXIT_FAILURE; + } + + std::cout << "Success" << std::endl; + return EXIT_SUCCESS; +} diff --git a/Modules/Filtering/RLEImage/test/itkRLEImageIteratorsForwardBackwardTest.cxx b/Modules/Filtering/RLEImage/test/itkRLEImageIteratorsForwardBackwardTest.cxx new file mode 100644 index 000000000000..25e380481c55 --- /dev/null +++ b/Modules/Filtering/RLEImage/test/itkRLEImageIteratorsForwardBackwardTest.cxx @@ -0,0 +1,167 @@ +/*========================================================================= + * + * 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 + +#include "itkRLEImage.h" + +int +itkRLEImageIteratorsForwardBackwardTest(int, char *[]) +{ + std::cout << "Creating an image" << std::endl; + using ImageType = itk::RLEImage; + + ImageType::Pointer myImage = ImageType::New(); + + ImageType::SizeType size; + + size[0] = 4; + size[1] = 4; + size[2] = 4; + + ImageType::IndexType start; + start.Fill(0); + + ImageType::RegionType region; + region.SetIndex(start); + region.SetSize(size); + + myImage->SetLargestPossibleRegion(region); + myImage->SetBufferedRegion(region); + myImage->SetRequestedRegion(region); + myImage->Allocate(); + + using IteratorType = itk::ImageRegionIteratorWithIndex; + + using ConstIteratorType = itk::ImageRegionConstIteratorWithIndex; + + IteratorType it(myImage, region); + + ImageType::PixelType value; + + value = itk::NumericTraits::ZeroValue(); + + // Store information on the Image + std::cout << "Storing data on the image ... " << std::endl; + + while (!it.IsAtEnd()) + { + value++; + it.Set(value); + ++it; + } + + + // Verification + IteratorType ot(myImage, region); + std::cout << "Verifying the data forwards... "; + + value = itk::NumericTraits::ZeroValue(); + + while (!ot.IsAtEnd()) + { + value++; + + if (ot.Get() != value) + { + std::cerr << "Error in forward pass" << std::endl; + std::cerr << "Values don't correspond to what was stored " << std::endl; + std::cerr << "Test failed at index "; + std::cerr << ot.GetIndex() << std::endl; + std::cerr << "Value stored is = " << ot.Get() << std::endl; + std::cerr << "Value should be = " << value << std::endl; + return EXIT_FAILURE; + } + ++ot; + } + + std::cout << " PASSED !" << std::endl; + + // Verification + std::cout << "Verifying the data backwards... "; + + ot.GoToEnd(); + do + { + --ot; + if (ot.Get() != value) + { + std::cerr << "Error in backwards pass" << std::endl; + std::cerr << "Values don't correspond to what was stored " << std::endl; + std::cerr << "Test failed at index "; + std::cerr << ot.GetIndex() << std::endl; + std::cerr << "Value stored is = " << ot.Get() << std::endl; + std::cerr << "Value should be = " << value << std::endl; + return EXIT_FAILURE; + } + value--; + } while (!ot.IsAtBegin()); + + std::cout << " PASSED !" << std::endl; + + // Verification + ConstIteratorType cot(myImage, region); + std::cout << "Const Iterator: Verifying the data forwards... "; + + value = itk::NumericTraits::ZeroValue(); + + while (!cot.IsAtEnd()) + { + value++; + + if (cot.Get() != value) + { + std::cerr << "Error in forward pass" << std::endl; + std::cerr << "Values don't correspond to what was stored " << std::endl; + std::cerr << "Test failed at index "; + std::cerr << cot.GetIndex() << std::endl; + std::cerr << "Value stored is = " << cot.Get() << std::endl; + std::cerr << "Value should be = " << value << std::endl; + return EXIT_FAILURE; + } + ++cot; + } + + std::cout << " PASSED !" << std::endl; + + // Verification + std::cout << "Const Iterator : Verifying the data backwards... "; + + cot.GoToEnd(); + do + { + --cot; + if (cot.Get() != value) + { + std::cerr << "Error in backwards pass" << std::endl; + std::cerr << "Values don't correspond to what was stored " << std::endl; + std::cerr << "Test failed at index "; + std::cerr << cot.GetIndex() << std::endl; + std::cerr << "Value stored is = " << cot.Get() << std::endl; + std::cerr << "Value should be = " << value << std::endl; + return EXIT_FAILURE; + } + value--; + } while (!cot.IsAtBegin()); + + std::cout << " PASSED !" << std::endl; + + std::cout << std::endl << "Test passed" << std::endl; + + return EXIT_SUCCESS; +} diff --git a/Modules/Filtering/RLEImage/test/itkRLEImageMultiLabelMeshPipelineFriendTest.cxx b/Modules/Filtering/RLEImage/test/itkRLEImageMultiLabelMeshPipelineFriendTest.cxx new file mode 100644 index 000000000000..416da5990879 --- /dev/null +++ b/Modules/Filtering/RLEImage/test/itkRLEImageMultiLabelMeshPipelineFriendTest.cxx @@ -0,0 +1,101 @@ +/*========================================================================= + * + * 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. + * + *=========================================================================*/ + +// This test pins the `friend class ::MultiLabelMeshPipeline;` grant +// declared by `itk::ImageConstIterator>` and +// `itk::ImageRegionConstIterator>` (see +// itkRLEImageConstIterator.h and itkRLEImageRegionConstIterator.h). +// +// `MultiLabelMeshPipeline` is the downstream consumer (originally in +// ITK-SNAP) that needs direct access to the iterators' protected +// scan-line bookkeeping (m_Index0 / m_BeginIndex0 / m_EndIndex0 / +// m_RealIndex / m_SegmentRemainder / m_BI) for the multi-label +// surface-extraction pipeline. Removing the friendship would silently +// break that downstream build. This test compiles successfully if and +// only if the friendship is present, so it acts as a build-time gate +// against accidental removal. + +#include "itkRLEImage.h" +#include "itkRLEImageRegionIterator.h" +#include "itkImageRegionIterator.h" + +// MultiLabelMeshPipeline is declared at global scope (matching the +// `friend class ::MultiLabelMeshPipeline;` grants) and reads several +// protected members of the const-iterator. The class is intentionally +// defined here in test scope rather than imported from the downstream +// project; it is the simplest in-tree witness to the friend-access +// contract. +class MultiLabelMeshPipeline +{ +public: + template + static bool + ExerciseProtectedAccess(const TIterator & it) + { + // Access several of the protected scan-line bookkeeping members. + // If the friendship is removed, every line below becomes a + // protected-access compile error. + const auto idx0 = it.m_Index0; + const auto beginIdx0 = it.m_BeginIndex0; + const auto endIdx0 = it.m_EndIndex0; + const auto realIdx = it.m_RealIndex; + const auto segRem = it.m_SegmentRemainder; + const bool inRange = idx0 >= beginIdx0 && idx0 <= endIdx0; + return inRange && (realIdx + segRem) >= 0; + } +}; + +int +itkRLEImageMultiLabelMeshPipelineFriendTest(int, char *[]) +{ + using PixelType = unsigned short; + constexpr unsigned int Dimension = 3; + using ImageType = itk::RLEImage; + using ConstIteratorType = itk::ImageConstIterator; + using RegionConstIteratorType = itk::ImageRegionConstIterator; + + auto image = ImageType::New(); + + ImageType::IndexType idx{}; + ImageType::SizeType size{ { 4, 4, 4 } }; + ImageType::RegionType region{ idx, size }; + + image->SetRegions(region); + image->Allocate(true); + image->FillBuffer(PixelType{ 0 }); + + // Plain ImageConstIterator> path + ConstIteratorType cit(image, region); + cit.GoToBegin(); + if (!MultiLabelMeshPipeline::ExerciseProtectedAccess(cit)) + { + std::cerr << "Friend access via ImageConstIterator returned false.\n"; + return EXIT_FAILURE; + } + + // ImageRegionConstIterator> path (separate friend grant) + RegionConstIteratorType rcit(image, region); + rcit.GoToBegin(); + if (!MultiLabelMeshPipeline::ExerciseProtectedAccess(rcit)) + { + std::cerr << "Friend access via ImageRegionConstIterator returned false.\n"; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/Modules/Filtering/RLEImage/test/itkRLEImageRegionConstIteratorWithOnlyIndexTest.cxx b/Modules/Filtering/RLEImage/test/itkRLEImageRegionConstIteratorWithOnlyIndexTest.cxx new file mode 100644 index 000000000000..e946ab7d9bf4 --- /dev/null +++ b/Modules/Filtering/RLEImage/test/itkRLEImageRegionConstIteratorWithOnlyIndexTest.cxx @@ -0,0 +1,230 @@ +/*========================================================================= + * + * 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 + +#include "itkRLEImage.h" + +template +class itkRLEImageRegionConstIteratorWithOnlyIndexTestIteratorTester +{ +public: + using ImageType = TImage; + using IndexType = typename ImageType::IndexType; + + using ConstIteratorType = itk::ImageRegionConstIteratorWithOnlyIndex; + + itkRLEImageRegionConstIteratorWithOnlyIndexTestIteratorTester() + { + m_Image = ImageType::New(); + + typename ImageType::SizeType size; + size.Fill(100); + + typename ImageType::IndexType start; + start.Fill(0); + + typename ImageType::RegionType region; + region.SetSize(size); + region.SetIndex(start); + + m_Image->SetRegions(region); + m_Image->Allocate(); + + // Setup a smaller requested region + size.Fill(50); + // size[0] = 40; + start.Fill(10); + // start[0] = 8; + region.SetSize(size); + region.SetIndex(start); + m_Image->SetRequestedRegion(region); + } + + bool + TestConstIterator() + { + typename ImageType::RegionType region = m_Image->GetBufferedRegion(); + if (TestByRegion(region) == false) + { + std::cout << "Failed testing buffered region." << std::endl; + return false; + } + + region = m_Image->GetRequestedRegion(); + if (TestByRegion(region) == false) + { + std::cout << "Failed testing requested region." << std::endl; + return false; + } + + return true; + } + + bool + TestByRegion(typename ImageType::RegionType & region) + { + ConstIteratorType it(m_Image, region); + it.GoToBegin(); + typename ImageType::IndexValueType step = 0; + + while (!it.IsAtEnd()) + { + IndexType index = it.GetIndex(); + // Check to see if the index is within allowed bounds + bool isInside = region.IsInside(index); + if (!isInside) + { + std::cout << "Index is not inside region! - " << index << std::endl; + return false; + } + // check repeatibility + if (index != it.GetIndex()) + { + std::cout << "Failed to repeat GetIndex." << std::endl; + return false; + } + // increment and test index + ++it; + IndexType truthIndex; + truthIndex[0] = step % region.GetSize()[0] + region.GetIndex()[0]; + truthIndex[1] = step / region.GetSize()[0] + region.GetIndex()[1]; + if (ImageType::GetImageDimension() > 2) + { + truthIndex[1] = (step / region.GetSize()[0]) % region.GetSize()[1] + region.GetIndex()[1]; + truthIndex[2] = step / (region.GetSize()[0] * region.GetSize()[1]) + region.GetIndex()[2]; + } + + if (index != truthIndex) + { + std::cout << "Failed single increment. step: " << step << " index: " << index << " truthIndex: " << truthIndex + << std::endl; + return false; + } + ++step; + // check repeatibility after decrement + --it; + if (index != it.GetIndex()) + { + std::cout << "Failed to increment and decrement." << std::endl; + return false; + } + ++it; + } + + // Test iterating fwd by line + IndexType index; + it.GoToBegin(); + index = it.GetIndex(); + for (unsigned int i = 0; i < region.GetSize()[0]; i++) + { + ++it; + } + if (index[0] != it.GetIndex()[0] || index[1] != it.GetIndex()[1] - 1) + { + std::cout << "Failed iterating forward by line." << std::endl; + } + + // iterate back + for (unsigned int i = 0; i < region.GetSize()[0]; i++) + { + --it; + } + if (index != it.GetIndex()) + { + std::cout << "Failed iterating back by line." << std::endl; + } + + // Test iterating fwd by slice + if (ImageType::GetImageDimension() > 2) + { + it.GoToBegin(); + index = it.GetIndex(); + for (unsigned int i = 0; i < region.GetSize()[0] * region.GetSize()[1]; i++) + { + ++it; + } + ++it; // extra step + if (index[0] != it.GetIndex()[0] - 1 || index[1] != it.GetIndex()[1] || index[2] != it.GetIndex()[2] - 1) + { + std::cout << "Failed iterating forward by slice." << std::endl; + } + + // iterate back + for (unsigned int i = 0; i < region.GetSize()[0] * region.GetSize()[1]; i++) + { + --it; + } + --it; + if (index != it.GetIndex()) + { + std::cout << "Failed iterating back by slice." << std::endl; + } + } + + return true; + } + +private: + typename ImageType::Pointer m_Image; +}; + +int +itkRLEImageRegionConstIteratorWithOnlyIndexTest(int, char *[]) +{ + bool testPassed = true; // let's be optimistic + + // Instantiate image of various types and + // test the iterators on them + + { + std::cout << "Testing with Image< char, 3 >... " << std::endl; + itkRLEImageRegionConstIteratorWithOnlyIndexTestIteratorTester> Tester; + if (Tester.TestConstIterator() == false) + { + testPassed = false; + } + } + + { + std::cout << "Testing with ImageBase< 2 >... " << std::endl; + itkRLEImageRegionConstIteratorWithOnlyIndexTestIteratorTester> Tester; + if (Tester.TestConstIterator() == false) + { + testPassed = false; + } + } + + { + std::cout << "Testing with ImageBase< 3 >... " << std::endl; + itkRLEImageRegionConstIteratorWithOnlyIndexTestIteratorTester> Tester; + if (Tester.TestConstIterator() == false) + { + testPassed = false; + } + } + + if (!testPassed) + { + std::cout << "Failed" << std::endl; + return EXIT_FAILURE; + } + + std::cout << "Success" << std::endl; + return EXIT_SUCCESS; +} diff --git a/Modules/Filtering/RLEImage/test/itkRLEImageRegionIteratorTest.cxx b/Modules/Filtering/RLEImage/test/itkRLEImageRegionIteratorTest.cxx new file mode 100644 index 000000000000..20be82e4ec6c --- /dev/null +++ b/Modules/Filtering/RLEImage/test/itkRLEImageRegionIteratorTest.cxx @@ -0,0 +1,245 @@ +/*========================================================================= + * + * 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 + +#include "itkRLEImage.h" + + +// This routine is used to make sure that we call the "const" version +// of GetPixel() (via the operator[]) +template +void +TestConstPixelAccess(const itk::RLEImage & in, itk::RLEImage & out) +{ + typename itk::RLEImage::IndexType regionStartIndex3D = { { 5, 10, 15 } }; + typename itk::RLEImage::IndexType regionEndIndex3D = { { 8, 15, 17 } }; + + T vec; + + vec[0] = 5; + vec[1] = 4; + vec[2] = 3; + vec[3] = 2; + vec[4] = 1; + + out.SetPixel(regionStartIndex3D, vec); + out.SetPixel(regionEndIndex3D, in[regionStartIndex3D]); + // out[regionStartIndex3D] = vec; + // out[regionEndIndex3D] = in[regionStartIndex3D]; +} + + +int +itkRLEImageRegionIteratorTest(int, char *[]) +{ + std::cout << "Creating an image" << std::endl; + itk::RLEImage>::Pointer o3 = itk::RLEImage>::New(); + + int status = 0; + + float origin3D[3] = { 5.f, 2.1f, 8.1f }; + float spacing3D[3] = { 1.5f, 2.1f, 1.f }; + + itk::RLEImage>::SizeType imageSize3D = { { 20, 40, 60 } }; + itk::RLEImage>::SizeType bufferSize3D = { { 20, 20, 17 } }; + itk::RLEImage>::SizeType regionSize3D = { { 4, 6, 6 } }; + + itk::RLEImage>::IndexType startIndex3D = { { 5, 4, 1 } }; + itk::RLEImage>::IndexType bufferStartIndex3D = { { 5, 5, 1 } }; + itk::RLEImage>::IndexType regionStartIndex3D = { { 5, 10, 12 } }; + itk::RLEImage>::IndexType regionEndIndex3D = { { 8, 15, 17 } }; + + + itk::RLEImage>::RegionType region; + region.SetSize(imageSize3D); + region.SetIndex(startIndex3D); + o3->SetLargestPossibleRegion(region); + region.SetSize(bufferSize3D); + region.SetIndex(bufferStartIndex3D); + o3->SetBufferedRegion(region); + region.SetSize(regionSize3D); + region.SetIndex(regionStartIndex3D); + o3->SetRequestedRegion(region); + + o3->SetOrigin(origin3D); + o3->SetSpacing(spacing3D); + + o3->Allocate(); + + std::cout << "Setting/Getting a pixel" << std::endl; + itk::Vector vec; + + vec[0] = 5; + vec[1] = 4; + vec[2] = 3; + vec[3] = 2; + vec[4] = 1; + + (*o3).SetPixel(regionStartIndex3D, vec); + (*o3).SetPixel(regionEndIndex3D, (*o3)[regionStartIndex3D]); + TestConstPixelAccess(*o3, *o3); + + + itk::ImageIterator>> standardIt(o3, region); + + // Iterate over a region using a simple for loop + itk::ImageRegionIterator>> it(o3, region); + + std::cout << "Simple iterator loop: "; + for (; !it.IsAtEnd(); ++it) + { + itk::RLEImage>::IndexType index = it.GetIndex(); + std::cout << index << std::endl; + } + + itk::ImageRegionConstIterator>> standardCIt(o3, region); + + // Iterate over a region using a simple for loop and a const iterator + itk::ImageRegionConstIterator>> cit(o3, region); + + std::cout << "Simple const iterator loop: "; + for (; !cit.IsAtEnd(); ++cit) + { + itk::RLEImage>::IndexType index = cit.GetIndex(); + std::cout << index << std::endl; + } + + + // Iterator over the region backwards using a simple for loop + itk::ImageRegionIterator>> backIt(o3, region); + + backIt.GoToEnd(); // one pixel past the end of the region + do + { + --backIt; + + itk::RLEImage>::IndexType index = backIt.GetIndex(); + std::cout << "Simple iterator backwards loop: "; + for (unsigned int i = 0; i < index.GetIndexDimension(); i++) + { + std::cout << index[i] << " "; + } + std::cout << std::endl; + } while (!backIt.IsAtBegin()); // stop when we reach the beginning + + // Iterate over a region, then change the region and iterate over the new region + { + // Create an image + using TestImageType = itk::RLEImage; + TestImageType::IndexType imageCorner; + imageCorner.Fill(0); + + TestImageType::SizeType imageSize; + imageSize.Fill(3); + + TestImageType::RegionType imageRegion(imageCorner, imageSize); + + TestImageType::Pointer image = TestImageType::New(); + image->SetRegions(imageRegion); + image->Allocate(); + + itk::ImageRegionIterator createImageIterator(image, imageRegion); + + // Set all pixels with first index == 0 to 0, and set the rest of the image to 255 + while (!createImageIterator.IsAtEnd()) + { + if (createImageIterator.GetIndex()[0] == 0) + { + createImageIterator.Set(0); + } + else + { + createImageIterator.Set(255); + } + + ++createImageIterator; + } + + // Setup and iterate over the first region + TestImageType::IndexType region1Start; + region1Start.Fill(0); + + TestImageType::SizeType regionSize; + regionSize.Fill(2); + + TestImageType::RegionType region1(region1Start, regionSize); + + itk::ImageRegionConstIterator imageIterator(image, region1); + + std::vector expectedValuesRegion1(8); + expectedValuesRegion1[0] = 0; + expectedValuesRegion1[1] = 255; + expectedValuesRegion1[2] = 0; + expectedValuesRegion1[3] = 255; + expectedValuesRegion1[4] = 0; + expectedValuesRegion1[5] = 255; + expectedValuesRegion1[6] = 0; + expectedValuesRegion1[7] = 255; + unsigned int counter = 0; + while (!imageIterator.IsAtEnd()) + { + if (imageIterator.Get() != expectedValuesRegion1[counter]) + { + status = 1; // Fail + } + counter++; + ++imageIterator; + } + + // Change iteration region + TestImageType::IndexType region2start; + region2start.Fill(1); + + TestImageType::RegionType region2(region2start, regionSize); + + imageIterator.SetRegion(region2); + imageIterator.GoToBegin(); + + std::vector expectedValuesRegion2(8); + expectedValuesRegion2[0] = 255; + expectedValuesRegion2[1] = 255; + expectedValuesRegion2[2] = 255; + expectedValuesRegion2[3] = 255; + expectedValuesRegion2[4] = 255; + expectedValuesRegion2[5] = 255; + expectedValuesRegion2[6] = 255; + expectedValuesRegion2[7] = 255; + counter = 0; + while (!imageIterator.IsAtEnd()) + { + if (imageIterator.Get() != expectedValuesRegion2[counter]) + { + status = 1; // Fail + } + counter++; + ++imageIterator; + } + + } // end "Change Region" test + + if (status == 0) + { + std::cout << "Passed" << std::endl; + } + else + { + std::cout << "Failed" << std::endl; + } + return status; +} diff --git a/Modules/Filtering/RLEImage/test/itkRLEImageScanlineIteratorTest1.cxx b/Modules/Filtering/RLEImage/test/itkRLEImageScanlineIteratorTest1.cxx new file mode 100644 index 000000000000..f6e7add8fe36 --- /dev/null +++ b/Modules/Filtering/RLEImage/test/itkRLEImageScanlineIteratorTest1.cxx @@ -0,0 +1,253 @@ +/*========================================================================= + * + * 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 + +#include "itkRLEImage.h" + +// This routine is used to make sure that we call the "const" version +// of GetPixel() (via the operator[]) +template +void +TestConstPixelAccess(const itk::RLEImage & in, + itk::RLEImage & out) +{ + typename itk::RLEImage::IndexType regionStartIndex3D = { { 5, 10, 15 } }; + typename itk::RLEImage::IndexType regionEndIndex3D = { { 8, 15, 17 } }; + + TPixel vec; + + vec[0] = 5; + vec[1] = 4; + vec[2] = 3; + vec[3] = 2; + vec[4] = 1; + + out.SetPixel(regionStartIndex3D, vec); + out.SetPixel(regionEndIndex3D, in[regionStartIndex3D]); + // out[regionStartIndex3D] = vec; + // out[regionEndIndex3D] = in[regionStartIndex3D]; +} + + +int +itkRLEImageScanlineIteratorTest1(int, char *[]) +{ + itk::RLEImage>::Pointer o3 = itk::RLEImage>::New(); + + int status = EXIT_SUCCESS; + + float origin3D[3] = { 5.f, 2.1f, 8.1f }; + float spacing3D[3] = { 1.5f, 2.1f, 1.f }; + + using ImageType = itk::RLEImage>; + + ImageType::SizeType imageSize3D = { { 20, 40, 60 } }; + ImageType::SizeType bufferSize3D = { { 20, 20, 14 } }; + ImageType::SizeType regionSize3D = { { 4, 6, 6 } }; + + ImageType::IndexType startIndex3D = { { 5, 4, 1 } }; + ImageType::IndexType bufferStartIndex3D = { { 5, 3, 5 } }; + ImageType::IndexType regionStartIndex3D = { { 5, 10, 12 } }; + ImageType::IndexType regionEndIndex3D = { { 8, 15, 17 } }; + + + ImageType::RegionType region; + region.SetSize(imageSize3D); + region.SetIndex(startIndex3D); + o3->SetLargestPossibleRegion(region); + region.SetSize(bufferSize3D); + region.SetIndex(bufferStartIndex3D); + o3->SetBufferedRegion(region); + region.SetSize(regionSize3D); + region.SetIndex(regionStartIndex3D); + o3->SetRequestedRegion(region); + + o3->SetOrigin(origin3D); + o3->SetSpacing(spacing3D); + + o3->Allocate(); + + std::cout << "Setting/Getting a pixel" << std::endl; + itk::Vector vec; + + vec[0] = 5; + vec[1] = 4; + vec[2] = 3; + vec[3] = 2; + vec[4] = 1; + + (*o3).SetPixel(regionStartIndex3D, vec); + (*o3).SetPixel(regionEndIndex3D, (*o3)[regionStartIndex3D]); + TestConstPixelAccess(*o3, *o3); + + + itk::ImageIterator standardIt(o3, region); + + // Iterate over a region using a simple for loop + itk::ImageScanlineIterator it(standardIt); + + std::cout << "Simple iterator loop 1\n"; + while (!it.IsAtEnd()) + { + while (!it.IsAtEndOfLine()) + { + ++it; + } + it.NextLine(); + } + + itk::ImageScanlineIterator it2(o3, o3->GetBufferedRegion()); + std::cout << "Simple iterator loop 1bis\n"; + while (!it2.IsAtEnd()) + { + while (!it2.IsAtEndOfLine()) + { + ++it2; + } + it2.NextLine(); + } + + itk::ImageScanlineConstIterator testBeginEnd(o3, region); + testBeginEnd.GoToBeginOfLine(); + testBeginEnd.GoToEndOfLine(); + + itk::ImageScanlineConstIterator standardCIt(o3, region); + + // Iterate over a region using a simple loop and a const iterator + itk::ImageScanlineConstIterator cit(standardIt); + + std::cout << "Simple const iterator loop 2\n"; + while (!cit.IsAtEnd()) + { + while (!cit.IsAtEndOfLine()) + { + ++cit; + } + cit.NextLine(); + } + + while (!cit.IsAtEnd()) + { + cit.NextLine(); + } + + + // Iterate over a region, then change the region and iterate over the new region + { + // Create an image + using TestImageType = itk::Image; + TestImageType::IndexType imageCorner; + imageCorner.Fill(0); + + TestImageType::SizeType imageSize; + imageSize.Fill(3); + + TestImageType::RegionType imageRegion(imageCorner, imageSize); + + TestImageType::Pointer image = TestImageType::New(); + image->SetRegions(imageRegion); + image->Allocate(); + + itk::ImageScanlineIterator createImageIterator(image, imageRegion); + + // Set all pixels with first index == 0 to 0, and set the rest of the image to 255 + while (!createImageIterator.IsAtEnd()) + { + while (!createImageIterator.IsAtEndOfLine()) + { + if (createImageIterator.GetIndex()[0] == 0) + { + createImageIterator.Set(0); + } + else + { + createImageIterator.Set(255); + } + ++createImageIterator; + } + createImageIterator.NextLine(); + } + + // Setup and iterate over the first region + TestImageType::IndexType region1Start; + region1Start.Fill(0); + + TestImageType::SizeType regionSize; + regionSize.Fill(2); + + TestImageType::RegionType region1(region1Start, regionSize); + + itk::ImageScanlineConstIterator imageIterator(image, region1); + + std::vector expectedValuesRegion1(4); + expectedValuesRegion1[0] = 0; + expectedValuesRegion1[1] = 255; + expectedValuesRegion1[2] = 0; + expectedValuesRegion1[3] = 255; + unsigned int counter = 0; + while (!imageIterator.IsAtEnd()) + { + while (!imageIterator.IsAtEndOfLine()) + { + if (imageIterator.Get() != expectedValuesRegion1[counter]) + { + status = EXIT_FAILURE; // Fail + } + counter++; + ++imageIterator; + } + imageIterator.NextLine(); + } + + // Change iteration region + TestImageType::IndexType region2start; + region2start.Fill(1); + + TestImageType::RegionType region2(region2start, regionSize); + + imageIterator.SetRegion(region2); + imageIterator.GoToBegin(); + + std::vector expectedValuesRegion2(4); + expectedValuesRegion2[0] = 255; + expectedValuesRegion2[1] = 255; + expectedValuesRegion2[2] = 255; + expectedValuesRegion2[3] = 255; + counter = 0; + while (!imageIterator.IsAtEnd()) + { + while (!imageIterator.IsAtEndOfLine()) + { + if (imageIterator.Get() != expectedValuesRegion2[counter]) + { + status = EXIT_FAILURE; // Fail + } + counter++; + ++imageIterator; + } + imageIterator.NextLine(); + } + + } // end "Change Region" test + if (status == EXIT_SUCCESS) + std::cout << "All scanline iterator tests successful!\n"; + else + std::cout << "Some scanline iterator tests failed!\n"; + return status; +} diff --git a/Modules/Filtering/RLEImage/test/itkRLEImageTest.cxx b/Modules/Filtering/RLEImage/test/itkRLEImageTest.cxx new file mode 100644 index 000000000000..ced43bbbb87e --- /dev/null +++ b/Modules/Filtering/RLEImage/test/itkRLEImageTest.cxx @@ -0,0 +1,244 @@ +/*========================================================================= + * + * 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 "itkRLEImage.h" +#include "itkImageFileReader.h" +#include "itkImageFileWriter.h" +#include "itkTestingMacros.h" +#include +#include + +template +void +compare(itk::SmartPointer im1, itk::SmartPointer im2) +{ + itk::ImageRegionConstIterator it1(im1, im1->GetLargestPossibleRegion()); + itk::ImageRegionConstIterator it2(im2, im2->GetLargestPossibleRegion()); + while (!it1.IsAtEnd()) + { + if (it1.Get() != it2.Get()) + { + itkGenericExceptionMacro(<< "Images differ. Val1: " << it1.Get() << " Val2: " << it2.Get() + << "\nInd1: " << it1.GetIndex() << " Ind2: " << it2.GetIndex()); + } + ++it1; + ++it2; + } +} + +template +void +roiTest(itk::SmartPointer> orig) +{ + using myRLEImage = itk::RLEImage; + using charRLEImage = itk::RLEImage; + using charConverterType = itk::RegionOfInterestImageFilter; + typename charConverterType::Pointer cConv = charConverterType::New(); + cConv->SetInput(orig); + cConv->SetRegionOfInterest(orig->GetLargestPossibleRegion()); + cConv->Update(); + typename charRLEImage::Pointer cIn = cConv->GetOutput(); + cIn->DisconnectPipeline(); + + using ucharRLEImage = itk::RLEImage; + using ucharConverterType = itk::RegionOfInterestImageFilter; + typename ucharConverterType::Pointer ucConv = ucharConverterType::New(); + ucConv->SetInput(orig); + ucConv->SetRegionOfInterest(orig->GetLargestPossibleRegion()); + ucConv->Update(); + typename ucharRLEImage::Pointer ucIn = ucConv->GetOutput(); + ucIn->DisconnectPipeline(); + + std::cout << "Comparing direct conversions..."; + compare(cIn, ucIn); + std::cout << "OK" << std::endl; + + typename myRLEImage::RegionType reg = orig->GetLargestPossibleRegion(); + typename myRLEImage::RegionType rNeg = reg; + for (unsigned i = 0; i < ImageType::ImageDimension; i++) + { + rNeg.SetIndex(i, -typename myRLEImage::IndexValueType(reg.GetSize(i)) * 3 / 4); + } + cIn->SetRegions(rNeg); + ucIn->SetRegions(rNeg); + std::cout << "Comparing LPR with negative indices..."; + compare(cIn, ucIn); + std::cout << "OK" << std::endl; + + // region for partial coverage, skip X due to RLE representation constraints + for (unsigned i = 1; i < ImageType::ImageDimension; i++) + { + reg.GetModifiableIndex()[i] += (reg.GetSize(i) - 1) / 4; + rNeg.GetModifiableIndex()[i] += typename myRLEImage::IndexValueType(rNeg.GetSize(i) - 1) / 4; + reg.SetSize(i, (reg.GetSize(i) + 1) / 2); + rNeg.SetSize(i, (rNeg.GetSize(i) + 1) / 2); + } + + using myConverterType = itk::RegionOfInterestImageFilter; + typename myConverterType::Pointer myConv = myConverterType::New(); + myConv->SetInput(orig); + myConv->SetRegionOfInterest(reg); + myConv->Update(); + typename myRLEImage::Pointer myIn = myConv->GetOutput(); + + using cRoIType = itk::RegionOfInterestImageFilter; + typename cRoIType::Pointer cRoI = cRoIType::New(); + cRoI->SetInput(cIn); + cRoI->SetRegionOfInterest(rNeg); + cRoI->Update(); + cIn = cRoI->GetOutput(); + cIn->DisconnectPipeline(); + + using ucRoIType = itk::RegionOfInterestImageFilter; + typename ucRoIType::Pointer ucRoI = ucRoIType::New(); + ucRoI->SetInput(ucIn); + ucRoI->SetRegionOfInterest(rNeg); + ucRoI->Update(); + ucIn = ucRoI->GetOutput(); + ucIn->DisconnectPipeline(); + + std::cout << "Comparing RoIs with negative indices..."; + compare(cIn, ucIn); + std::cout << "OK" << std::endl; + + std::cout << "Comparing RoIs with negative and positive indices..."; + compare(cIn, myIn); + compare(ucIn, myIn); + std::cout << "OK" << std::endl; +} + +template +int +doTest(std::string inFilename, std::string outFilename) +{ + using ReaderType = itk::ImageFileReader; + typename ReaderType::Pointer reader = ReaderType::New(); + reader->SetFileName(inFilename); + std::cout << "Reading " << inFilename << std::endl; + reader->Update(); + + using myRLEImage = itk::RLEImage; + using inConverterType = itk::RegionOfInterestImageFilter; + typename inConverterType::Pointer inConv = inConverterType::New(); + inConv->SetInput(reader->GetOutput()); + inConv->SetRegionOfInterest(reader->GetOutput()->GetLargestPossibleRegion()); + std::cout << "Converting regular image to RLEImage" << std::endl; + inConv->Update(); + typename myRLEImage::Pointer test = inConv->GetOutput(); + itk::SizeValueType xSize = test->GetLargestPossibleRegion().GetSize(0); + + std::cout << "Running region of interest tests" << std::endl; + if (xSize > 127) + { + std::cout << "\n xSize>127 (CHAR_MAX)" << std::endl; + ITK_TRY_EXPECT_EXCEPTION(roiTest(test)); + } + else + { + ITK_TRY_EXPECT_NO_EXCEPTION(roiTest(test)); + } + + using outConverterType = itk::RegionOfInterestImageFilter; + typename outConverterType::Pointer outConv = outConverterType::New(); + outConv->SetInput(test); + outConv->SetRegionOfInterest(test->GetLargestPossibleRegion()); + std::cout << "Converting RLEImage to regular image" << std::endl; + outConv->Update(); + + using WriterType = itk::ImageFileWriter; + typename WriterType::Pointer writer = WriterType::New(); + writer->SetFileName(outFilename); + writer->SetInput(outConv->GetOutput()); + writer->SetUseCompression(true); + std::cout << "Writing " << outFilename << std::endl; + writer->Update(); + std::cout << "Test finished" << std::endl; + + return EXIT_SUCCESS; +} + +void +dimTest() +{ + using S2Type = itk::RLEImage; // 2D + using S3Type = itk::RLEImage; // 3D + S2Type::Pointer s2 = S2Type::New(); + S3Type::Pointer s3 = S3Type::New(); + + // instantiation of "RoIType" is dissalowed due to different dimension + // uncommenting the lines below should give a meaningful error message + // using RoIType = itk::RegionOfInterestImageFilter; + // typename RoIType::Pointer roi = RoIType::New(); +} + +int +itkRLEImageTest(int argc, char * argv[]) +{ + if (argc < 3) + { + std::cerr << "Usage: " << argv[0] << " inputImage outputImage" << std::endl; + return EXIT_FAILURE; + } + const char * inputImageFileName = argv[1]; + const char * outputImageFileName = argv[2]; + + using ScalarPixelType = itk::CommonEnums::IOComponent; + itk::ImageIOBase::Pointer imageIO = + itk::ImageIOFactory::CreateImageIO(inputImageFileName, itk::CommonEnums::IOFileMode::ReadMode); + if (!imageIO) + { + std::cerr << "Could not CreateImageIO for: " << inputImageFileName << std::endl; + return EXIT_FAILURE; + } + imageIO->SetFileName(inputImageFileName); + imageIO->ReadImageInformation(); + const ScalarPixelType pixelType = imageIO->GetComponentType(); + const size_t numDimensions = imageIO->GetNumberOfDimensions(); + + try + { + // unused cases are not instantiated because they greatly increase compile time + if (numDimensions == 2 && pixelType == itk::CommonEnums::IOComponent::UCHAR) + { + return doTest>(inputImageFileName, outputImageFileName); + } + if (numDimensions == 3 && + (pixelType == itk::CommonEnums::IOComponent::SHORT || pixelType == itk::CommonEnums::IOComponent::USHORT)) + { + return doTest>(inputImageFileName, outputImageFileName); + } + if (numDimensions == 3) // if not (u)short, then interpret as uint + { + return doTest>(inputImageFileName, outputImageFileName); + } + if (numDimensions == 4 && pixelType == itk::CommonEnums::IOComponent::UCHAR) + { + return doTest>(inputImageFileName, outputImageFileName); + } + + std::cerr << "Unsupported image type:\n Dimensions: " << numDimensions; + std::cerr << "\n Pixel type:" << imageIO->GetComponentTypeAsString(pixelType) << std::endl; + return EXIT_FAILURE; + } + catch (itk::ExceptionObject & error) + { + std::cerr << "Error: " << error << std::endl; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} diff --git a/Modules/Filtering/RLEImage/test/itkRegionOfInterestRLEImageFilterTest.cxx b/Modules/Filtering/RLEImage/test/itkRegionOfInterestRLEImageFilterTest.cxx new file mode 100644 index 000000000000..43ded2911f79 --- /dev/null +++ b/Modules/Filtering/RLEImage/test/itkRegionOfInterestRLEImageFilterTest.cxx @@ -0,0 +1,136 @@ +/*========================================================================= + * + * 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 "itkRLEImage.h" +#include "itkSimpleFilterWatcher.h" +#include + +int +itkRegionOfInterestRLEImageFilterTest(int, char *[]) +{ + constexpr unsigned int Dimension = 3; + using PixelType = itk::Index; + + using ImageType = itk::RLEImage; + + using FilterType = itk::RegionOfInterestImageFilter; + + + using RegionType = ImageType::RegionType; + using SizeType = ImageType::SizeType; + using IndexType = ImageType::IndexType; + using DirectionType = ImageType::DirectionType; + + using IteratorType = itk::ImageRegionIterator; + + FilterType::Pointer filter = FilterType::New(); + + + ImageType::Pointer image = ImageType::New(); + + IndexType start; + start.Fill(0); + + SizeType size; + size[0] = 40; + size[1] = 40; + size[2] = 40; + + RegionType region; + region.SetIndex(start); + region.SetSize(size); + + image->SetRegions(region); + image->Allocate(); + + DirectionType directions; + directions.SetIdentity(); + directions[0][0] = 0.0; + directions[1][0] = 1.0; + directions[2][0] = 0.0; + directions[0][1] = 1.0; + directions[1][1] = 0.0; + directions[2][1] = 0.0; + image->SetDirection(directions); + + // Fill the image pixels with their own index. + IteratorType intr(image, region); + intr.GoToBegin(); + while (!intr.IsAtEnd()) + { + intr.Set(intr.GetIndex()); + ++intr; + } + + + filter->SetInput(image); + + SizeType roiSize; + roiSize[0] = 20; + roiSize[1] = 20; + roiSize[2] = 20; + + IndexType roiStart; + roiStart[0] = 9; + roiStart[1] = 9; + roiStart[2] = 9; + + RegionType regionOfInterest; + regionOfInterest.SetIndex(roiStart); + regionOfInterest.SetSize(roiSize); + + // itk::SimpleFilterWatcher watcher(filter); + filter->SetRegionOfInterest(regionOfInterest); + + // filter->SetNumberOfWorkUnits(1); + filter->Update(); + filter->GetOutput()->Print(std::cout); + + + IteratorType ot(filter->GetOutput(), filter->GetOutput()->GetLargestPossibleRegion()); + + IteratorType it(image, regionOfInterest); + + it.GoToBegin(); + ot.GoToBegin(); + + bool passed = true; + while (!it.IsAtEnd()) + { + IndexType inIndex = it.Get(); + IndexType outIndex = ot.Get(); + if (inIndex[0] != outIndex[0] || inIndex[1] != outIndex[1] || inIndex[2] != outIndex[2]) + { + std::cerr << "Test failed at pixel " << inIndex << std::endl; + std::cerr << "pixel value is " << outIndex << std::endl; + passed = false; + break; + } + + ++it; + ++ot; + } + + if (!passed) + { + return EXIT_FAILURE; + } + + std::cout << "Test PASSED !" << std::endl; + return EXIT_SUCCESS; +} diff --git a/Modules/Filtering/RLEImage/test/manualTest.cxx b/Modules/Filtering/RLEImage/test/manualTest.cxx new file mode 100644 index 000000000000..5be6d44734aa --- /dev/null +++ b/Modules/Filtering/RLEImage/test/manualTest.cxx @@ -0,0 +1,33 @@ +/*========================================================================= + * + * 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. + * + *=========================================================================*/ + +#ifdef _MSC_VER +# pragma warning(disable : 4996) /* deprecation */ +#endif + +#include "itkTestDriverIncludeRequiredIOFactories.h" + +extern int +itkRLEImageTest(int argc, char * argv[]); + +int +main(int argc, char * argv[]) +{ + RegisterRequiredFactories(); + itkRLEImageTest(argc, argv); +} diff --git a/Modules/Filtering/RLEImage/test/rleStats.cxx b/Modules/Filtering/RLEImage/test/rleStats.cxx new file mode 100644 index 000000000000..e7ca3e64816f --- /dev/null +++ b/Modules/Filtering/RLEImage/test/rleStats.cxx @@ -0,0 +1,63 @@ +/*========================================================================= + * + * Copyright NumFOCUS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + *=========================================================================*/ + +#include "itkImageFileReader.h" +#include "itkRLEImage.h" +#include "itkTestDriverIncludeRequiredIOFactories.h" +#include +#include + +int +main(int argc, char * argv[]) +{ + if (argc < 2) + { + std::cerr << "Usage: " << argv[0] << " inputImage" << std::endl; + return EXIT_FAILURE; + } + + using ImageType = itk::Image; + using myRLEImage = itk::RLEImage; + + using ReaderType = itk::ImageFileReader; + ReaderType::Pointer reader = ReaderType::New(); + reader->SetFileName(argv[1]); + + using inConverterType = itk::RegionOfInterestImageFilter; + inConverterType::Pointer inConv = inConverterType::New(); + + try + { + RegisterRequiredFactories(); + reader->Update(); + inConv->SetInput(reader->GetOutput()); + inConv->SetRegionOfInterest(reader->GetOutput()->GetLargestPossibleRegion()); + inConv->Update(); + myRLEImage::Pointer test = inConv->GetOutput(); + std::cout << test; + std::cout << "Invoking CleanUp() method\n"; + test->CleanUp(); + std::cout << test; + } + catch (itk::ExceptionObject & error) + { + std::cerr << "Error: " << error << std::endl; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} diff --git a/Modules/Filtering/RLEImage/wrapping/CMakeLists.txt b/Modules/Filtering/RLEImage/wrapping/CMakeLists.txt new file mode 100644 index 000000000000..98d3bb8ebdc2 --- /dev/null +++ b/Modules/Filtering/RLEImage/wrapping/CMakeLists.txt @@ -0,0 +1,10 @@ +itk_wrap_module(RLEImage) + +set( + WRAPPER_SUBMODULE_ORDER + itkRLEImage + itkRLERegionOfInterestImageFilter +) + +itk_auto_load_submodules() +itk_end_wrap_module() diff --git a/Modules/Filtering/RLEImage/wrapping/itkRLEImage.wrap b/Modules/Filtering/RLEImage/wrapping/itkRLEImage.wrap new file mode 100644 index 000000000000..393de322c949 --- /dev/null +++ b/Modules/Filtering/RLEImage/wrapping/itkRLEImage.wrap @@ -0,0 +1,7 @@ +itk_wrap_class("itk::RLEImage" POINTER) +foreach(d ${ITK_WRAP_IMAGE_DIMS}) + foreach(t ${WRAP_ITK_SCALAR}) + itk_wrap_template("${ITKM_${t}}${d}US" "${ITKT_${t}},${d},unsigned short") + endforeach() +endforeach() +itk_end_wrap_class() diff --git a/Modules/Filtering/RLEImage/wrapping/itkRLEImageConstIterator.notwrapped b/Modules/Filtering/RLEImage/wrapping/itkRLEImageConstIterator.notwrapped new file mode 100644 index 000000000000..9951d9cd29ad --- /dev/null +++ b/Modules/Filtering/RLEImage/wrapping/itkRLEImageConstIterator.notwrapped @@ -0,0 +1 @@ +message(FATAL_ERROR "Iterators are not efficient when wrapped.") diff --git a/Modules/Filtering/RLEImage/wrapping/itkRLEImageIterator.notwrapped b/Modules/Filtering/RLEImage/wrapping/itkRLEImageIterator.notwrapped new file mode 100644 index 000000000000..9951d9cd29ad --- /dev/null +++ b/Modules/Filtering/RLEImage/wrapping/itkRLEImageIterator.notwrapped @@ -0,0 +1 @@ +message(FATAL_ERROR "Iterators are not efficient when wrapped.") diff --git a/Modules/Filtering/RLEImage/wrapping/itkRLEImageRegionConstIterator.notwrapped b/Modules/Filtering/RLEImage/wrapping/itkRLEImageRegionConstIterator.notwrapped new file mode 100644 index 000000000000..9951d9cd29ad --- /dev/null +++ b/Modules/Filtering/RLEImage/wrapping/itkRLEImageRegionConstIterator.notwrapped @@ -0,0 +1 @@ +message(FATAL_ERROR "Iterators are not efficient when wrapped.") diff --git a/Modules/Filtering/RLEImage/wrapping/itkRLEImageRegionIterator.notwrapped b/Modules/Filtering/RLEImage/wrapping/itkRLEImageRegionIterator.notwrapped new file mode 100644 index 000000000000..9951d9cd29ad --- /dev/null +++ b/Modules/Filtering/RLEImage/wrapping/itkRLEImageRegionIterator.notwrapped @@ -0,0 +1 @@ +message(FATAL_ERROR "Iterators are not efficient when wrapped.") diff --git a/Modules/Filtering/RLEImage/wrapping/itkRLEImageScanlineConstIterator.notwrapped b/Modules/Filtering/RLEImage/wrapping/itkRLEImageScanlineConstIterator.notwrapped new file mode 100644 index 000000000000..9951d9cd29ad --- /dev/null +++ b/Modules/Filtering/RLEImage/wrapping/itkRLEImageScanlineConstIterator.notwrapped @@ -0,0 +1 @@ +message(FATAL_ERROR "Iterators are not efficient when wrapped.") diff --git a/Modules/Filtering/RLEImage/wrapping/itkRLEImageScanlineIterator.notwrapped b/Modules/Filtering/RLEImage/wrapping/itkRLEImageScanlineIterator.notwrapped new file mode 100644 index 000000000000..9951d9cd29ad --- /dev/null +++ b/Modules/Filtering/RLEImage/wrapping/itkRLEImageScanlineIterator.notwrapped @@ -0,0 +1 @@ +message(FATAL_ERROR "Iterators are not efficient when wrapped.") diff --git a/Modules/Filtering/RLEImage/wrapping/itkRLERegionOfInterestImageFilter.wrap b/Modules/Filtering/RLEImage/wrapping/itkRLERegionOfInterestImageFilter.wrap new file mode 100644 index 000000000000..bdb01cd5f1af --- /dev/null +++ b/Modules/Filtering/RLEImage/wrapping/itkRLERegionOfInterestImageFilter.wrap @@ -0,0 +1,58 @@ +set(WRAPPER_AUTO_INCLUDE_HEADERS OFF) + +itk_wrap_include("itkRLEImage.h") +itk_wrap_include("itkRLERegionOfInterestImageFilter.h") + +# first wrap the ImageSource (first templated class in inheritance hierarchy) +itk_wrap_class("itk::ImageSource" POINTER) +foreach(d ${ITK_WRAP_IMAGE_DIMS}) + foreach(t ${WRAP_ITK_SCALAR}) + # ImageSource only has output. Do it only for RLEImage, as regular image is wrapped elsewhere. + itk_wrap_template("RLEImage${ITKM_${t}}${d}US" "itk::RLEImage<${ITKT_${t}},${d},unsigned short>") + endforeach() +endforeach() +itk_end_wrap_class() + +# now wrap the base class: ImageToImageFilter +itk_wrap_class("itk::ImageToImageFilter" POINTER) +foreach(d ${ITK_WRAP_IMAGE_DIMS}) + foreach(t ${WRAP_ITK_SCALAR}) + # RLEImage to RLEImage + itk_wrap_template("RLEImage${ITKM_${t}}${d}USRLEImage${ITKM_${t}}${d}US" + "itk::RLEImage<${ITKT_${t}},${d},unsigned short>,itk::RLEImage<${ITKT_${t}},${d},unsigned short>" + ) + + # RLEImage to itk::Image + itk_wrap_template("RLEImage${ITKM_${t}}${d}USI${ITKM_${t}}${d}" + "itk::RLEImage<${ITKT_${t}},${d},unsigned short>,itk::Image<${ITKT_${t}},${d}>" + ) + + # itk::Image to RLEImage + itk_wrap_template("I${ITKM_${t}}${d}RLEImage${ITKM_${t}}${d}US" + "itk::Image<${ITKT_${t}},${d}>,itk::RLEImage<${ITKT_${t}},${d},unsigned short>" + ) + endforeach() +endforeach() +itk_end_wrap_class() + +# now we can wrap the class of interest with our custom type (RLEImage) +itk_wrap_class("itk::RegionOfInterestImageFilter" POINTER) +foreach(d ${ITK_WRAP_IMAGE_DIMS}) + foreach(t ${WRAP_ITK_SCALAR}) + # RLEImage to RLEImage + itk_wrap_template("RLEImage${ITKM_${t}}${d}USRLEImage${ITKM_${t}}${d}US" + "itk::RLEImage<${ITKT_${t}},${d},unsigned short>,itk::RLEImage<${ITKT_${t}},${d},unsigned short>" + ) + + # RLEImage to itk::Image + itk_wrap_template("RLEImage${ITKM_${t}}${d}USI${ITKM_${t}}${d}" + "itk::RLEImage<${ITKT_${t}},${d},unsigned short>,itk::Image<${ITKT_${t}},${d}>" + ) + + # itk::Image to RLEImage + itk_wrap_template("I${ITKM_${t}}${d}RLEImage${ITKM_${t}}${d}US" + "itk::Image<${ITKT_${t}},${d}>,itk::RLEImage<${ITKT_${t}},${d},unsigned short>" + ) + endforeach() +endforeach() +itk_end_wrap_class() diff --git a/Modules/Filtering/RLEImage/wrapping/test/CMakeLists.txt b/Modules/Filtering/RLEImage/wrapping/test/CMakeLists.txt new file mode 100644 index 000000000000..1204796b7e9f --- /dev/null +++ b/Modules/Filtering/RLEImage/wrapping/test/CMakeLists.txt @@ -0,0 +1,20 @@ +# test regular image to RLE conversion +itk_python_expression_add_test( + NAME itkRegionOfInterestImageFilterISS2RLEImageSS2USPythonTest + EXPRESSION + "instance = itk.RegionOfInterestImageFilter.ISS2RLEImageSS2US.New()" +) + +# test RLE to regular image conversion +itk_python_expression_add_test( + NAME itkRegionOfInterestImageFilterRLEImageSS2USISS2PythonTest + EXPRESSION + "instance = itk.RegionOfInterestImageFilter.RLEImageSS2USISS2.New()" +) + +# test RLE to RLE (plain RoI operation) +itk_python_expression_add_test( + NAME itkRegionOfInterestImageFilterRLEImageSS2USRLEImageSS2USPythonTest + EXPRESSION + "instance = itk.RegionOfInterestImageFilter.RLEImageSS2USRLEImageSS2US.New()" +) diff --git a/Modules/Remote/RLEImage.remote.cmake b/Modules/Remote/RLEImage.remote.cmake deleted file mode 100644 index e254dffe06f0..000000000000 --- a/Modules/Remote/RLEImage.remote.cmake +++ /dev/null @@ -1,57 +0,0 @@ -#-- # Grading Level Criteria Report -#-- EVALUATION DATE: 2020-03-24 -#-- EVALUATORS: [Dženan Zukić] -#-- -#-- ## Compliance level 5 star (AKA ITK main modules, or remote modules that could become core modules) -#-- - [ ] Widespread community dependance -#-- - [ ] Above 90% code coverage -#-- - [ ] CI dashboards and testing monitored rigorously -#-- - [X] Key API features are exposed in wrapping interface -#-- - [ ] All requirements of Levels 4,3,2,1 -#-- -#-- ## Compliance Level 4 star (Very high-quality code, perhaps small community dependance) -#-- - [X] Meets all ITK code style standards -#-- - [X] No external requirements beyond those needed by ITK proper -#-- - [ ] Builds and passes tests on all supported platforms within 1 month of each core tagged release -#-- - [ ] Windows Shared Library Build with Visual Studio -#-- - [ ] Mac with clang compiller -#-- - [ ] Linux with gcc compiler -#-- - [ ] Active developer community dedicated to maintaining code-base -#-- - [ ] 75% code coverage demonstrated for testing suite -#-- - [X] Continuous integration testing performed -#-- - [X] All requirements of Levels 3,2,1 -#-- -#-- ## Compliance Level 3 star (Quality beta code) -#-- - [X] API | executable interface is considered mostly stable and feature complete -#-- - [X] 10% C0-code coverage demonstrated for testing suite -#-- - [X] Some tests exist and pass on at least some platform -#-- - [X] All requirements of Levels 2,1 -#-- -#-- ## Compliance Level 2 star (Alpha code feature API development or niche community/execution environment dependance ) -#-- - [X] Compiles for at least 1 niche set of execution envirionments, and perhaps others -#-- (may depend on specific external tools like a java environment, or specific external libraries to work ) -#-- - [X] All requirements of Levels 1 -#-- -#-- ## Compliance Level 1 star (Pre-alpha features under development and code of unknown quality) -#-- - [X] Code complies on at least 1 platform -#-- -#-- ## Compliance Level 0 star ( Code/Feature of known poor-quality or deprecated status ) -#-- - [ ] Code reviewed and explicitly identified as not recommended for use -#-- -#-- ### Please document here any justification for the criteria above -# Code style enforced by clang-format on 2020-02-19, and clang-tidy modernizations completed - -# Contact: Dženan Zukić -itk_fetch_module( - RLEImage - "Run-length encoded memory compression scheme for an itk::Image. - -Insight Journal article: -https://doi.org/10.54294/t82x76 - -This work is supported by NIH grant R01 EB014346 -'Continued development and maintenance of the ITK-SNAP 3D image segmentation software'." - MODULE_COMPLIANCE_LEVEL 3 - GIT_REPOSITORY https://github.com/KitwareMedical/ITKRLEImage.git - GIT_TAG d7a59ec1bb2edfd55d96cb664b73e7b1e29ed410 - ) diff --git a/pyproject.toml b/pyproject.toml index cf4dc7d016b1..9f1dd62bf5ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,6 +64,7 @@ cmd = '''cmake -DModule_SplitComponents:BOOL=ON -DModule_IOMeshMZ3:BOOL=ON -DModule_IOFDF:BOOL=ON + -DModule_RLEImage:BOOL=ON -DITK_COMPUTER_MEMORY_SIZE:STRING=11 -DModule_StructuralSimilarity:BOOL=ON''' description = "Configure ITK for CI (with ccache compiler launcher)"