├── README.md ├── build └── CMakeLists.txt ├── include ├── Array2D.h ├── Data2D.h ├── Disp2D.h ├── Image2D.h ├── ROI2D.h ├── Strain2D.h └── ncorr.h ├── license.txt ├── src ├── Array2D.cpp ├── Data2D.cpp ├── Disp2D.cpp ├── Image2D.cpp ├── ROI2D.cpp ├── Strain2D.cpp └── ncorr.cpp └── test ├── bin └── images │ ├── ohtcfrp_00.png │ ├── ohtcfrp_01.png │ ├── ohtcfrp_02.png │ ├── ohtcfrp_03.png │ ├── ohtcfrp_04.png │ ├── ohtcfrp_05.png │ ├── ohtcfrp_06.png │ ├── ohtcfrp_07.png │ ├── ohtcfrp_08.png │ ├── ohtcfrp_09.png │ ├── ohtcfrp_10.png │ ├── ohtcfrp_11.png │ └── roi.png ├── build └── CMakeLists.txt └── src └── ncorr_test.cpp /README.md: -------------------------------------------------------------------------------- 1 | # ncorr_2D_cpp 2 | 3 | This is the offical repo for the complete C++ port of: 4 | 5 | ``` 6 | Ncorr: open-source 2D digital image correlation matlab software 7 | J Blaber, B Adair, A Antoniou 8 | Experimental Mechanics 55 (6), 1105-1122 9 | ``` 10 | 11 | Please cite this paper if you use this software in your research. 12 | 13 | Future plans for this code are to write a real command-line executable to help automation and to containerize the code with Docker. Stay tuned! 14 | -------------------------------------------------------------------------------- /build/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | CMAKE_MINIMUM_REQUIRED(VERSION 3.2) 2 | PROJECT(ncorr_library) 3 | 4 | # Only tested for g++ on Ubuntu 12.04. This assumes all required libraries have been 5 | # installed, so directories to dependent libraries and their headers are not explicitly 6 | # included, since the install directories are searched automatically by g++. 7 | 8 | # Set files 9 | SET(ncorr_src ../src/ncorr.cpp ../src/Strain2D.cpp ../src/Disp2D.cpp ../src/Data2D.cpp ../src/ROI2D.cpp ../src/Image2D.cpp ../src/Array2D.cpp) 10 | SET(ncorr_h ../include/ncorr.h ../include/Strain2D.h ../include/Disp2D.h ../include/Data2D.h ../include/ROI2D.h ../include/Image2D.h ../include/Array2D.h) 11 | 12 | # Set include directory 13 | INCLUDE_DIRECTORIES(../include) 14 | 15 | # Set output for library 16 | SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ../lib) 17 | 18 | # Add library 19 | ADD_LIBRARY(ncorr STATIC ${ncorr_src}) 20 | 21 | # Set C++11 support 22 | set_property(TARGET ncorr PROPERTY CXX_STANDARD 11) 23 | set_property(TARGET ncorr PROPERTY CXX_STANDARD_REQUIRED ON) 24 | 25 | # Set -03 optimization 26 | INCLUDE(CheckCXXCompilerFlag) 27 | CHECK_CXX_COMPILER_FLAG("-O3" COMPILER_SUPPORTS_O3) 28 | if (COMPILER_SUPPORTS_O3) 29 | SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3") 30 | endif() 31 | 32 | # Disable debugging 33 | ADD_DEFINITIONS(-DNDEBUG) 34 | 35 | # Install library 36 | INSTALL(TARGETS ncorr DESTINATION lib) 37 | INSTALL(FILES ${ncorr_h} DESTINATION include) 38 | -------------------------------------------------------------------------------- /include/Data2D.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: Data2D.h 3 | * Author: justin 4 | * 5 | * Created on February 28, 2015, 8:57 PM 6 | */ 7 | 8 | #ifndef DATA2D_H 9 | #define DATA2D_H 10 | 11 | #include "Array2D.h" 12 | #include "Image2D.h" 13 | #include "ROI2D.h" 14 | 15 | namespace ncorr { 16 | 17 | namespace details { 18 | class Data2D_nlinfo_interpolator; 19 | } 20 | 21 | class Data2D final { 22 | // -------------------------------------------------------------------------- // 23 | // -------------------------------------------------------------------------- // 24 | // Data2D encompasses everything associated with a 2D Data array. It // 25 | // supports: // 26 | // 1) Having a scalefactor, as some 2D data plots can be "reduced" to save // 27 | // space or computational savings. // 28 | // 2) Having a region of interest, as not all 2D Data are "full." This // 29 | // supports ROI2D based interpolation. // // 30 | // -------------------------------------------------------------------------- // 31 | // -------------------------------------------------------------------------- // 32 | public: 33 | typedef std::ptrdiff_t difference_type; 34 | typedef std::pair coords; 35 | typedef details::Data2D_nlinfo_interpolator nlinfo_interpolator; 36 | 37 | // Rule of 5 and destructor ----------------------------------------------// 38 | Data2D() noexcept : scalefactor() { } 39 | Data2D(const Data2D&) = default; 40 | Data2D(Data2D&&) noexcept = default; 41 | Data2D& operator=(const Data2D&) = default; 42 | Data2D& operator=(Data2D&&) = default; 43 | ~Data2D() noexcept = default; 44 | 45 | // Additional constructors -----------------------------------------------// 46 | Data2D(Array2D, const ROI2D&, difference_type); // r-value 47 | Data2D(Array2D, const ROI2D&); // r-value 48 | Data2D(Array2D, difference_type); // r-value 49 | explicit Data2D(Array2D); // r-value 50 | 51 | // Static factory methods ------------------------------------------------// 52 | static Data2D load(std::ifstream&); 53 | 54 | // Operators interface ---------------------------------------------------// 55 | friend std::ostream& operator<<(std::ostream&, const Data2D&); 56 | friend void imshow(const Data2D&, difference_type delay = -1); 57 | friend bool isequal(const Data2D&, const Data2D&); 58 | friend void save(const Data2D&, std::ofstream&); 59 | 60 | // Access ----------------------------------------------------------------// 61 | // Note that Data2D is immutable, so all access is const. 62 | difference_type data_height() const { return A_ptr->height(); } 63 | difference_type data_width() const { return A_ptr->width(); } 64 | const Array2D& get_array() const { return *A_ptr; } 65 | const ROI2D& get_roi() const { return roi; } 66 | difference_type get_scalefactor() const { return scalefactor; } 67 | 68 | // Interpolator ----------------------------------------------------------// 69 | nlinfo_interpolator get_nlinfo_interpolator(difference_type, INTERP) const; 70 | 71 | // Utility ---------------------------------------------------------------// 72 | std::string size_string() const { return std::to_string(A_ptr->size()); } 73 | std::string size_2D_string() const { return "(" + std::to_string(data_height()) + "," + std::to_string(data_width()) + ")"; } 74 | 75 | private: 76 | // Utility ---------------------------------------------------------------// 77 | void chk_scalefactor() const; 78 | void chk_data_roi_same_size() const; 79 | 80 | difference_type scalefactor; 81 | ROI2D roi; // immutable - ROI2D already has pointer semantics 82 | std::shared_ptr> A_ptr; // immutable 83 | }; 84 | 85 | namespace details { 86 | class Data2D_nlinfo_interpolator final { 87 | public: 88 | typedef Data2D::difference_type difference_type; 89 | typedef Data2D::coords coords; 90 | 91 | friend Data2D; 92 | 93 | // Rule of 5 and destructor --------------------------------------// 94 | Data2D_nlinfo_interpolator() noexcept : scalefactor(), nlinfo_top(), nlinfo_left() { } 95 | Data2D_nlinfo_interpolator(const Data2D_nlinfo_interpolator&) = default; 96 | Data2D_nlinfo_interpolator(Data2D_nlinfo_interpolator&&) = default; 97 | Data2D_nlinfo_interpolator& operator=(const Data2D_nlinfo_interpolator&) = default; 98 | Data2D_nlinfo_interpolator& operator=(Data2D_nlinfo_interpolator&&) = default; 99 | ~Data2D_nlinfo_interpolator() noexcept = default; 100 | 101 | // Additional Constructors ---------------------------------------// 102 | Data2D_nlinfo_interpolator(const Data2D&, difference_type, INTERP); 103 | 104 | double operator()(double p1, double p2) const { return sub_data_interp(p1_unscaled(p1), p2_unscaled(p2)); } 105 | const Array2D& first_order(double p1, double p2) const { 106 | const auto &fo_unscaled = sub_data_interp.first_order(p1_unscaled(p1), p2_unscaled(p2)); 107 | first_order_buf(0) = fo_unscaled(0); // value - do not modify 108 | first_order_buf(1) = fo_unscaled(1) / scalefactor; // p1 gradient - must scale 109 | first_order_buf(2) = fo_unscaled(2) / scalefactor; // p2_gradient - must scale 110 | 111 | return first_order_buf; 112 | } 113 | 114 | private: 115 | // Access methods ------------------------------------------------// 116 | double p1_unscaled(double p1) const { return (p1 / scalefactor) - nlinfo_top + border; } 117 | double p2_unscaled(double p2) const { return (p2 / scalefactor) - nlinfo_left + border; } 118 | 119 | std::shared_ptr> sub_data_ptr; // immutable 120 | Array2D::interpolator sub_data_interp; // must have copy of interpolator 121 | mutable Array2D first_order_buf; // have copy 122 | difference_type scalefactor; 123 | difference_type nlinfo_top; 124 | difference_type nlinfo_left; 125 | difference_type border = 20; 126 | }; 127 | } 128 | 129 | } 130 | 131 | #endif /* DATA2D_H */ -------------------------------------------------------------------------------- /include/Disp2D.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: Disp2D.h 3 | * Author: justin 4 | * 5 | * Created on May 12, 2015, 12:36 PM 6 | */ 7 | 8 | #ifndef DISP2D_H 9 | #define DISP2D_H 10 | 11 | #include "Array2D.h" 12 | #include "Image2D.h" 13 | #include "ROI2D.h" 14 | #include "Data2D.h" 15 | 16 | namespace ncorr { 17 | 18 | namespace details { 19 | class Disp2D_nlinfo_interpolator; 20 | } 21 | 22 | class Disp2D final { 23 | // ---------------------------------------------------------------------------// 24 | // Disp2D is a class for 2D displacements. -----------------------------------// 25 | // ---------------------------------------------------------------------------// 26 | public: 27 | typedef Data2D::difference_type difference_type; 28 | typedef Data2D::coords coords; 29 | typedef details::Disp2D_nlinfo_interpolator nlinfo_interpolator; 30 | 31 | // Rule of 5 and destructor ----------------------------------------------// 32 | Disp2D() noexcept = default; 33 | Disp2D(const Disp2D&) = default; 34 | Disp2D(Disp2D&&) noexcept = default; 35 | Disp2D& operator=(const Disp2D&) = default; 36 | Disp2D& operator=(Disp2D&&) = default; 37 | ~Disp2D() noexcept = default; 38 | 39 | // Additional constructors -----------------------------------------------// 40 | Disp2D(Array2D v, Array2D u, const ROI2D &roi, difference_type scalefactor) : // r-value 41 | v(std::move(v), roi, scalefactor), u(std::move(u), roi, scalefactor) { } 42 | 43 | // Static factory methods ------------------------------------------------// 44 | static Disp2D load(std::ifstream&); 45 | 46 | // Operators interface ---------------------------------------------------// 47 | friend std::ostream& operator<<(std::ostream&, const Disp2D&); 48 | friend void imshow(const Disp2D&, difference_type delay = -1); 49 | friend bool isequal(const Disp2D&, const Disp2D&); 50 | friend void save(const Disp2D&, std::ofstream&); 51 | 52 | // Access ----------------------------------------------------------------// 53 | // Note that Disp2D is immutable, so all access is const. 54 | difference_type data_height() const { return v.data_height(); } 55 | difference_type data_width() const { return v.data_width(); } 56 | const Data2D& get_v() const { return v; } 57 | const Data2D& get_u() const { return u; } 58 | const ROI2D& get_roi() const { return v.get_roi(); } 59 | difference_type get_scalefactor() const { return v.get_scalefactor(); } 60 | 61 | // Interpolator ----------------------------------------------------------// 62 | nlinfo_interpolator get_nlinfo_interpolator(difference_type, INTERP) const; 63 | 64 | // Utility ---------------------------------------------------------------// 65 | std::string size_string() const { return v.size_string(); } 66 | std::string size_2D_string() const { return v.size_2D_string(); } 67 | 68 | private: 69 | Data2D v; // Immutable - Data2D has pointer semantics 70 | Data2D u; // Immutable - Data2D has pointer semantics 71 | }; 72 | 73 | namespace details { 74 | class Disp2D_nlinfo_interpolator final { 75 | public: 76 | typedef Disp2D::difference_type difference_type; 77 | typedef Disp2D::coords coords; 78 | 79 | friend Disp2D; 80 | 81 | // Rule of 5 and destructor --------------------------------------// 82 | Disp2D_nlinfo_interpolator() noexcept = default; 83 | Disp2D_nlinfo_interpolator(const Disp2D_nlinfo_interpolator&) = default; 84 | Disp2D_nlinfo_interpolator(Disp2D_nlinfo_interpolator&&) = default; 85 | Disp2D_nlinfo_interpolator& operator=(const Disp2D_nlinfo_interpolator&) = default; 86 | Disp2D_nlinfo_interpolator& operator=(Disp2D_nlinfo_interpolator&&) = default; 87 | ~Disp2D_nlinfo_interpolator() noexcept = default; 88 | 89 | // Additional Constructors ---------------------------------------// 90 | Disp2D_nlinfo_interpolator(const Disp2D &disp, difference_type region_idx, INTERP interp_type) : 91 | v_interp(disp.get_v().get_nlinfo_interpolator(region_idx,interp_type)), u_interp(disp.get_u().get_nlinfo_interpolator(region_idx,interp_type)) { } 92 | 93 | // Access methods ------------------------------------------------// 94 | std::pair operator()(double p1, double p2) const { return { v_interp(p1,p2), u_interp(p1,p2) }; } 95 | std::pair&,const Array2D&> first_order(double p1, double p2) const { return { v_interp.first_order(p1,p2), u_interp.first_order(p1,p2) }; } 96 | 97 | private: 98 | Data2D::nlinfo_interpolator v_interp; // must have copy of interpolator 99 | Data2D::nlinfo_interpolator u_interp; // must have copy of interpolator 100 | }; 101 | } 102 | 103 | } 104 | 105 | #endif /* DISP2D_H */ 106 | 107 | -------------------------------------------------------------------------------- /include/Image2D.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: Image2D.h 3 | * Author: justin 4 | * 5 | * Created on January 28, 2015, 3:10 PM 6 | */ 7 | 8 | #ifndef IMAGE2D_H 9 | #define IMAGE2D_H 10 | 11 | #include "Array2D.h" 12 | 13 | namespace ncorr { 14 | 15 | class Image2D final { 16 | public: 17 | typedef std::ptrdiff_t difference_type; 18 | 19 | // Rule of 5 and destructor ------------------------------------------// 20 | Image2D() = default; 21 | Image2D(const Image2D&) = default; 22 | Image2D(Image2D&&) noexcept = default; 23 | Image2D& operator=(const Image2D&) = default; 24 | Image2D& operator=(Image2D&&) = default; 25 | ~Image2D() noexcept = default; 26 | 27 | // Additional Constructors -------------------------------------------// 28 | // Allow implicit conversion 29 | Image2D(std::string filename) : filename_ptr(std::make_shared(std::move(filename))) { } // by-value 30 | 31 | // Static factory methods --------------------------------------------// 32 | static Image2D load(std::ifstream&); 33 | 34 | // Interface functions -----------------------------------------------// 35 | friend std::ostream& operator<<(std::ostream&, const Image2D&); 36 | friend void imshow(const Image2D&, difference_type delay = -1); 37 | friend bool isequal(const Image2D&, const Image2D&); 38 | friend void save(const Image2D&, std::ofstream&); 39 | 40 | // Access ------------------------------------------------------------// 41 | std::string get_filename() const { return *filename_ptr; } 42 | Array2D get_gs() const; // Returns image as double precision grayscale array with values from 0 - 1. 43 | 44 | private: 45 | std::shared_ptr filename_ptr; // immutable 46 | }; 47 | 48 | } 49 | 50 | #endif /* IMAGE2D_H */ -------------------------------------------------------------------------------- /include/ROI2D.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: ROI2D.h 3 | * Author: justin 4 | * 5 | * Created on February 11, 2015, 12:02 AM 6 | */ 7 | 8 | #ifndef ROI2D_H 9 | #define ROI2D_H 10 | 11 | #include "Array2D.h" 12 | 13 | namespace ncorr { 14 | 15 | namespace details { 16 | // incrementor is like an iterator, except it does not have the dereference operation 17 | class nlinfo_incrementor; 18 | class ROI2D_incrementor; 19 | 20 | class ROI2D_contig_subregion_generator; // Forms a contiguous subregion around an (x,y) input 21 | } 22 | 23 | enum class SUBREGION { CIRCLE, SQUARE }; 24 | 25 | class ROI2D final { 26 | public: 27 | typedef std::ptrdiff_t difference_type; 28 | typedef std::pair coords; 29 | typedef details::ROI2D_incrementor incrementor; 30 | typedef details::ROI2D_contig_subregion_generator contig_subregion_generator; 31 | 32 | struct region_boundary; 33 | struct region_nlinfo; 34 | struct region; 35 | 36 | friend incrementor; 37 | friend contig_subregion_generator; 38 | 39 | // Rule of 5 and destructor ----------------------------------------------// 40 | ROI2D() : points() { } 41 | ROI2D(const ROI2D&) = default; 42 | ROI2D(ROI2D&&) noexcept = default; 43 | ROI2D& operator=(const ROI2D&) = default; 44 | ROI2D& operator=(ROI2D&&) = default; 45 | ~ROI2D() noexcept = default; 46 | 47 | // Additional Constructors -----------------------------------------------// 48 | explicit ROI2D(Array2D, difference_type = 0); // by-value 49 | ROI2D(region_nlinfo, difference_type, difference_type); // by-value 50 | ROI2D(region_boundary, difference_type, difference_type); // by-value 51 | ROI2D(std::vector, difference_type, difference_type); // by-value 52 | ROI2D(std::vector, difference_type, difference_type); // by-value 53 | 54 | // Static factory methods ------------------------------------------------// 55 | static ROI2D simple_circle(difference_type); 56 | static ROI2D simple_square(difference_type); 57 | static ROI2D load(std::ifstream&); 58 | 59 | // Operators interface ---------------------------------------------------// 60 | friend std::ostream& operator<<(std::ostream&, const ROI2D&); 61 | friend void imshow(const ROI2D &roi, difference_type delay = -1) { imshow(*roi.mask_ptr, delay); } 62 | friend bool isequal(const ROI2D&, const ROI2D&); 63 | friend void save(const ROI2D&, std::ofstream&); 64 | 65 | // Access ----------------------------------------------------------------// 66 | // Note that ROI2D is immutable, so all access should be const. 67 | difference_type height() const { return mask_ptr->height(); } 68 | difference_type width() const { return mask_ptr->width(); } 69 | bool empty() const { return points == 0; } 70 | bool in_bounds(difference_type p) const { return mask_ptr->in_bounds(p); } 71 | bool in_bounds(difference_type p1, difference_type p2) const { return mask_ptr->in_bounds(p1,p2); } 72 | 73 | bool operator()(difference_type p) const { return (*mask_ptr)(p); } 74 | bool operator()(difference_type p1, difference_type p2) const { return (*mask_ptr)(p1,p2); } 75 | // Perhaps add forwarding of Array2D style region indexing later. 76 | 77 | const Array2D& get_mask() const { return *mask_ptr; }; 78 | const region_nlinfo& get_nlinfo(difference_type) const; 79 | const region_boundary& get_boundary(difference_type) const; 80 | difference_type get_points() const { return points; } 81 | difference_type size_regions() const { return regions_ptr->size(); } 82 | std::pair get_region_idx(difference_type, difference_type) const; 83 | 84 | // Arithmetic operations -------------------------------------------------// 85 | ROI2D reduce(difference_type) const; 86 | ROI2D form_union(const Array2D&) const; 87 | 88 | // incrementor -----------------------------------------------------------// 89 | incrementor begin_inc() const; 90 | incrementor end_inc() const; 91 | 92 | // contig_subregion_generator --------------------------------------------// 93 | contig_subregion_generator get_contig_subregion_generator(SUBREGION, difference_type) const; 94 | 95 | // Utility ---------------------------------------------------------------// 96 | std::string size_string() const { return mask_ptr->size_string(); } 97 | std::string size_2D_string() const { return mask_ptr->size_2D_string(); } 98 | 99 | private: 100 | // Utility functions -----------------------------------------------------// 101 | void set_points(); 102 | void draw_mask(); 103 | 104 | // Checks ----------------------------------------------------------------// 105 | void chk_region_idx_in_bounds(difference_type, const std::string&) const; 106 | 107 | std::shared_ptr> mask_ptr; // immutable 108 | std::shared_ptr> regions_ptr; // immutable 109 | difference_type points; 110 | }; 111 | 112 | struct ROI2D::region_nlinfo final { 113 | typedef details::nlinfo_incrementor incrementor; 114 | 115 | // nlinfo maintains 4-way contiguity as invariant 116 | 117 | // Rule of 5 and destructor ----------------------------------------------// 118 | region_nlinfo() : top(), bottom(), left(), right(), left_nl(), right_nl(), points() { } 119 | region_nlinfo(const region_nlinfo&) = default; 120 | region_nlinfo(region_nlinfo&&) = default; 121 | region_nlinfo& operator=(const region_nlinfo&) = default; 122 | region_nlinfo& operator=(region_nlinfo&&) = default; 123 | ~region_nlinfo() noexcept = default; 124 | 125 | friend ROI2D; 126 | 127 | // Additional constructor ------------------------------------------------// 128 | region_nlinfo(difference_type top, 129 | difference_type bottom, 130 | difference_type left, 131 | difference_type right, 132 | difference_type nl_left, 133 | difference_type nl_right, 134 | difference_type h_nl, 135 | difference_type w_nl, 136 | difference_type points) : top(top), 137 | bottom(bottom), 138 | left(left), 139 | right(right), 140 | left_nl(nl_left), 141 | right_nl(nl_right), 142 | nodelist(h_nl,w_nl), 143 | noderange(1,w_nl), 144 | points(points) { } 145 | 146 | // Static factory methods ------------------------------------------------// 147 | static region_nlinfo load(std::ifstream&); 148 | 149 | // Operators interface ---------------------------------------------------// 150 | friend std::ostream& operator<<(std::ostream&, const ROI2D::region_nlinfo&); 151 | friend bool isequal(const region_nlinfo&, const region_nlinfo&); 152 | friend void save(const region_nlinfo&, std::ofstream&); 153 | 154 | // Access methods --------------------------------------------------------// 155 | // The following 6 operations are inlined and defined in this header file. 156 | difference_type first_pos_idx() const; 157 | difference_type first_pos_p1() const; 158 | difference_type first_pos_p2() const; 159 | difference_type last_pos_idx() const; 160 | difference_type last_pos_p1() const; 161 | difference_type last_pos_p2() const; 162 | 163 | bool empty() const { return points == 0; } 164 | 165 | // Arithmetic methods ----------------------------------------------------// 166 | bool in_nlinfo(difference_type, difference_type) const; 167 | region_nlinfo& shift(difference_type, difference_type); // in-place 168 | 169 | // Incrementor -----------------------------------------------------------// 170 | incrementor begin_inc() const; 171 | incrementor end_inc() const; 172 | 173 | // Static factory methods ------------------------------------------------// 174 | static std::pair,bool> form_nlinfos(const Array2D&, ROI2D::difference_type = 0); 175 | 176 | difference_type top; 177 | difference_type bottom; 178 | difference_type left; 179 | difference_type right; 180 | difference_type left_nl; // left p2 position of beginning of nodelist - can differ from "left" 181 | difference_type right_nl; // right p2 position of end of nodelist - can differ from "right" 182 | Array2D nodelist; 183 | Array2D noderange; 184 | difference_type points; 185 | 186 | private: 187 | // Arithmetic methods ----------------------------------------------------// 188 | // Note that operations on nlinfo must maintain 4-way contiguity invariant 189 | region_nlinfo reduce(difference_type, Array2D&) const; 190 | region_nlinfo form_union(const Array2D&, Array2D&) const; 191 | region_nlinfo largest_contig_nlinfo(Array2D&) const; 192 | ROI2D::region_boundary to_boundary(Array2D&) const; 193 | 194 | // Utility ---------------------------------------------------------------// 195 | void chk_nonempty_op(const std::string&) const; 196 | }; 197 | 198 | struct ROI2D::region_boundary final { 199 | // Rule of 5 and destructor ----------------------------------------------// 200 | region_boundary() = default; 201 | region_boundary(const region_boundary&) = default; 202 | region_boundary(region_boundary&&) = default; 203 | region_boundary& operator=(const region_boundary&) = default; 204 | region_boundary& operator=(region_boundary&&) = default; 205 | ~region_boundary() noexcept = default; 206 | 207 | friend ROI2D; 208 | 209 | // Additional Constructors -----------------------------------------------// 210 | region_boundary(const Array2D &add, const std::vector> &sub) : add(add), sub(sub) { } 211 | region_boundary(const Array2D &add, std::vector> &&sub) : add(add), sub(std::move(sub)) { } 212 | region_boundary(Array2D &&add, const std::vector> &sub) : add(std::move(add)), sub(sub) { } 213 | region_boundary(Array2D &&add, std::vector> &&sub) : add(std::move(add)), sub(std::move(sub)) { } 214 | 215 | // Static factory methods ------------------------------------------------// 216 | static region_boundary load(std::ifstream&); 217 | 218 | // Operators interface ---------------------------------------------------// 219 | friend std::ostream& operator<<(std::ostream&, const ROI2D::region_boundary&); 220 | friend bool isequal(const region_boundary&, const region_boundary&); 221 | friend void save(const region_boundary&, std::ofstream&); 222 | 223 | Array2D add; 224 | std::vector> sub; 225 | 226 | private: 227 | // Arithmetic methods ----------------------------------------------------// 228 | ROI2D::region_nlinfo to_nlinfo(Array2D&) const; 229 | }; 230 | 231 | struct ROI2D::region final { 232 | // Rule of 5 and destructor ----------------------------------------------// 233 | region() = default; 234 | region(const region&) = default; 235 | region(region&&) = default; 236 | region& operator=(const region&) = default; 237 | region& operator=(region&&) = default; 238 | ~region() noexcept = default; 239 | 240 | // Additional Constructors -----------------------------------------------// 241 | region(const region_nlinfo &nlinfo, const region_boundary &boundary) : nlinfo(nlinfo), boundary(boundary) { } 242 | region(const region_nlinfo &nlinfo, region_boundary &&boundary) : nlinfo(nlinfo), boundary(std::move(boundary)) { } 243 | region(region_nlinfo &&nlinfo, const region_boundary &boundary) : nlinfo(std::move(nlinfo)), boundary(boundary) { } 244 | region(region_nlinfo &&nlinfo, region_boundary &&boundary) : nlinfo(std::move(nlinfo)), boundary(std::move(boundary)) { } 245 | 246 | // Static factory methods ------------------------------------------------// 247 | static region load(std::ifstream&); 248 | 249 | // Operators interface ---------------------------------------------------// 250 | friend std::ostream& operator<<(std::ostream&, const ROI2D::region&); 251 | friend bool isequal(const region ®1, const region ®2) { return isequal(reg1.nlinfo,reg2.nlinfo) && isequal(reg1.boundary,reg2.boundary); } 252 | friend void save(const region&, std::ofstream&); 253 | 254 | region_nlinfo nlinfo; 255 | region_boundary boundary; 256 | }; 257 | 258 | namespace details { 259 | // Incrementors ----------------------------------------------------------// 260 | class nlinfo_incrementor final { 261 | public: 262 | typedef ROI2D::difference_type difference_type; 263 | typedef ROI2D::coords coords; 264 | 265 | friend ROI2D::region_nlinfo; 266 | 267 | // Rule of 5 and destructor --------------------------------------// 268 | nlinfo_incrementor() noexcept : nlinfo_ptr(nullptr), nl_idx(), np_idx(), p1() { } 269 | nlinfo_incrementor(const nlinfo_incrementor&) = default; 270 | nlinfo_incrementor(nlinfo_incrementor&&) = default; 271 | nlinfo_incrementor& operator=(const nlinfo_incrementor&) = default; 272 | nlinfo_incrementor& operator=(nlinfo_incrementor&&) = default; 273 | ~nlinfo_incrementor() noexcept = default; 274 | 275 | // Additional Constructors ---------------------------------------// 276 | nlinfo_incrementor(const ROI2D::region_nlinfo &nlinfo, difference_type nl_idx, difference_type np_idx, difference_type p1) : 277 | nlinfo_ptr(&nlinfo), nl_idx(nl_idx), np_idx(np_idx), p1(p1) { } 278 | 279 | // Access methods ------------------------------------------------// 280 | coords pos_2D() const { return { p1, nlinfo_ptr->left_nl + nl_idx }; } 281 | 282 | // Arithmetic methods --------------------------------------------// 283 | nlinfo_incrementor& operator++(); 284 | // Maybe add decrement operator later 285 | 286 | bool operator==(const nlinfo_incrementor &inc) const { 287 | return inc.nlinfo_ptr == nlinfo_ptr && inc.nl_idx == nl_idx && inc.np_idx == np_idx && inc.p1 == p1; 288 | } 289 | bool operator!=(const nlinfo_incrementor &inc) const { return !(inc == *this); } 290 | 291 | private: 292 | const ROI2D::region_nlinfo *nlinfo_ptr; 293 | difference_type nl_idx; 294 | difference_type np_idx; 295 | difference_type p1; 296 | }; 297 | 298 | class ROI2D_incrementor final { 299 | public: 300 | typedef ROI2D::difference_type difference_type; 301 | typedef ROI2D::coords coords; 302 | 303 | friend ROI2D; 304 | 305 | // Rule of 5 and destructor --------------------------------------// 306 | ROI2D_incrementor() noexcept : region_idx() { } 307 | ROI2D_incrementor(const ROI2D_incrementor&) = default; 308 | ROI2D_incrementor(ROI2D_incrementor&&) = default; 309 | ROI2D_incrementor& operator=(const ROI2D_incrementor&) = default; 310 | ROI2D_incrementor& operator=(ROI2D_incrementor&&) = default; 311 | ~ROI2D_incrementor() noexcept = default; 312 | 313 | // Additional Constructors ---------------------------------------// 314 | ROI2D_incrementor(const ROI2D&, difference_type, const ROI2D::region_nlinfo::incrementor&); 315 | 316 | // Access methods ------------------------------------------------// 317 | coords pos_2D() const { return nlinfo_inc.pos_2D(); } 318 | 319 | // Arithmetic methods --------------------------------------------// 320 | ROI2D_incrementor& operator++(); 321 | // Maybe add decrement operator later 322 | 323 | bool operator==(const ROI2D_incrementor &inc) const { 324 | return inc.region_idx == region_idx && inc.nlinfo_inc == nlinfo_inc; 325 | } 326 | bool operator!=(const ROI2D_incrementor &inc) const { return !(*this == inc); } 327 | 328 | private: 329 | ROI2D roi; // ROI2D has pointer semantics 330 | difference_type region_idx; 331 | ROI2D::region_nlinfo::incrementor nlinfo_inc; 332 | }; 333 | 334 | // contig_subregion_generator --------------------------------------------// 335 | class ROI2D_contig_subregion_generator final { 336 | public: 337 | typedef ROI2D::difference_type difference_type; 338 | 339 | friend ROI2D; 340 | 341 | // Rule of 5 and destructor --------------------------------------// 342 | ROI2D_contig_subregion_generator() noexcept : r() { } 343 | ROI2D_contig_subregion_generator(const ROI2D_contig_subregion_generator&) = default; 344 | ROI2D_contig_subregion_generator(ROI2D_contig_subregion_generator&&) = default; 345 | ROI2D_contig_subregion_generator& operator=(const ROI2D_contig_subregion_generator&) = default; 346 | ROI2D_contig_subregion_generator& operator=(ROI2D_contig_subregion_generator&&) = default; 347 | ~ROI2D_contig_subregion_generator() noexcept = default; 348 | 349 | // Additional Constructors ---------------------------------------// 350 | ROI2D_contig_subregion_generator(const ROI2D&, SUBREGION, difference_type); 351 | 352 | // Access methods ------------------------------------------------// 353 | difference_type get_r() const { return r; } 354 | const ROI2D::region_nlinfo& get_subregion_nlinfo() const { return nlinfo_subregion; } 355 | 356 | // Arithmetic methods --------------------------------------------// 357 | const ROI2D::region_nlinfo& operator()(difference_type, difference_type) const; 358 | 359 | private: 360 | ROI2D roi; // ROI2D has pointer semantics 361 | difference_type r; 362 | mutable Array2D active_nodepairs; 363 | mutable ROI2D::region_nlinfo nlinfo_simple; 364 | mutable ROI2D::region_nlinfo nlinfo_subregion; 365 | }; 366 | } 367 | 368 | inline ROI2D::difference_type ROI2D::region_nlinfo::first_pos_idx() const { 369 | #ifndef NDEBUG 370 | chk_nonempty_op("first_pos_idx()"); 371 | #endif 372 | 373 | return left - left_nl; 374 | } 375 | 376 | inline ROI2D::difference_type ROI2D::region_nlinfo::first_pos_p1() const { 377 | #ifndef NDEBUG 378 | chk_nonempty_op("first_pos_p1()"); 379 | #endif 380 | 381 | return nodelist(0,first_pos_idx()); 382 | } 383 | 384 | inline ROI2D::difference_type ROI2D::region_nlinfo::first_pos_p2() const { 385 | #ifndef NDEBUG 386 | chk_nonempty_op("first_pos_p2()"); 387 | #endif 388 | 389 | return left; 390 | } 391 | 392 | inline ROI2D::difference_type ROI2D::region_nlinfo::last_pos_idx() const { 393 | #ifndef NDEBUG 394 | chk_nonempty_op("last_pos_idx()"); 395 | #endif 396 | 397 | return nodelist.width() - (right_nl - right) - 1; 398 | } 399 | 400 | inline ROI2D::difference_type ROI2D::region_nlinfo::last_pos_p1() const { 401 | #ifndef NDEBUG 402 | chk_nonempty_op("last_pos_p1()"); 403 | #endif 404 | 405 | return nodelist(noderange(last_pos_idx()) - 1, last_pos_idx()); 406 | } 407 | 408 | inline ROI2D::difference_type ROI2D::region_nlinfo::last_pos_p2() const { 409 | #ifndef NDEBUG 410 | chk_nonempty_op("last_pos_p2()"); 411 | #endif 412 | 413 | return right; 414 | } 415 | 416 | // Interface functions -------------------------------------------------------// 417 | template 418 | T_container& fill(T_container &A, const ROI2D::region_nlinfo &nlinfo, const T &val) { 419 | typedef ROI2D::difference_type difference_type; 420 | 421 | for (difference_type nl_idx = 0; nl_idx < nlinfo.nodelist.width(); ++nl_idx) { 422 | difference_type p2 = nl_idx + nlinfo.left_nl; 423 | for (difference_type np_idx = 0; np_idx < nlinfo.noderange(nl_idx); np_idx += 2) { 424 | difference_type np_top = nlinfo.nodelist(np_idx, nl_idx); 425 | difference_type np_bottom = nlinfo.nodelist(np_idx + 1, nl_idx); 426 | for (difference_type p1 = np_top; p1 <= np_bottom; ++p1) { 427 | A(p1,p2) = val; 428 | } 429 | } 430 | } 431 | 432 | return A; 433 | } 434 | 435 | template 436 | T_container& fill(T_container &A, const Array2D&boundary, const T &val) { 437 | typedef ROI2D::difference_type difference_type; 438 | 439 | if (boundary.width() != 2) { 440 | throw std::invalid_argument("Input boundary has size: " + boundary.size_2D_string() + ". Boundary must have a width of 2."); 441 | } 442 | 443 | // Flood fill algorithm from : http://alienryderflex.com/polygon_fill/ 444 | // boundary must have width of 2, where the first column is p1 coordinates 445 | // and the second column is p2 coordinates. 446 | 447 | if (boundary.empty()) { 448 | // If boundary is empty just return 449 | return A; 450 | } 451 | 452 | // node_buf is used to hold nodes that are calculated to paint the polygon 453 | // along the sweep line. It is initialized to hold at least the max number 454 | // of nodes. 455 | Array2D node_buf(std::max(boundary.height(),difference_type(2)),1); 456 | // Get bounds from max and min of p2 coordinates in boundary. Also note that 457 | // boundary is non-empty at this point so min and max exist. 458 | difference_type left = std::ceil(std::max(*std::min_element(boundary.get_pointer() + boundary.height() - 1, boundary.get_pointer() + 2 * boundary.height()), 0.0)); 459 | difference_type right = std::floor(std::min(*std::max_element(boundary.get_pointer() + boundary.height() - 1, boundary.get_pointer() + 2 * boundary.height()), A.width()-1.0)); 460 | for (difference_type p2_sweep = left; p2_sweep <= right; ++p2_sweep) { 461 | difference_type buf_length = 0; // Keeps track of # of nodes 462 | // This will cycle over each line segment (point0->point1) of the polygon 463 | // and test for intersections with a vertical sweep line on integer pixel 464 | // locations. 465 | for (difference_type point1_idx = 0, point0_idx = boundary.height() - 1; point1_idx < boundary.height(); point0_idx = point1_idx++) { 466 | double point0_p1 = boundary(point0_idx,0), point0_p2 = boundary(point0_idx,1); 467 | double point1_p1 = boundary(point1_idx,0), point1_p2 = boundary(point1_idx,1); 468 | if ((p2_sweep < point1_p2 && p2_sweep >= point0_p2) || 469 | (p2_sweep < point0_p2 && p2_sweep >= point1_p2)) { 470 | node_buf(buf_length++) = point1_p1 + (p2_sweep-point1_p2)/(point0_p2-point1_p2) * (point0_p1-point1_p1); 471 | } 472 | } 473 | 474 | // Sort nodes 475 | std::sort(node_buf.get_pointer(), node_buf.get_pointer() + buf_length); 476 | 477 | // Paint nodes 478 | for (difference_type idx = 0; idx < buf_length; idx += 2) { 479 | difference_type np_top = std::ceil(node_buf(idx)); 480 | difference_type np_bottom = std::floor(node_buf(idx+1)); 481 | if (np_top >= A.height()) { 482 | break; // top node is lower than bottom of the mask 483 | } 484 | if (np_bottom >= 0) { // bottom node is lower than the top of the mask 485 | if (np_top < 0) { np_top = 0; } 486 | if (np_bottom >= A.height()) { np_bottom = A.height() - 1; } 487 | 488 | // At this point the nodes are within the mask bounds, so paint 489 | // them. Note that it's possible for np_top > np_bottom in the 490 | // case that the boundary is between two pixels. In this case, 491 | // the loop does nothing, so its safe. 492 | for (difference_type p1 = np_top; p1 <= np_bottom; p1++) { 493 | A(p1,p2_sweep) = val; 494 | } 495 | } 496 | } 497 | } 498 | 499 | return A; 500 | } 501 | 502 | template 503 | std::pair max(T_container &A, const ROI2D::region_nlinfo &nlinfo) { 504 | typedef ROI2D::difference_type difference_type; 505 | 506 | if (nlinfo.empty()) { 507 | throw std::invalid_argument("Attempted to find the max value in Array using an empty nlinfo."); 508 | } 509 | 510 | difference_type p1_max = nlinfo.first_pos_p1(); 511 | difference_type p2_max = nlinfo.first_pos_p2(); 512 | difference_type val_max = A(nlinfo.first_pos_idx()); 513 | for (difference_type nl_idx = 0; nl_idx < nlinfo.nodelist.width(); ++nl_idx) { 514 | difference_type p2 = nl_idx + nlinfo.left_nl; 515 | for (difference_type np_idx = 0; np_idx < nlinfo.noderange(nl_idx); np_idx += 2) { 516 | difference_type np_top = nlinfo.nodelist(np_idx, nl_idx); 517 | difference_type np_bottom = nlinfo.nodelist(np_idx + 1, nl_idx); 518 | for (difference_type p1 = np_top; p1 <= np_bottom; ++p1) { 519 | if (A(p1,p2) > val_max) { 520 | p1_max = p1; 521 | p2_max = p2; 522 | val_max = A(p1,p2); 523 | } 524 | } 525 | } 526 | } 527 | 528 | return { val_max, { p1_max,p2_max } }; 529 | } 530 | 531 | template 532 | std::pair min(T_container &A, const ROI2D::region_nlinfo &nlinfo) { 533 | typedef ROI2D::difference_type difference_type; 534 | 535 | if (nlinfo.empty()) { 536 | throw std::invalid_argument("Attempted to find the min value in Array using an empty nlinfo."); 537 | } 538 | 539 | difference_type p1_min = nlinfo.first_pos_p1(); 540 | difference_type p2_min = nlinfo.first_pos_p2(); 541 | difference_type val_min = A(nlinfo.first_pos_idx()); 542 | for (difference_type nl_idx = 0; nl_idx < nlinfo.nodelist.width(); ++nl_idx) { 543 | difference_type p2 = nl_idx + nlinfo.left_nl; 544 | for (difference_type np_idx = 0; np_idx < nlinfo.noderange(nl_idx); np_idx += 2) { 545 | difference_type np_top = nlinfo.nodelist(np_idx, nl_idx); 546 | difference_type np_bottom = nlinfo.nodelist(np_idx + 1, nl_idx); 547 | for (difference_type p1 = np_top; p1 <= np_bottom; ++p1) { 548 | if (A(p1,p2) < val_min) { 549 | p1_min = p1; 550 | p2_min = p2; 551 | val_min = A(p1,p2); 552 | } 553 | } 554 | } 555 | } 556 | 557 | return { val_min, { p1_min,p2_min } }; 558 | } 559 | 560 | } 561 | 562 | #endif /* ROI2D_H */ -------------------------------------------------------------------------------- /include/Strain2D.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: Strain2D.h 3 | * Author: justin 4 | * 5 | * Created on June 7, 2015, 10:14 PM 6 | */ 7 | 8 | #ifndef STRAIN2D_H 9 | #define STRAIN2D_H 10 | 11 | #include "Array2D.h" 12 | #include "Image2D.h" 13 | #include "ROI2D.h" 14 | #include "Data2D.h" 15 | 16 | namespace ncorr { 17 | 18 | namespace details { 19 | class Strain2D_nlinfo_interpolator; 20 | } 21 | 22 | class Strain2D final { 23 | // ---------------------------------------------------------------------------// 24 | // Strain2D is a class for 2D strains. ---------------------------------------// 25 | // ---------------------------------------------------------------------------// 26 | public: 27 | typedef Data2D::difference_type difference_type; 28 | typedef Data2D::coords coords; 29 | typedef details::Strain2D_nlinfo_interpolator nlinfo_interpolator; 30 | 31 | // Rule of 5 and destructor ----------------------------------------------// 32 | Strain2D() noexcept = default; 33 | Strain2D(const Strain2D&) = default; 34 | Strain2D(Strain2D&&) noexcept = default; 35 | Strain2D& operator=(const Strain2D&) = default; 36 | Strain2D& operator=(Strain2D&&) = default; 37 | ~Strain2D() noexcept = default; 38 | 39 | // Additional constructors -----------------------------------------------// 40 | Strain2D(Array2D eyy, Array2D exy, Array2D exx, const ROI2D &roi, difference_type scalefactor) : // r-value 41 | eyy(std::move(eyy), roi, scalefactor), exy(std::move(exy), roi, scalefactor), exx(std::move(exx), roi, scalefactor) { } 42 | 43 | // Static factory methods ------------------------------------------------// 44 | static Strain2D load(std::ifstream&); 45 | 46 | // Operators interface ---------------------------------------------------// 47 | friend std::ostream& operator<<(std::ostream&, const Strain2D&); 48 | friend void imshow(const Strain2D&, difference_type delay = -1); 49 | friend bool isequal(const Strain2D&, const Strain2D&); 50 | friend void save(const Strain2D&, std::ofstream&); 51 | 52 | // Access ----------------------------------------------------------------// 53 | // Note that Strain2D is immutable, so all access is const. 54 | difference_type data_height() const { return eyy.data_height(); } 55 | difference_type data_width() const { return eyy.data_width(); } 56 | const Data2D& get_eyy() const { return eyy; } 57 | const Data2D& get_exy() const { return exy; } 58 | const Data2D& get_exx() const { return exx; } 59 | const ROI2D& get_roi() const { return eyy.get_roi(); } 60 | difference_type get_scalefactor() const { return eyy.get_scalefactor(); } 61 | 62 | // Interpolator ----------------------------------------------------------// 63 | nlinfo_interpolator get_nlinfo_interpolator(difference_type, INTERP) const; 64 | 65 | // Utility ---------------------------------------------------------------// 66 | std::string size_string() const { return eyy.size_string(); } 67 | std::string size_2D_string() const { return eyy.size_2D_string(); } 68 | 69 | private: 70 | Data2D eyy; // Immutable - Data2D has pointer semantics 71 | Data2D exy; // Immutable - Data2D has pointer semantics 72 | Data2D exx; // Immutable - Data2D has pointer semantics 73 | }; 74 | 75 | namespace details { 76 | class Strain2D_nlinfo_interpolator final { 77 | public: 78 | typedef Strain2D::difference_type difference_type; 79 | typedef Strain2D::coords coords; 80 | 81 | friend Strain2D; 82 | 83 | // Rule of 5 and destructor --------------------------------------// 84 | Strain2D_nlinfo_interpolator() noexcept = default; 85 | Strain2D_nlinfo_interpolator(const Strain2D_nlinfo_interpolator&) = default; 86 | Strain2D_nlinfo_interpolator(Strain2D_nlinfo_interpolator&&) = default; 87 | Strain2D_nlinfo_interpolator& operator=(const Strain2D_nlinfo_interpolator&) = default; 88 | Strain2D_nlinfo_interpolator& operator=(Strain2D_nlinfo_interpolator&&) = default; 89 | ~Strain2D_nlinfo_interpolator() noexcept = default; 90 | 91 | // Additional Constructors ---------------------------------------// 92 | Strain2D_nlinfo_interpolator(const Strain2D &strain, difference_type region_idx, INTERP interp_type) : 93 | eyy_interp(strain.get_eyy().get_nlinfo_interpolator(region_idx,interp_type)), 94 | exy_interp(strain.get_exy().get_nlinfo_interpolator(region_idx,interp_type)), 95 | exx_interp(strain.get_exx().get_nlinfo_interpolator(region_idx,interp_type)) { } 96 | 97 | // Access methods ------------------------------------------------// 98 | std::tuple operator()(double p1, double p2) const { return std::make_tuple(eyy_interp(p1,p2), exy_interp(p1,p2), exx_interp(p1,p2)); } 99 | std::tuple&,const Array2D&,const Array2D&> first_order(double p1, double p2) const { return std::make_tuple(eyy_interp.first_order(p1,p2), exy_interp.first_order(p1,p2), exx_interp.first_order(p1,p2)); } 100 | 101 | private: 102 | Data2D::nlinfo_interpolator eyy_interp; // must have copy of interpolator 103 | Data2D::nlinfo_interpolator exy_interp; // must have copy of interpolator 104 | Data2D::nlinfo_interpolator exx_interp; // must have copy of interpolator 105 | }; 106 | } 107 | 108 | } 109 | 110 | #endif /* STRAIN2D_H */ 111 | 112 | -------------------------------------------------------------------------------- /include/ncorr.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: ncorr.h 3 | * Author: justin 4 | * 5 | * Created on May 12, 2015, 1:33 AM 6 | */ 7 | 8 | #ifndef NCORR_H 9 | #define NCORR_H 10 | 11 | #include "Array2D.h" 12 | #include "Image2D.h" 13 | #include "ROI2D.h" 14 | #include "Data2D.h" 15 | #include "Disp2D.h" 16 | #include "Strain2D.h" 17 | 18 | namespace ncorr { 19 | 20 | namespace details { 21 | // Nonlinear optimization ------------------------------------------------// 22 | class nloptimizer_base { 23 | public: 24 | typedef std::ptrdiff_t difference_type; 25 | typedef std::pair coords; 26 | 27 | // Rule of 5 and destructor --------------------------------------// 28 | nloptimizer_base() noexcept = default; 29 | nloptimizer_base(const nloptimizer_base&) = default; 30 | nloptimizer_base(nloptimizer_base&&) = default; 31 | nloptimizer_base& operator=(const nloptimizer_base&) = default; 32 | nloptimizer_base& operator=(nloptimizer_base&&) = default; 33 | virtual ~nloptimizer_base() noexcept = default; 34 | 35 | // Additional Constructors ---------------------------------------// 36 | nloptimizer_base(difference_type order, difference_type num_params) : 37 | grad_buf(order,1), hess_buf(order,order), params(num_params,1) { } 38 | 39 | // Arithmetic operations -----------------------------------------// 40 | std::pair&, bool> global(const Array2D&) const; 41 | std::pair&, bool> operator()(const Array2D&) const; 42 | 43 | protected: 44 | // Arithmetic operations -----------------------------------------// 45 | virtual bool initial_guess() const = 0; 46 | virtual bool iterative_search() const = 0; 47 | virtual bool newton() const = 0; 48 | 49 | // Utility -------------------------------------------------------// 50 | void chk_input_params_size(const Array2D&) const; 51 | 52 | mutable Array2D grad_buf; 53 | mutable Array2D hess_buf; 54 | mutable Array2D params; 55 | double cutoff_norm = 1e-6; 56 | difference_type cutoff_iterations = 50; 57 | }; 58 | 59 | class disp_nloptimizer final : public nloptimizer_base { 60 | public: 61 | typedef nloptimizer_base::difference_type difference_type; 62 | typedef nloptimizer_base::coords coords; 63 | 64 | // Rule of 5 and destructor --------------------------------------// 65 | disp_nloptimizer() noexcept : region_idx() { } 66 | disp_nloptimizer(const disp_nloptimizer&) = default; 67 | disp_nloptimizer(disp_nloptimizer&&) = default; 68 | disp_nloptimizer& operator=(const disp_nloptimizer&) = default; 69 | disp_nloptimizer& operator=(disp_nloptimizer&&) = default; 70 | ~disp_nloptimizer() noexcept = default; 71 | 72 | // Additional Constructors ---------------------------------------// 73 | // Note: params = {p1_new, p2_new, p1_old, p2_old, v_old, u_old, dv_dp1_old, dv_dp2_old, du_dp1_old, du_dp2_old, dist, grad_norm} 74 | disp_nloptimizer(const Disp2D &disp, difference_type region_idx, INTERP interp_type) : 75 | nloptimizer_base(2, 12), disp(disp), region_idx(region_idx), disp_interp(disp.get_nlinfo_interpolator(region_idx, interp_type)) { } 76 | 77 | private: 78 | // Arithmetic operations -----------------------------------------// 79 | bool initial_guess() const override; 80 | bool iterative_search() const override; 81 | bool newton() const override; 82 | 83 | Disp2D disp; // immutable - Disp2D has pointer semantics 84 | difference_type region_idx; 85 | Disp2D::nlinfo_interpolator disp_interp; // Have copy 86 | }; 87 | 88 | class subregion_nloptimizer final : public nloptimizer_base { 89 | public: 90 | typedef nloptimizer_base::difference_type difference_type; 91 | typedef nloptimizer_base::coords coords; 92 | 93 | // Rule of 5 and destructor --------------------------------------// 94 | subregion_nloptimizer() noexcept : scalefactor(), ref_template_avg(), ref_template_ssd_inv() { } 95 | subregion_nloptimizer(const subregion_nloptimizer&) = default; 96 | subregion_nloptimizer(subregion_nloptimizer&&) = default; 97 | subregion_nloptimizer& operator=(const subregion_nloptimizer&) = default; 98 | subregion_nloptimizer& operator=(subregion_nloptimizer&&) = default; 99 | ~subregion_nloptimizer() noexcept = default; 100 | 101 | // Additional Constructors ---------------------------------------// 102 | // Note: params = {p1, p2, v, u, dv_dp1, dv_dp2, du_dp1, du_dp2, corr_coef, diff_norm} 103 | subregion_nloptimizer(const Array2D&, const Array2D&, const ROI2D&, difference_type, INTERP, SUBREGION, difference_type); 104 | 105 | private: 106 | // Arithmetic operations -----------------------------------------// 107 | bool initial_guess() const override; 108 | bool iterative_search() const override; 109 | bool newton() const override; 110 | 111 | std::shared_ptr> A_ref_ptr; // Allows R-value arrays; immutable 112 | std::shared_ptr> A_cur_ptr; // Allows R-value arrays; immutable 113 | difference_type scalefactor; 114 | Array2D::interpolator A_cur_interp; // Have copy 115 | ROI2D::contig_subregion_generator subregion_gen; // Have copy 116 | // Buffers for NCC: 117 | std::shared_ptr> A_cur_cumsum_p1_ptr; // immutable 118 | std::shared_ptr> A_cur_pow_cumsum_p1_ptr; // immutable 119 | mutable Array2D A_ref_template; // Have copy 120 | // Buffers for inverse compositional gauss newton method: 121 | std::shared_ptr> A_dref_dp1_ptr; // immutable 122 | std::shared_ptr> A_dref_dp2_ptr; // immutable 123 | mutable double ref_template_avg; 124 | mutable double ref_template_ssd_inv; 125 | // Steepest descent images: 126 | mutable Array2D A_dref_dv; // Have copy 127 | mutable Array2D A_dref_du; // Have copy 128 | mutable Array2D A_dref_dv_dp1; // Have copy 129 | mutable Array2D A_dref_dv_dp2; // Have copy 130 | mutable Array2D A_dref_du_dp1; // Have copy 131 | mutable Array2D A_dref_du_dp2; // Have copy 132 | // Linsolver for hessian 133 | mutable Array2D::linsolver hess_linsolver; // Have copy 134 | // Cur template buffer 135 | mutable Array2D A_cur_template; // Have copy 136 | }; 137 | } 138 | 139 | // Interface functions -------------------------------------------------------// 140 | ROI2D update(const ROI2D&, const Disp2D&, INTERP); 141 | 142 | Data2D update(const Data2D&, const Disp2D&, INTERP); 143 | 144 | Disp2D add(const std::vector&, INTERP); 145 | 146 | // DIC_analysis --------------------------------------------------------------// 147 | std::pair RGDIC(const Array2D&, const Array2D&, const ROI2D&, ROI2D::difference_type, INTERP, SUBREGION, ROI2D::difference_type, ROI2D::difference_type, double, bool); 148 | 149 | enum class DIC_analysis_config { NO_UPDATE, KEEP_MOST_POINTS, REMOVE_BAD_POINTS }; 150 | 151 | struct DIC_analysis_input final { 152 | typedef ROI2D::difference_type difference_type; 153 | 154 | // Rule of 5 and destructor ----------------------------------------------// 155 | DIC_analysis_input() : scalefactor(), interp_type(), subregion_type(), r(), num_threads(), cutoff_corrcoef(), update_corrcoef(), prctile_corrcoef(), debug() { } 156 | DIC_analysis_input(const DIC_analysis_input&) = default; 157 | DIC_analysis_input(DIC_analysis_input&&) = default; 158 | DIC_analysis_input& operator=(const DIC_analysis_input&) = default; 159 | DIC_analysis_input& operator=(DIC_analysis_input&&) = default; 160 | ~DIC_analysis_input() noexcept = default; 161 | 162 | // Additional constructors -----------------------------------------------// 163 | DIC_analysis_input(const std::vector &imgs, 164 | const ROI2D &roi, 165 | difference_type scalefactor, 166 | INTERP interp_type, 167 | SUBREGION subregion_type, 168 | difference_type r, 169 | difference_type num_threads, 170 | double cutoff_corrcoef, 171 | double update_corrcoef, 172 | double prctile_corrcoef, 173 | bool debug) : imgs(imgs), 174 | roi(roi), 175 | scalefactor(scalefactor), 176 | interp_type(interp_type), 177 | subregion_type(subregion_type), 178 | r(r), 179 | num_threads(num_threads), 180 | cutoff_corrcoef(cutoff_corrcoef), 181 | update_corrcoef(update_corrcoef), 182 | prctile_corrcoef(prctile_corrcoef), 183 | debug(debug) { } 184 | 185 | DIC_analysis_input(const std::vector&, const ROI2D&, difference_type, INTERP, SUBREGION, difference_type, difference_type, DIC_analysis_config, bool); 186 | 187 | // Static factory methods ------------------------------------------------// 188 | static DIC_analysis_input load(std::ifstream&); 189 | static DIC_analysis_input load(const std::string&); 190 | 191 | // Interface functions ---------------------------------------------------// 192 | friend void save(const DIC_analysis_input&, std::ofstream&); 193 | friend void save(const DIC_analysis_input&, const std::string&); 194 | 195 | std::vector imgs; 196 | ROI2D roi; 197 | difference_type scalefactor; 198 | INTERP interp_type; 199 | SUBREGION subregion_type; 200 | difference_type r; 201 | difference_type num_threads; 202 | double cutoff_corrcoef; 203 | double update_corrcoef; 204 | double prctile_corrcoef; 205 | bool debug; 206 | }; 207 | 208 | enum class PERSPECTIVE { EULERIAN, LAGRANGIAN }; 209 | 210 | struct DIC_analysis_output final { 211 | typedef ROI2D::difference_type difference_type; 212 | 213 | // Rule of 5 and destructor ----------------------------------------------// 214 | DIC_analysis_output() : units_per_pixel() { } 215 | DIC_analysis_output(const DIC_analysis_output&) = default; 216 | DIC_analysis_output(DIC_analysis_output&&) = default; 217 | DIC_analysis_output& operator=(const DIC_analysis_output&) = default; 218 | DIC_analysis_output& operator=(DIC_analysis_output&&) = default; 219 | ~DIC_analysis_output() noexcept = default; 220 | 221 | // Additional constructors -----------------------------------------------// 222 | DIC_analysis_output(const std::vector &disps, PERSPECTIVE perspective_type, const std::string &units, double units_per_pixel) : 223 | disps(disps), perspective_type(perspective_type), units(units), units_per_pixel(units_per_pixel) { } 224 | 225 | // Static factory methods ------------------------------------------------// 226 | static DIC_analysis_output load(std::ifstream&); 227 | static DIC_analysis_output load(const std::string&); 228 | 229 | // Interface functions ---------------------------------------------------// 230 | friend void save(const DIC_analysis_output&, std::ofstream&); 231 | friend void save(const DIC_analysis_output&, const std::string&); 232 | 233 | std::vector disps; 234 | PERSPECTIVE perspective_type; 235 | std::string units; 236 | double units_per_pixel; 237 | }; 238 | 239 | DIC_analysis_output DIC_analysis(const DIC_analysis_input&); 240 | 241 | // Conversion between Lagrangian and Eulerian displacements ------------------// 242 | DIC_analysis_output change_perspective(const DIC_analysis_output&, INTERP); 243 | 244 | // set units -----------------------------------------------------------------// 245 | DIC_analysis_output set_units(const DIC_analysis_output&, const std::string&, double); 246 | 247 | // strain_analysis -----------------------------------------------------------// 248 | Strain2D LS_strain(const Disp2D&, PERSPECTIVE, double, SUBREGION, ROI2D::difference_type); 249 | 250 | struct strain_analysis_input final { 251 | typedef ROI2D::difference_type difference_type; 252 | 253 | // Rule of 5 and destructor ----------------------------------------------// 254 | strain_analysis_input() : r() { } 255 | strain_analysis_input(const strain_analysis_input&) = default; 256 | strain_analysis_input(strain_analysis_input&&) = default; 257 | strain_analysis_input& operator=(const strain_analysis_input&) = default; 258 | strain_analysis_input& operator=(strain_analysis_input&&) = default; 259 | ~strain_analysis_input() noexcept = default; 260 | 261 | // Additional constructors -----------------------------------------------// 262 | strain_analysis_input(const DIC_analysis_input &DIC_input, 263 | const DIC_analysis_output &DIC_output, 264 | SUBREGION subregion_type, 265 | difference_type r) : DIC_input(DIC_input), 266 | DIC_output(DIC_output), 267 | subregion_type(subregion_type), 268 | r(r){ } 269 | 270 | // Static factory methods ------------------------------------------------// 271 | static strain_analysis_input load(std::ifstream&); 272 | static strain_analysis_input load(const std::string&); 273 | 274 | // Interface functions ---------------------------------------------------// 275 | friend void save(const strain_analysis_input&, std::ofstream&); 276 | friend void save(const strain_analysis_input&, const std::string&); 277 | 278 | DIC_analysis_input DIC_input; 279 | DIC_analysis_output DIC_output; 280 | SUBREGION subregion_type; 281 | difference_type r; 282 | }; 283 | 284 | struct strain_analysis_output final { 285 | typedef ROI2D::difference_type difference_type; 286 | 287 | // Rule of 5 and destructor ----------------------------------------------// 288 | strain_analysis_output() = default; 289 | strain_analysis_output(const strain_analysis_output&) = default; 290 | strain_analysis_output(strain_analysis_output&&) = default; 291 | strain_analysis_output& operator=(const strain_analysis_output&) = default; 292 | strain_analysis_output& operator=(strain_analysis_output&&) = default; 293 | ~strain_analysis_output() noexcept = default; 294 | 295 | // Additional constructors -----------------------------------------------// 296 | strain_analysis_output(const std::vector &strains) : strains(strains) { } 297 | 298 | // Static factory methods ------------------------------------------------// 299 | static strain_analysis_output load(std::ifstream&); 300 | static strain_analysis_output load(const std::string&); 301 | 302 | // Interface functions ---------------------------------------------------// 303 | friend void save(const strain_analysis_output&, std::ofstream&); 304 | friend void save(const strain_analysis_output&, const std::string&); 305 | 306 | std::vector strains; 307 | }; 308 | 309 | strain_analysis_output strain_analysis(const strain_analysis_input&); 310 | 311 | // Interface functions for viewing and saving ncorr related data -------------// 312 | void imshow_ncorr_data_over_img(const Image2D&, const Data2D&, ROI2D::difference_type = -1); 313 | 314 | void save_ncorr_data_over_img(const std::string&, 315 | const Image2D&, 316 | const Data2D&, 317 | double, 318 | double, 319 | double, 320 | bool, 321 | bool, 322 | bool, 323 | const std::string&, 324 | double, 325 | double, 326 | double, 327 | ROI2D::difference_type, 328 | int); 329 | 330 | void save_ncorr_data_over_img_video(const std::string&, 331 | const std::vector&, 332 | const std::vector&, 333 | double, 334 | double, 335 | double, 336 | double, 337 | bool, 338 | bool, 339 | bool, 340 | const std::string&, 341 | double, 342 | double, 343 | double, 344 | ROI2D::difference_type, 345 | int, 346 | double, 347 | int); 348 | 349 | enum class DISP { U, V }; 350 | void save_DIC_video(const std::string&, 351 | const DIC_analysis_input&, 352 | const DIC_analysis_output&, 353 | DISP, 354 | double, 355 | double, 356 | double = std::numeric_limits::quiet_NaN(), 357 | double = std::numeric_limits::quiet_NaN(), 358 | bool = true, 359 | bool = true, 360 | bool = true, 361 | double = -1.0, 362 | double = 1.0, 363 | ROI2D::difference_type = 11, 364 | int = cv::COLORMAP_JET, 365 | double = 2.0, 366 | int = cv::VideoWriter::fourcc('M','J','P','G')); 367 | 368 | enum class STRAIN { EYY, EXY, EXX }; 369 | void save_strain_video(const std::string&, 370 | const strain_analysis_input&, 371 | const strain_analysis_output&, 372 | STRAIN, 373 | double, 374 | double, 375 | double = std::numeric_limits::quiet_NaN(), 376 | double = std::numeric_limits::quiet_NaN(), 377 | bool = true, 378 | bool = true, 379 | bool = true, 380 | double = -1.0, 381 | double = 1.0, 382 | ROI2D::difference_type = 11, 383 | int = cv::COLORMAP_JET, 384 | double = 2.0, 385 | int = cv::VideoWriter::fourcc('M','J','P','G')); 386 | // ---------------------------------------------------------------------------// 387 | 388 | } 389 | 390 | #endif /* NCORR_H */ -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Justin Blaber 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in 12 | the documentation and/or other materials provided with the distribution 13 | * Neither the name of the Georgia Institute of Technology nor the names 14 | of its contributors may be used to endorse or promote products derived 15 | from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /src/Array2D.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * File: Array2D.h 3 | * Author: justin 4 | * 5 | * Created on December 30, 2014, 4:55 PM 6 | */ 7 | 8 | #include "Array2D.h" 9 | 10 | namespace ncorr { 11 | 12 | namespace details { 13 | // The only thread-safe routine in FFTW is fftw_execute(), so use this mutex 14 | // for all other routines. 15 | std::mutex fftw_mutex; 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /src/Data2D.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * File: Data2D.cpp 3 | * Author: justin 4 | * 5 | * Created on February 28, 2015, 8:57 PM 6 | */ 7 | 8 | #include "Data2D.h" 9 | 10 | namespace ncorr { 11 | 12 | // Additional constructors ---------------------------------------------------// 13 | Data2D::Data2D(Array2D A, const ROI2D &roi, difference_type scalefactor) : 14 | scalefactor(scalefactor), roi(roi), A_ptr(std::make_shared>(std::move(A))) { 15 | chk_scalefactor(); 16 | chk_data_roi_same_size(); 17 | } 18 | 19 | Data2D::Data2D(Array2D A, const ROI2D &roi) : 20 | scalefactor(1), roi(roi), A_ptr(std::make_shared>(std::move(A))) { 21 | chk_data_roi_same_size(); 22 | } 23 | 24 | Data2D::Data2D(Array2D A, difference_type scalefactor) : 25 | scalefactor(scalefactor), roi(Array2D(A.height(),A.width(),true)), A_ptr(std::make_shared>(std::move(A))) { 26 | chk_scalefactor(); 27 | } 28 | 29 | Data2D::Data2D(Array2D A) : 30 | scalefactor(1), roi(Array2D(A.height(),A.width(),true)), A_ptr(std::make_shared>(std::move(A))) { } 31 | 32 | // Static factory methods ----------------------------------------------------// 33 | Data2D Data2D::load(std::ifstream &is) { 34 | // Form empty Data2D then fill in values in accordance to how they are saved 35 | Data2D data; 36 | 37 | // Load scalefactor 38 | is.read(reinterpret_cast(&data.scalefactor), std::streamsize(sizeof(difference_type))); 39 | 40 | // Load roi 41 | data.roi = ROI2D::load(is); 42 | 43 | // Load A 44 | data.A_ptr = std::make_shared>(Array2D::load(is)); 45 | 46 | return data; 47 | } 48 | 49 | // Operators interface -------------------------------------------------------// 50 | std::ostream& operator<<(std::ostream &os, const Data2D &data) { 51 | os << "Array: " << '\n' << *data.A_ptr; 52 | os << '\n' << "ROI: " << '\n' << data.roi; 53 | os << '\n' << "scale factor: " << data.scalefactor; 54 | 55 | return os; 56 | } 57 | 58 | void imshow(const Data2D &data, Data2D::difference_type delay) { 59 | // Form buffer; set all values outside of ROI to slightly below minimum 60 | // value of data, then show it, this guarantees the area outside the ROI is 61 | // black. 62 | Array2D A_buf = data.get_array(); 63 | Array2D A_data = A_buf(data.get_roi().get_mask()); 64 | if (!A_data.empty()) { 65 | double A_min = min(A_data); 66 | // Subtract small amount so min value doesn't show as black. 67 | A_buf(~data.get_roi().get_mask()) = std::nextafter(A_min, A_min-1); 68 | } 69 | imshow(A_buf,delay); 70 | } 71 | 72 | bool isequal(const Data2D &data1, const Data2D &data2) { 73 | return isequal(data1.get_array(), data2.get_array()) && 74 | isequal(data1.get_roi(), data2.get_roi()) && 75 | data1.scalefactor == data2.scalefactor; 76 | } 77 | 78 | void save(const Data2D &data, std::ofstream &os) { 79 | typedef Data2D::difference_type difference_type; 80 | 81 | // Save scalefactor -> roi -> A 82 | os.write(reinterpret_cast(&data.scalefactor), std::streamsize(sizeof(difference_type))); 83 | save(data.roi, os); 84 | save(*data.A_ptr, os); 85 | } 86 | 87 | // Interpolator --------------------------------------------------------------// 88 | Data2D::nlinfo_interpolator Data2D::get_nlinfo_interpolator(difference_type region_idx, INTERP interp_type) const { 89 | return details::Data2D_nlinfo_interpolator(*this, region_idx, interp_type); 90 | } 91 | 92 | // Utility -------------------------------------------------------------------// 93 | void Data2D::chk_scalefactor() const { 94 | if (scalefactor < 1) { 95 | throw std::invalid_argument("Attempted to form Data2D with scalefactor of: " + std::to_string(scalefactor) + 96 | ". scalefactor must be an integer of value 1 or greater."); 97 | } 98 | } 99 | 100 | void Data2D::chk_data_roi_same_size() const { 101 | if (A_ptr->height() != roi.height() || A_ptr->width() != roi.width()) { 102 | throw std::invalid_argument("Attempted to form Data2D with Array2D of size: " + A_ptr->size_2D_string() + 103 | " and ROI2D of size: " + roi.size_2D_string() + ". Sizes must be the same."); 104 | } 105 | } 106 | 107 | namespace details { 108 | struct sparse_tree_element final { 109 | typedef ROI2D::difference_type difference_type; 110 | 111 | // Constructor -------------------------------------------------------// 112 | sparse_tree_element(difference_type p1, difference_type p2, difference_type val) : p1(p1), p2(p2), val(val) { } 113 | 114 | // Arithmetic methods ------------------------------------------------// 115 | bool operator<(const sparse_tree_element &b) const { 116 | if (p2 == b.p2) { 117 | return p1 < b.p1; // Sort by p1 118 | } else { 119 | return p2 < b.p2; // Sort by p2 first 120 | } 121 | }; 122 | 123 | difference_type p1; 124 | difference_type p2; 125 | mutable difference_type val; 126 | }; 127 | 128 | void add_to_sparse_tree(std::set &sparse_tree, const sparse_tree_element &ste) { 129 | auto ste_it = sparse_tree.find(ste); 130 | if (ste_it == sparse_tree.end()) { 131 | // Val isnt in sparse_tree, so just insert it 132 | sparse_tree.insert(ste); 133 | } else { 134 | // Val is already in sparse_tree, so just modify the value 135 | ste_it->val += ste.val; 136 | } 137 | } 138 | 139 | Array2D& inpaint_nlinfo(Array2D &A, const ROI2D::region_nlinfo &nlinfo) { 140 | typedef ROI2D::difference_type difference_type; 141 | 142 | if (nlinfo.empty()) { 143 | // No digital inpainting if nlinfo is empty 144 | return A; 145 | } 146 | 147 | // Form mask ---------------------------------------------------------// 148 | Array2D mask_nlinfo(A.height(),A.width()); 149 | fill(mask_nlinfo, nlinfo, true); 150 | 151 | // Precompute inverse of nlinfo pixels' linear indices ---------------// 152 | Array2D A_inv_loc(A.height(),A.width(),-1); // -1 indicates pixel in nlinfo. 153 | difference_type inv_loc_counter = 0; 154 | for (difference_type p2 = 0; p2 < mask_nlinfo.width(); ++p2) { 155 | for (difference_type p1 = 0; p1 < mask_nlinfo.height(); ++p1) { 156 | if (!mask_nlinfo(p1,p2)) { 157 | A_inv_loc(p1,p2) = inv_loc_counter++; 158 | } 159 | } 160 | } 161 | 162 | // Cycle over Array and form constraints -----------------------------// 163 | // Analyze points outside nlinfo AND boundary points 164 | std::set sparse_tree; 165 | std::vector b; 166 | for (difference_type p2 = 0; p2 < A_inv_loc.width(); ++p2) { 167 | for (difference_type p1 = 0; p1 < A_inv_loc.height(); ++p1) { 168 | // Corners don't have constraints 169 | if ((p1 > 0 && p1 < A_inv_loc.height()-1) || (p2 > 0 && p2 < A_inv_loc.width()-1)) { 170 | // Sides have special constraints 171 | if (p1 == 0 || p1 == A_inv_loc.height()-1) { 172 | // Top or bottom 173 | if (A_inv_loc(p1,p2-1) != -1 || A_inv_loc(p1,p2) != -1 || A_inv_loc(p1,p2+1) != -1) { 174 | // Point of interest - add a constraint 175 | b.push_back(0); 176 | if (A_inv_loc(p1,p2-1) == -1) { b[b.size()-1] -= A(p1,p2-1); } else { add_to_sparse_tree(sparse_tree, {difference_type(b.size())-1, A_inv_loc(p1,p2-1), 1}); } 177 | if (A_inv_loc(p1,p2) == -1) { b[b.size()-1] += 2*A(p1,p2); } else { add_to_sparse_tree(sparse_tree, {difference_type(b.size())-1, A_inv_loc(p1,p2), -2}); } 178 | if (A_inv_loc(p1,p2+1) == -1) { b[b.size()-1] -= A(p1,p2+1); } else { add_to_sparse_tree(sparse_tree, {difference_type(b.size())-1, A_inv_loc(p1,p2+1), 1}); } 179 | } 180 | } else if (p2 == 0 || p2 == A_inv_loc.width()-1) { 181 | // Left or right 182 | if (A_inv_loc(p1-1,p2) != -1 || A_inv_loc(p1,p2) != -1 || A_inv_loc(p1+1,p2) != -1) { 183 | // Point of interest - add a constraint 184 | b.push_back(0); 185 | if (A_inv_loc(p1-1,p2) == -1) { b[b.size()-1] -= A(p1-1,p2); } else { add_to_sparse_tree(sparse_tree, {difference_type(b.size())-1, A_inv_loc(p1-1,p2), 1}); } 186 | if (A_inv_loc(p1,p2) == -1) { b[b.size()-1] += 2*A(p1,p2); } else { add_to_sparse_tree(sparse_tree, {difference_type(b.size())-1, A_inv_loc(p1,p2), -2}); } 187 | if (A_inv_loc(p1+1,p2) == -1) { b[b.size()-1] -= A(p1+1,p2); } else { add_to_sparse_tree(sparse_tree, {difference_type(b.size())-1, A_inv_loc(p1+1,p2), 1}); } 188 | } 189 | } else { 190 | // Center 191 | if (A_inv_loc(p1-1,p2) != -1 || A_inv_loc(p1+1,p2) != -1 || A_inv_loc(p1,p2) != -1 || A_inv_loc(p1,p2-1) != -1 || A_inv_loc(p1,p2+1) != -1) { 192 | // Point of interest - add a constraint 193 | b.push_back(0); 194 | if (A_inv_loc(p1-1,p2) == -1) { b[b.size()-1] -= A(p1-1,p2); } else { add_to_sparse_tree(sparse_tree, {difference_type(b.size())-1, A_inv_loc(p1-1,p2), 1}); } 195 | if (A_inv_loc(p1+1,p2) == -1) { b[b.size()-1] -= A(p1+1,p2); } else { add_to_sparse_tree(sparse_tree, {difference_type(b.size())-1, A_inv_loc(p1+1,p2), 1}); } 196 | if (A_inv_loc(p1,p2) == -1) { b[b.size()-1] += 4*A(p1,p2); } else { add_to_sparse_tree(sparse_tree, {difference_type(b.size())-1, A_inv_loc(p1,p2), -4}); } 197 | if (A_inv_loc(p1,p2-1) == -1) { b[b.size()-1] -= A(p1,p2-1); } else { add_to_sparse_tree(sparse_tree, {difference_type(b.size())-1, A_inv_loc(p1,p2-1), 1}); } 198 | if (A_inv_loc(p1,p2+1) == -1) { b[b.size()-1] -= A(p1,p2+1); } else { add_to_sparse_tree(sparse_tree, {difference_type(b.size())-1, A_inv_loc(p1,p2+1), 1}); } 199 | } 200 | } 201 | } 202 | } 203 | } 204 | 205 | // Use sparse QR solver ----------------------------------------------// 206 | // Note that later on maybe encapsulate free() into smart pointers. Make 207 | // sure that cholmod_common is finished() last. 208 | // Start CHOLMOD 209 | cholmod_common c; 210 | cholmod_l_start(&c); 211 | 212 | // Allocate and fill A 213 | cholmod_sparse *A_sparse = cholmod_l_allocate_sparse(b.size(), // height 214 | inv_loc_counter, // width 215 | sparse_tree.size(), // # of elements 216 | true, // row indices are sorted 217 | true, // it is packed 218 | 0, // symmetry (0 = unsymmetric) 219 | CHOLMOD_REAL, 220 | &c); 221 | ((SuiteSparse_long*)A_sparse->p)[inv_loc_counter] = sparse_tree.size(); // Set last element before tree gets erased 222 | for (difference_type counter = 0, p2 = 0; p2 < inv_loc_counter; ++p2) { 223 | ((SuiteSparse_long*)A_sparse->p)[p2] = counter; 224 | while (!sparse_tree.empty() && p2 == sparse_tree.begin()->p2) { 225 | // Get first element 226 | auto it_ste = sparse_tree.begin(); 227 | ((SuiteSparse_long*)A_sparse->i)[counter] = it_ste->p1; 228 | ((double*)A_sparse->x)[counter] = it_ste->val; 229 | sparse_tree.erase(it_ste); // delete element 230 | ++counter; 231 | } 232 | } 233 | 234 | // Allocate and fill b 235 | cholmod_dense *b_dense = cholmod_l_allocate_dense(b.size(), // Height 236 | 1, // Width 237 | b.size(), // Leading dimension 238 | CHOLMOD_REAL, 239 | &c); 240 | for (difference_type p = 0; p < difference_type(b.size()); ++p) { 241 | ((double*)b_dense->x)[p] = b[p]; 242 | } 243 | 244 | // Solve and then fill results into inverse region of A 245 | // Note that documentation was hard to understand so I've done no error 246 | // checking here (i.e. for out of memory) so maybe fix this later. 247 | cholmod_dense *x_dense = SuiteSparseQR(A_sparse, b_dense, &c); 248 | difference_type counter = 0; 249 | for (difference_type p2 = 0; p2 < mask_nlinfo.width(); ++p2) { 250 | for (difference_type p1 = 0; p1 < mask_nlinfo.height(); ++p1) { 251 | if (!mask_nlinfo(p1,p2)) { 252 | A(p1,p2) = ((double*)x_dense->x)[counter++]; 253 | } 254 | } 255 | } 256 | 257 | // Free memory 258 | cholmod_l_free_dense(&x_dense, &c); 259 | cholmod_l_free_dense(&b_dense, &c); 260 | cholmod_l_free_sparse(&A_sparse, &c); 261 | 262 | // Finish cholmod 263 | cholmod_l_finish(&c); 264 | 265 | return A; 266 | } 267 | 268 | Data2D_nlinfo_interpolator::Data2D_nlinfo_interpolator(const Data2D &data, difference_type region_idx, INTERP interp_type) : 269 | sub_data_ptr(std::make_shared>()), 270 | sub_data_interp(sub_data_ptr->get_interpolator(interp_type)), 271 | first_order_buf(3,1), 272 | scalefactor(data.get_scalefactor()), 273 | nlinfo_top(), 274 | nlinfo_left() { 275 | // Perform digital inpainting of data contained in nlinfo first - this is 276 | // mainly for biquintic interpolation or any other form that requires an 277 | // image patch for interpolation near edge points. 278 | 279 | // Get copy of nlinfo - copy needed because it is shift()'ed which alters it in-place. 280 | auto nlinfo = data.get_roi().get_nlinfo(region_idx); 281 | 282 | if (nlinfo.empty()) { 283 | // If this nlinfo is empty, there is nothing to interpolate. Note 284 | // this will still return a functional interpolator that will return 285 | // NaNs. 286 | return; 287 | } 288 | 289 | // Store bounds - this allows conversion of interpolation coords 290 | // (p1, p2) to local coordinates 291 | this->nlinfo_top = nlinfo.top; 292 | this->nlinfo_left = nlinfo.left; 293 | 294 | // Get sub_data array; use pinpainting so interpolation works nicely for 295 | // boundary points. 296 | sub_data_ptr = std::make_shared>(nlinfo.bottom - nlinfo.top + 2*border + 1, 297 | nlinfo.right - nlinfo.left + 2*border + 1); 298 | (*sub_data_ptr)({border, sub_data_ptr->height() - border - 1},{border, sub_data_ptr->width() - border - 1}) = data.get_array()({nlinfo.top,nlinfo.bottom},{nlinfo.left,nlinfo.right}); 299 | inpaint_nlinfo(*sub_data_ptr, nlinfo.shift(border - nlinfo.top, border - nlinfo.left)); 300 | 301 | // Get interpolator 302 | sub_data_interp = sub_data_ptr->get_interpolator(interp_type); 303 | } 304 | } 305 | 306 | } -------------------------------------------------------------------------------- /src/Disp2D.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * File: Disp2D.cpp 3 | * Author: justin 4 | * 5 | * Created on May 12, 2015, 12:36 PM 6 | */ 7 | 8 | #include "Disp2D.h" 9 | 10 | namespace ncorr { 11 | 12 | // Static factory methods ----------------------------------------------------// 13 | Disp2D Disp2D::load(std::ifstream &is) { 14 | // Form empty Disp2D then fill in values in accordance to how they are saved 15 | Disp2D disp; 16 | 17 | // Load v 18 | disp.v = Data2D::load(is); 19 | 20 | // Load u 21 | disp.u = Data2D::load(is); 22 | 23 | return disp; 24 | } 25 | 26 | // Operators interface -------------------------------------------------------// 27 | std::ostream& operator<<(std::ostream &os, const Disp2D &disp) { 28 | os << "V data: " << '\n' << disp.get_v(); 29 | os << '\n' << "U data: " << '\n' << disp.get_u(); 30 | 31 | return os; 32 | } 33 | 34 | void imshow(const Disp2D &disp, Disp2D::difference_type delay) { 35 | // Just show each separately for now. If you combine into one buffer, you 36 | // must scale each as their ranges might be different. 37 | imshow(disp.v, delay); 38 | imshow(disp.u, delay); 39 | } 40 | 41 | bool isequal(const Disp2D &disp1, const Disp2D &disp2) { 42 | return isequal(disp1.v, disp2.v) && isequal(disp1.u, disp2.u); 43 | } 44 | 45 | void save(const Disp2D &disp, std::ofstream &os) { 46 | // Save v -> u 47 | save(disp.v, os); 48 | save(disp.u, os); 49 | } 50 | 51 | // Interpolator --------------------------------------------------------------// 52 | Disp2D::nlinfo_interpolator Disp2D::get_nlinfo_interpolator(difference_type region_idx, INTERP interp_type) const { 53 | return details::Disp2D_nlinfo_interpolator(*this, region_idx, interp_type); 54 | } 55 | 56 | } 57 | 58 | -------------------------------------------------------------------------------- /src/Image2D.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * File: Image2D.h 3 | * Author: justin 4 | * 5 | * Created on January 28, 2015, 3:10 PM 6 | */ 7 | 8 | #include "Image2D.h" 9 | 10 | namespace ncorr { 11 | 12 | // Static factory methods ----------------------------------------------------// 13 | Image2D Image2D::load(std::ifstream &is) { 14 | // Form empty Image2D then fill in values in accordance to how they are saved 15 | Image2D img; 16 | 17 | // Load length 18 | difference_type length = 0; 19 | is.read(reinterpret_cast(&length), std::streamsize(sizeof(difference_type))); 20 | 21 | // Allocate new string 22 | img.filename_ptr = std::make_shared(length,' '); 23 | 24 | // Read data 25 | is.read(const_cast(img.filename_ptr->c_str()), std::streamsize(length)); 26 | 27 | return img; 28 | } 29 | 30 | // Operations interface ------------------------------------------------------// 31 | std::ostream& operator<<(std::ostream &os, const Image2D &img) { 32 | os << *img.filename_ptr; 33 | 34 | return os; 35 | } 36 | 37 | void imshow(const Image2D &img, Image2D::difference_type delay) { 38 | imshow(img.get_gs(),delay); 39 | } 40 | 41 | bool isequal(const Image2D &img1, const Image2D &img2) { 42 | return *img1.filename_ptr == *img2.filename_ptr; 43 | } 44 | 45 | void save(const Image2D &img, std::ofstream &os) { 46 | typedef Image2D::difference_type difference_type; 47 | 48 | // Save length -> image name 49 | difference_type length = img.filename_ptr->size(); 50 | os.write(reinterpret_cast(&length), std::streamsize(sizeof(difference_type))); 51 | os.write(img.filename_ptr->c_str(), std::streamsize(img.filename_ptr->size())); 52 | } 53 | 54 | // Access --------------------------------------------------------------------// 55 | Array2D Image2D::get_gs() const { 56 | // get_gs() uses opencv's imread() function; must convert Mat to Array2D type. 57 | cv::Mat cv_img = cv::imread((*filename_ptr), cv::IMREAD_GRAYSCALE); 58 | if (!cv_img.data) { 59 | throw std::invalid_argument("Image file : " + *filename_ptr + " cannot be found or read."); 60 | } 61 | 62 | // Images will be read as 8-bit grayscale values; convert these to double 63 | // precision with values ranging from 0 - 1. 64 | Array2D A(cv_img.rows,cv_img.cols); 65 | double conversion = 1/255.0; 66 | for (difference_type p1 = 0; p1 < cv_img.cols; ++p1) { 67 | for (difference_type p2 = 0; p2 < cv_img.rows; ++p2) { 68 | A(p2,p1) = cv_img.data[p1 + p2*cv_img.cols] * conversion; 69 | } 70 | } 71 | 72 | return A; 73 | } 74 | 75 | } -------------------------------------------------------------------------------- /src/ROI2D.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * File: ROI2D.cpp 3 | * Author: justin 4 | * 5 | * Created on February 11, 2015, 12:02 AM 6 | */ 7 | 8 | #include "ROI2D.h" 9 | 10 | namespace ncorr { 11 | 12 | // Additional Constructors ---------------------------------------------------// 13 | ROI2D::ROI2D(Array2D mask, difference_type cutoff) : mask_ptr(std::make_shared>(std::move(mask))), regions_ptr(std::make_shared>()) { 14 | if (cutoff < 0) { 15 | throw std::invalid_argument("Attempted to form ROI2D with cutoff of: " + std::to_string(cutoff) + 16 | ". cutoff must be an integer of value 0 or greater."); 17 | } 18 | 19 | // Given an input of a mask, form ROI2D ----------------------------------// 20 | // Form the nlinfos first 21 | auto nlinfos_pair = ROI2D::region_nlinfo::form_nlinfos(*this->mask_ptr, cutoff); 22 | 23 | // Cycle over each nlinfo and create a corresponding region_boundary 24 | Array2D mask_buf(this->mask_ptr->height(), this->mask_ptr->width()); 25 | for (const auto &nlinfo : nlinfos_pair.first) { 26 | auto boundary = nlinfo.to_boundary(mask_buf); 27 | this->regions_ptr->emplace_back(std::move(nlinfo), std::move(boundary)); 28 | } 29 | 30 | if (nlinfos_pair.second) { 31 | // If regions were removed, update the mask 32 | draw_mask(); 33 | } 34 | 35 | // Set points 36 | set_points(); 37 | } 38 | 39 | ROI2D::ROI2D(region_nlinfo nlinfo, difference_type h, difference_type w) : mask_ptr(std::make_shared>(h,w)), regions_ptr(std::make_shared>()) { 40 | // Given an input of an nlinfo, form ROI2D. . 41 | 42 | // Set regions - can use mask_ptr as buffer since it's empty at this point 43 | auto boundary = nlinfo.to_boundary(*this->mask_ptr); 44 | this->regions_ptr->emplace_back(std::move(nlinfo), std::move(boundary)); 45 | 46 | // Draw mask 47 | draw_mask(); 48 | 49 | // Set points 50 | set_points(); 51 | } 52 | 53 | ROI2D::ROI2D(region_boundary boundary, difference_type h, difference_type w) : mask_ptr(std::make_shared>(h,w)), regions_ptr(std::make_shared>()) { 54 | // Given an input of a boundary, form ROI2D. 55 | 56 | // Set region - can use mask_ptr as buffer since it's empty at this point 57 | auto nlinfo = boundary.to_nlinfo(*this->mask_ptr); 58 | this->regions_ptr->emplace_back(std::move(nlinfo), std::move(boundary)); 59 | 60 | // Draw mask 61 | draw_mask(); 62 | 63 | // Set points 64 | set_points(); 65 | } 66 | 67 | ROI2D::ROI2D(std::vector nlinfos, difference_type h, difference_type w) : mask_ptr(std::make_shared>(h,w)), regions_ptr(std::make_shared>()) { 68 | // Given input of nlinfos, form ROI2D. 69 | 70 | // Set region(s) - can use mask_ptr as buffer since it's empty at this point 71 | for (const auto &nlinfo : nlinfos) { 72 | auto boundary = nlinfo.to_boundary(*this->mask_ptr); 73 | this->regions_ptr->emplace_back(std::move(nlinfo), std::move(boundary)); 74 | } 75 | 76 | // Draw mask 77 | draw_mask(); 78 | 79 | // Set points 80 | set_points(); 81 | } 82 | 83 | ROI2D::ROI2D(std::vector boundaries, difference_type h, difference_type w) : mask_ptr(std::make_shared>(h,w)), regions_ptr(std::make_shared>()) { 84 | // Given an input of boundaries, form ROI2D. 85 | 86 | // Set region(s) - can use mask_ptr as buffer since it's empty at this point 87 | for (const auto &boundary : boundaries) { 88 | auto nlinfo = boundary.to_nlinfo(*this->mask_ptr); 89 | this->regions_ptr->emplace_back(std::move(nlinfo), std::move(boundary)); 90 | } 91 | 92 | // Draw mask 93 | draw_mask(); 94 | 95 | // Set points 96 | set_points(); 97 | } 98 | 99 | // Static factory methods ----------------------------------------------------// 100 | 101 | // Note that 'simple' ROIs require: 102 | // 1) Contains 1 region/nlinfo 103 | // 2) nlinfo must have two nodes per column 104 | // 3) Mask must have odd width 105 | // 4) Must contain its center point. 106 | ROI2D ROI2D::simple_circle(difference_type r) { 107 | difference_type length = 2*r + 1; 108 | double r_squared = std::pow(r,2); 109 | 110 | Array2D mask(length, length); 111 | for (difference_type p2 = 0; p2 < length; ++p2) { 112 | double h_squared = std::pow(p2-r,2); 113 | difference_type np_top = std::ceil(-std::sqrt(r_squared - h_squared)) + r; 114 | difference_type np_bottom = std::floor(std::sqrt(r_squared - h_squared)) + r; 115 | for (difference_type p1 = np_top; p1 <= np_bottom; ++p1) { 116 | mask(p1,p2) = true; 117 | } 118 | } 119 | 120 | return ROI2D(std::move(mask)); 121 | } 122 | 123 | ROI2D ROI2D::simple_square(difference_type r) { 124 | Array2D mask(2*r+1, 2*r+1, true); 125 | 126 | return ROI2D(std::move(mask)); 127 | } 128 | 129 | ROI2D ROI2D::load(std::ifstream &is) { 130 | // Form empty ROI2D then fill in values in accordance to how they are saved 131 | ROI2D roi; 132 | 133 | // Load mask 134 | roi.mask_ptr = std::make_shared>(Array2D::load(is)); 135 | 136 | // Load regions 137 | difference_type num_regions = 0; 138 | is.read(reinterpret_cast(&num_regions), std::streamsize(sizeof(difference_type))); 139 | roi.regions_ptr = std::make_shared>(num_regions); 140 | for (auto ® : *roi.regions_ptr) { 141 | reg = ROI2D::region::load(is); 142 | } 143 | 144 | // Load points 145 | is.read(reinterpret_cast(&roi.points), std::streamsize(sizeof(difference_type))); 146 | 147 | return roi; 148 | } 149 | 150 | // Operators Interface -------------------------------------------------------// 151 | std::ostream& operator<<(std::ostream &os, const ROI2D &roi) { 152 | os << "Mask :" << '\n'; 153 | os << *roi.mask_ptr; 154 | 155 | for (const auto ®ion : *roi.regions_ptr) { 156 | os << region << '\n'; 157 | } 158 | 159 | os << "Total points: " << roi.points; 160 | 161 | return os; 162 | } 163 | 164 | bool isequal(const ROI2D &roi1, const ROI2D &roi2) { 165 | typedef ROI2D::difference_type difference_type; 166 | 167 | if (roi1.points == roi2.points && isequal(*roi1.mask_ptr, *roi2.mask_ptr) && roi1.regions_ptr->size() == roi2.regions_ptr->size()) { 168 | for (difference_type region_idx = 0; region_idx < difference_type(roi1.regions_ptr->size()); ++region_idx) { 169 | if (!isequal((*roi1.regions_ptr)[region_idx], (*roi2.regions_ptr)[region_idx])) { 170 | return false; 171 | } 172 | } 173 | 174 | // At this point, the points, masks, and all the regions in roi1 and roi2 175 | // are the same 176 | return true; 177 | } 178 | 179 | return false; 180 | } 181 | 182 | void save(const ROI2D &roi, std::ofstream &os) { 183 | typedef ROI2D::difference_type difference_type; 184 | 185 | // Save mask -> regions -> points 186 | save(*roi.mask_ptr, os); 187 | 188 | difference_type num_regions = roi.regions_ptr->size(); 189 | os.write(reinterpret_cast(&num_regions), std::streamsize(sizeof(difference_type))); 190 | for (const auto ® : *roi.regions_ptr) { 191 | save(reg, os); 192 | } 193 | 194 | os.write(reinterpret_cast(&roi.points), std::streamsize(sizeof(difference_type))); 195 | } 196 | 197 | // Access --------------------------------------------------------------------// 198 | const ROI2D::region_nlinfo& ROI2D::get_nlinfo(difference_type region_idx) const { 199 | chk_region_idx_in_bounds(region_idx, "get_nlinfo()"); 200 | 201 | return (*regions_ptr)[region_idx].nlinfo; 202 | } 203 | 204 | const ROI2D::region_boundary& ROI2D::get_boundary(difference_type region_idx) const { 205 | chk_region_idx_in_bounds(region_idx, "get_boundary()"); 206 | 207 | return (*regions_ptr)[region_idx].boundary; 208 | } 209 | 210 | std::pair ROI2D::get_region_idx(difference_type p1, difference_type p2) const { 211 | // .first refers to idx of region - will be -1 if it does not exist 212 | // .second refers to idx of nodepair which contains p2 - will be -1 if it does not exists 213 | if (!mask_ptr->in_bounds(p1,p2)) { 214 | return {-1,-1}; 215 | } 216 | 217 | for (difference_type region_idx = 0; region_idx < difference_type(regions_ptr->size()); ++region_idx) { 218 | const auto &nlinfo = (*regions_ptr)[region_idx].nlinfo; 219 | difference_type nl_idx = p2 - nlinfo.left_nl; 220 | if (nlinfo.noderange.in_bounds(nl_idx)) { 221 | for (difference_type np_idx = 0; np_idx < nlinfo.noderange(nl_idx); np_idx += 2) { 222 | difference_type np_top = nlinfo.nodelist(np_idx, nl_idx); 223 | difference_type np_bottom = nlinfo.nodelist(np_idx + 1, nl_idx); 224 | if (p1 < np_top) { 225 | break; // p1 comes before top of nodepair 226 | } 227 | if (p1 <= np_bottom) { 228 | return {region_idx, np_idx}; // p1 is contained in this nodepair 229 | } 230 | } 231 | } 232 | } 233 | // No nlinfo contained p1 and p2 234 | return {-1,-1}; 235 | } 236 | 237 | // Arithmetic operations ------------------------------------------------------// 238 | ROI2D ROI2D::reduce(difference_type scalefactor) const { 239 | if (scalefactor < 1) { 240 | throw std::invalid_argument("Attempted to reduce ROI2D with scalefactor of: " + std::to_string(scalefactor) + 241 | ". scalefactor must be an integer of value 1 or greater."); 242 | } 243 | 244 | // Reduction is done by reducing nlinfos. It is possible for nlinfo to 245 | // disappear after reduction, but an empty nlinfo is kept in order to 246 | // maintain a 1 to 1 correspondence of regions. If nlinfo becomes 247 | // non-contiguous after reduction, reduce() returns the largest contiguous 248 | // region. 249 | std::vector nlinfos_reduced; 250 | Array2D mask_buf(height(), width()); 251 | for (difference_type region_idx = 0; region_idx < difference_type(regions_ptr->size()); ++region_idx) { 252 | nlinfos_reduced.push_back((*regions_ptr)[region_idx].nlinfo.reduce(scalefactor, mask_buf)); 253 | } 254 | 255 | return ROI2D(std::move(nlinfos_reduced), std::ceil(double(height())/scalefactor), std::ceil(double(width())/scalefactor)); 256 | } 257 | 258 | ROI2D ROI2D::form_union(const Array2D &mask) const { 259 | if (mask.height() != height() || mask.width() != width()) { 260 | throw std::invalid_argument("Attempted to form union of ROI2D of size: " + size_2D_string() + 261 | " with mask of size : " + mask.size_2D_string() + ". Mask and ROI2D must have the same size."); 262 | } 263 | 264 | // Union is done by finding new nodepair values depending on the values of 265 | // the mask between the original nodepair values. It is possible for nlinfo to 266 | // disappear after union, but an empty nlinfo is kept in order to maintain a 267 | // 1 to 1 correspondence of regions. If nlinfo becomes non-contiguous after 268 | // union, form_union() returns the largest contiguous region. 269 | std::vector nlinfos_unioned; 270 | Array2D mask_buf(height(), width()); 271 | for (difference_type region_idx = 0; region_idx < difference_type(regions_ptr->size()); ++region_idx) { 272 | nlinfos_unioned.push_back((*regions_ptr)[region_idx].nlinfo.form_union(mask,mask_buf)); 273 | } 274 | 275 | return ROI2D(std::move(nlinfos_unioned), height(), width()); 276 | } 277 | 278 | // Incrementor ---------------------------------------------------------------// 279 | ROI2D::incrementor ROI2D::begin_inc() const { 280 | return (regions_ptr->empty() ? incrementor(*this, 0, { }) : incrementor(*this, 0, regions_ptr->front().nlinfo.begin_inc())); 281 | } 282 | 283 | ROI2D::incrementor ROI2D::end_inc() const { 284 | return (regions_ptr->empty() ? incrementor(*this, 0, { }) : incrementor(*this, regions_ptr->size(), regions_ptr->back().nlinfo.end_inc())); 285 | } 286 | 287 | // contig_subregion_generator ------------------------------------------------// 288 | ROI2D::contig_subregion_generator ROI2D::get_contig_subregion_generator(SUBREGION subregion_type, difference_type r) const { 289 | return contig_subregion_generator(*this, subregion_type, r); 290 | } 291 | 292 | // Utility methods -----------------------------------------------------------// 293 | void ROI2D::set_points() { 294 | points = 0; 295 | for (const auto ®ion : *regions_ptr) { 296 | points += region.nlinfo.points; 297 | } 298 | } 299 | 300 | void ROI2D::draw_mask() { 301 | (*this->mask_ptr)() = false; 302 | for (const auto ®ion : *regions_ptr) { 303 | fill(*this->mask_ptr, region.nlinfo, true); 304 | } 305 | } 306 | 307 | // Checks --------------------------------------------------------------------// 308 | void ROI2D::chk_region_idx_in_bounds(difference_type idx, const std::string &op) const { 309 | if (idx < 0 || idx >= difference_type(regions_ptr->size())) { 310 | throw std::invalid_argument("Attempted to access region " + std::to_string(idx) + " for " + op + " operation, but there are only " + std::to_string(regions_ptr->size()) + " regions."); 311 | } 312 | } 313 | 314 | // Static factory methods ----------------------------------------------------// 315 | ROI2D::region_nlinfo ROI2D::region_nlinfo::load(std::ifstream &is) { 316 | // Form empty nlinfo then fill in values in accordance to how they are saved 317 | region_nlinfo nlinfo; 318 | 319 | // Load bounds 320 | is.read(reinterpret_cast(&nlinfo.top), std::streamsize(sizeof(difference_type))); 321 | is.read(reinterpret_cast(&nlinfo.bottom), std::streamsize(sizeof(difference_type))); 322 | is.read(reinterpret_cast(&nlinfo.left), std::streamsize(sizeof(difference_type))); 323 | is.read(reinterpret_cast(&nlinfo.right), std::streamsize(sizeof(difference_type))); 324 | is.read(reinterpret_cast(&nlinfo.left_nl), std::streamsize(sizeof(difference_type))); 325 | is.read(reinterpret_cast(&nlinfo.right_nl), std::streamsize(sizeof(difference_type))); 326 | 327 | // Load nodelist and noderange 328 | nlinfo.nodelist = Array2D::load(is); 329 | nlinfo.noderange = Array2D::load(is); 330 | 331 | // Load points 332 | is.read(reinterpret_cast(&nlinfo.points), std::streamsize(sizeof(difference_type))); 333 | 334 | return nlinfo; 335 | } 336 | 337 | // Operations interface ------------------------------------------------------// 338 | std::ostream& operator<<(std::ostream &os, const ROI2D::region_nlinfo &nlinfo) { 339 | os << "nlinfo: " << '\n'; 340 | os << "Top bound of nlinfo: " << std::to_string(nlinfo.top) << "." << '\n'; 341 | os << "Bottom bound of nlinfo: " << std::to_string(nlinfo.bottom) << "." << '\n'; 342 | os << "Left bound of nlinfo: " << std::to_string(nlinfo.left) << "." << '\n'; 343 | os << "Right bound of nlinfo: " << std::to_string(nlinfo.right) << "." << '\n'; 344 | os << "Left position of nodelist: " << std::to_string(nlinfo.left_nl) << "." << '\n'; 345 | os << "Right position of nodelist: " << std::to_string(nlinfo.right_nl) << "." << '\n'; 346 | 347 | os << '\n' << "nodelist: " << '\n'; 348 | os << nlinfo.nodelist << '\n'; 349 | os << "noderange: " << '\n'; 350 | os << nlinfo.noderange << '\n'; 351 | 352 | os << '\n' << "nlinfo points: " << std::to_string(nlinfo.points) << "." << '\n'; 353 | 354 | return os; 355 | } 356 | 357 | bool isequal(const ROI2D::region_nlinfo &nlinfo1, const ROI2D::region_nlinfo &nlinfo2) { 358 | return nlinfo1.top == nlinfo2.top && 359 | nlinfo1.bottom == nlinfo2.bottom && 360 | nlinfo1.left == nlinfo2.left && 361 | nlinfo1.right == nlinfo2.right && 362 | nlinfo1.left_nl == nlinfo2.left_nl && 363 | nlinfo1.right_nl == nlinfo2.right_nl && 364 | isequal(nlinfo1.nodelist,nlinfo2.nodelist) && 365 | isequal(nlinfo1.noderange,nlinfo2.noderange) && 366 | nlinfo1.points == nlinfo2.points; 367 | } 368 | 369 | void save(const ROI2D::region_nlinfo &nlinfo, std::ofstream &os) { 370 | typedef ROI2D::difference_type difference_type; 371 | 372 | // Save bounds -> nodelist -> noderange -> points 373 | os.write(reinterpret_cast(&nlinfo.top), std::streamsize(sizeof(difference_type))); 374 | os.write(reinterpret_cast(&nlinfo.bottom), std::streamsize(sizeof(difference_type))); 375 | os.write(reinterpret_cast(&nlinfo.left), std::streamsize(sizeof(difference_type))); 376 | os.write(reinterpret_cast(&nlinfo.right), std::streamsize(sizeof(difference_type))); 377 | os.write(reinterpret_cast(&nlinfo.left_nl), std::streamsize(sizeof(difference_type))); 378 | os.write(reinterpret_cast(&nlinfo.right_nl), std::streamsize(sizeof(difference_type))); 379 | 380 | save(nlinfo.nodelist, os); 381 | save(nlinfo.noderange, os); 382 | 383 | os.write(reinterpret_cast(&nlinfo.points), std::streamsize(sizeof(difference_type))); 384 | } 385 | 386 | // Arithmetic methods --------------------------------------------------------// 387 | bool ROI2D::region_nlinfo::in_nlinfo(difference_type p1, difference_type p2) const { 388 | difference_type nl_idx = p2 - left_nl; 389 | if (noderange.in_bounds(nl_idx)) { 390 | for (difference_type np_idx = 0; np_idx < noderange(nl_idx); np_idx += 2) { 391 | difference_type np_top = nodelist(np_idx, nl_idx); 392 | difference_type np_bottom = nodelist(np_idx + 1, nl_idx); 393 | if (p1 < np_top) { 394 | return false; // p1 comes before top of nodepair 395 | } 396 | if (p1 <= np_bottom) { 397 | return true; // p1 is contained in this nodepair 398 | } 399 | } 400 | } 401 | 402 | return false; 403 | } 404 | 405 | ROI2D::region_nlinfo& ROI2D::region_nlinfo::shift(difference_type p1_shift, difference_type p2_shift) { 406 | // Note that no checking is done for shifting out of bounds so be careful. 407 | 408 | // Shift bounds first 409 | top += p1_shift; 410 | bottom += p1_shift; 411 | left += p2_shift; 412 | right += p2_shift; 413 | left_nl += p2_shift; 414 | right_nl += p2_shift; 415 | 416 | // Shift nodelist 417 | for (difference_type nl_idx = 0; nl_idx < nodelist.width(); ++nl_idx) { 418 | for (difference_type np_idx = 0; np_idx < noderange(nl_idx); ++np_idx) { 419 | nodelist(np_idx,nl_idx) += p1_shift; 420 | } 421 | } 422 | 423 | return *this; 424 | } 425 | 426 | // Incrementor ---------------------------------------------------------------// 427 | ROI2D::region_nlinfo::incrementor ROI2D::region_nlinfo::begin_inc() const { 428 | return (empty() ? incrementor(*this, 0, 0, 0) : incrementor(*this, first_pos_idx(), 0, first_pos_p1())); 429 | } 430 | 431 | ROI2D::region_nlinfo::incrementor ROI2D::region_nlinfo::end_inc() const { 432 | return (empty() ? incrementor(*this, 0, 0, 0) : incrementor(*this, nodelist.width(), noderange(last_pos_idx()), last_pos_p1() + 1)); 433 | } 434 | 435 | // Static factory methods ----------------------------------------------------// 436 | namespace details { 437 | void add_interacting_nodes(ROI2D::difference_type np_adj_p2, 438 | ROI2D::difference_type np_loaded_top, 439 | ROI2D::difference_type np_loaded_bottom, 440 | Array2D> &overall_nodelist, 441 | Array2D> &overall_active_nodepairs, 442 | std::stack &queue_np_idx) { 443 | typedef ROI2D::difference_type difference_type; 444 | 445 | // Make sure adjacent nodepair(s) position is in range. 446 | if (overall_nodelist.in_bounds(np_adj_p2)) { 447 | // Scans nodes from top to bottom 448 | for (difference_type np_adj_idx = 0; np_adj_idx < difference_type(overall_nodelist(np_adj_p2).size()); np_adj_idx += 2) { 449 | difference_type np_adj_top = overall_nodelist(np_adj_p2)[np_adj_idx]; 450 | difference_type np_adj_bottom = overall_nodelist(np_adj_p2)[np_adj_idx + 1]; 451 | if (np_loaded_bottom < np_adj_top) { 452 | return; // top node of adjacent nodepair is below bottom node of loaded nodepair 453 | } 454 | if (overall_active_nodepairs(np_adj_p2)[np_adj_idx/2] && np_loaded_top <= np_adj_bottom) { 455 | // Inactivate node pair, and then insert into the queue 456 | overall_active_nodepairs(np_adj_p2)[np_adj_idx/2] = false; 457 | queue_np_idx.push(np_adj_top); 458 | queue_np_idx.push(np_adj_bottom); 459 | queue_np_idx.push(np_adj_p2); 460 | } 461 | } 462 | } 463 | } 464 | } 465 | 466 | std::pair,bool> ROI2D::region_nlinfo::form_nlinfos(const Array2D &mask, difference_type cutoff) { 467 | if (cutoff < 0) { 468 | throw std::invalid_argument("Attempted to form nlinfos with cutoff of: " + std::to_string(cutoff) + 469 | ". cutoff must be an integer of value 0 or greater."); 470 | } 471 | 472 | // Form overall_nodelist and overall_active_nodepairs --------------------// 473 | Array2D> overall_nodelist(1,mask.width()); 474 | Array2D> overall_active_nodepairs(1,mask.width()); 475 | for (difference_type p2 = 0; p2 < mask.width(); ++p2) { 476 | bool in_nodepair = false; 477 | for (difference_type p1 = 0; p1 < mask.height(); ++p1) { 478 | if (!in_nodepair && mask(p1,p2)) { 479 | in_nodepair = true; 480 | overall_nodelist(p2).push_back(p1); // sets top node 481 | } 482 | if (in_nodepair && (!mask(p1,p2) || p1 == mask.height()-1)) { 483 | in_nodepair = false; 484 | overall_nodelist(p2).push_back((p1 == mask.height()-1 && mask(p1,p2)) ? p1 : p1-1); // Sets bottom node 485 | } 486 | } 487 | 488 | // Update overall_active_nodepairs - this keeps track of which node 489 | // pairs have been analyzed when doing contiguous separation; set to 490 | // true initially. 491 | overall_active_nodepairs(p2).resize(overall_nodelist(p2).size()/2, true); 492 | } 493 | 494 | // Separate regions ------------------------------------------------------// 495 | // Regions are made 4-way contiguous. Scan over columns and separate 496 | // contiguous nodelists in overall_nodelist. 497 | std::vector nlinfos; // This will get updated and returned 498 | bool removed = false; // Keeps track if regions are removed due to "cutoff" parameter 499 | for (difference_type p2_sweep = 0; p2_sweep < overall_nodelist.width(); ++p2_sweep) { 500 | // Find first active node pair 501 | difference_type np_init_idx = -1; 502 | for (difference_type np_idx = 0; np_idx < difference_type(overall_nodelist(p2_sweep).size()); np_idx += 2) { 503 | if (overall_active_nodepairs(p2_sweep)[np_idx/2]) { // Test if nodepair is active 504 | overall_active_nodepairs(p2_sweep)[np_idx/2] = false; // Inactivate node pair 505 | np_init_idx = np_idx; // Store nodepair idx 506 | break; 507 | } 508 | } 509 | 510 | // If there are no active node pairs, then continue to next column 511 | if (np_init_idx == -1) { 512 | continue; 513 | } 514 | 515 | // nlinfo_buf to be updated, then inserted into nlinfos 516 | ROI2D::region_nlinfo nlinfo_buf(mask.height()-1, // Top bound of region (this gets updated) 517 | 0, // Bottom bound of region (this gets updated) 518 | p2_sweep, // Left bound of region (this is correct) 519 | 0, // Right bound of region (this gets updated) 520 | p2_sweep, // Left position of nodelist (this is correct) 521 | 0, // Right position of nodelist (this gets updated) 522 | 0, // h of nodelist (this gets updated) 523 | 0, // w of nodelist (this gets updated) 524 | 0); // Number of points in region (this gets updated) 525 | 526 | // Keep track of nodes with separate_nodelist 527 | Array2D> separate_nodelist(1,mask.width()); 528 | 529 | // Initialize queue and enter while loop - exit when queue is empty 530 | std::stack queue_np_idx; // Holds all nodepairs (along with their index) which need to be processed 531 | queue_np_idx.push(overall_nodelist(p2_sweep)[np_init_idx]); // Top 532 | queue_np_idx.push(overall_nodelist(p2_sweep)[np_init_idx + 1]); // Bottom 533 | queue_np_idx.push(p2_sweep); // position of nodepair 534 | while (!queue_np_idx.empty()) { 535 | // Pop nodepair and its position out of queue and compare 536 | // it to adjacent nodepairs (left and right of np_loaded_p2) 537 | difference_type np_loaded_p2 = queue_np_idx.top(); queue_np_idx.pop(); 538 | difference_type np_loaded_bottom = queue_np_idx.top(); queue_np_idx.pop(); 539 | difference_type np_loaded_top = queue_np_idx.top(); queue_np_idx.pop(); 540 | 541 | // Compare to node pairs LEFT. Any node pairs which interact are added to the queue 542 | details::add_interacting_nodes(np_loaded_p2 - 1, 543 | np_loaded_top, 544 | np_loaded_bottom, 545 | overall_nodelist, 546 | overall_active_nodepairs, 547 | queue_np_idx); 548 | 549 | // Compare to node pairs RIGHT. Any node pairs which interact are added to the queue 550 | details::add_interacting_nodes(np_loaded_p2 + 1, 551 | np_loaded_top, 552 | np_loaded_bottom, 553 | overall_nodelist, 554 | overall_active_nodepairs, 555 | queue_np_idx); 556 | 557 | // Update points 558 | nlinfo_buf.points += np_loaded_bottom - np_loaded_top + 1; 559 | 560 | // Update bounds - note that "right_nl" and "right" are the same 561 | if (np_loaded_top < nlinfo_buf.top) { nlinfo_buf.top = np_loaded_top; } // Top 562 | if (np_loaded_bottom > nlinfo_buf.bottom) { nlinfo_buf.bottom = np_loaded_bottom; } // Bottom 563 | if (np_loaded_p2 > nlinfo_buf.right) { nlinfo_buf.right_nl = nlinfo_buf.right = np_loaded_p2; } // Right 564 | 565 | // Insert node pairs and then sort - usually very small so BST isn't 566 | // necessary. 567 | separate_nodelist(np_loaded_p2).push_back(np_loaded_top); 568 | separate_nodelist(np_loaded_p2).push_back(np_loaded_bottom); 569 | std::sort(separate_nodelist(np_loaded_p2).begin(), separate_nodelist(np_loaded_p2).end()); 570 | } 571 | 572 | // Now finish setting nodelist and noderange for this region. 573 | // Find max nodes first so we can use it to set the correct height 574 | // for nodelist. 575 | difference_type max_nodes = 0; 576 | for (const auto &nodes : separate_nodelist) { 577 | if (difference_type(nodes.size()) > max_nodes) { 578 | max_nodes = nodes.size(); 579 | } 580 | } 581 | 582 | // Set and fill nodelist and noderange 583 | nlinfo_buf.nodelist = Array2D(max_nodes, nlinfo_buf.right_nl - nlinfo_buf.left_nl + 1); 584 | nlinfo_buf.noderange = Array2D(1, nlinfo_buf.right_nl - nlinfo_buf.left_nl + 1); 585 | for (difference_type nl_idx = 0; nl_idx < nlinfo_buf.nodelist.width(); ++nl_idx) { 586 | difference_type p2 = nl_idx + nlinfo_buf.left_nl; 587 | // noderange: 588 | nlinfo_buf.noderange(nl_idx) = separate_nodelist(p2).size(); 589 | // nodelist: 590 | for (difference_type np_idx = 0; np_idx < difference_type(separate_nodelist(p2).size()); ++np_idx) { 591 | nlinfo_buf.nodelist(np_idx,nl_idx) = separate_nodelist(p2)[np_idx]; 592 | } 593 | } 594 | 595 | // Subtract one from p2_sweep in order to recheck the column to 596 | // ensure all nodes are deactivated before proceeding 597 | --p2_sweep; 598 | 599 | // Make sure number of points in nlinfo is greater than or equal to 600 | // the cutoff 601 | if (nlinfo_buf.points >= cutoff) { 602 | nlinfos.push_back(nlinfo_buf); 603 | } else { 604 | removed = true; // Parameter lets caller know regions were removed 605 | } 606 | } 607 | 608 | return {std::move(nlinfos), removed}; 609 | } 610 | 611 | // Arithmetic methods --------------------------------------------------------// 612 | ROI2D::region_nlinfo ROI2D::region_nlinfo::reduce(difference_type scalefactor, Array2D &mask_buf) const { 613 | if (scalefactor < 1) { 614 | throw std::invalid_argument("Attempted to reduce nlinfo with scalefactor of: " + std::to_string(scalefactor) + 615 | ". scalefactor must be an integer of value 1 or greater."); 616 | } 617 | 618 | // This function will return a copy of a reduced nlinfo based on input 619 | // scalefactor. 620 | 621 | if (empty()) { 622 | // Return empty nlinfo 623 | return region_nlinfo(); 624 | } 625 | 626 | // Only *_nl bounds can be set now. Other bounds must be calculated since 627 | // regions can disappear if a large scalefactor is used. Note that since 628 | // nlinfo is nonempty at this point, using the bounds is valid. 629 | difference_type top_reduced = std::ceil(double(top)/scalefactor); 630 | difference_type bottom_reduced = std::floor(double(bottom)/scalefactor); 631 | difference_type left_reduced_nl = std::ceil(double(left_nl)/scalefactor); 632 | difference_type right_reduced_nl = std::floor(double(right_nl)/scalefactor); 633 | region_nlinfo nlinfo_reduced(bottom_reduced, // top (gets updated) 634 | top_reduced, // bottom (gets updated) 635 | right_reduced_nl, // left (gets updated) 636 | left_reduced_nl, // right (gets updated) 637 | left_reduced_nl, // left_nl (correct) 638 | right_reduced_nl, // right_nl (correct) 639 | nodelist.height(), // nodelist height (correct) 640 | right_reduced_nl - left_reduced_nl + 1, // nodelist width (correct) 641 | 0); // points (gets updated) 642 | 643 | for (difference_type nl_idx_reduced = 0; nl_idx_reduced < nlinfo_reduced.nodelist.width(); ++nl_idx_reduced) { 644 | difference_type nl_idx = (nl_idx_reduced + nlinfo_reduced.left_nl) * scalefactor - left_nl; 645 | for (difference_type np_idx = 0; np_idx < noderange(nl_idx); np_idx += 2) { 646 | difference_type np_top_reduced = std::ceil(double(nodelist(np_idx, nl_idx))/scalefactor); 647 | difference_type np_bottom_reduced = std::floor(double(nodelist(np_idx + 1, nl_idx))/scalefactor); 648 | if (np_bottom_reduced >= np_top_reduced) { 649 | // Update points 650 | nlinfo_reduced.points += np_bottom_reduced - np_top_reduced + 1; 651 | 652 | // Update bounds 653 | if (np_top_reduced < nlinfo_reduced.top) { nlinfo_reduced.top = np_top_reduced; } 654 | if (np_bottom_reduced > nlinfo_reduced.bottom) { nlinfo_reduced.bottom = np_bottom_reduced; } 655 | if (nl_idx_reduced + nlinfo_reduced.left_nl < nlinfo_reduced.left) { nlinfo_reduced.left = nl_idx_reduced + nlinfo_reduced.left_nl; } 656 | if (nl_idx_reduced + nlinfo_reduced.left_nl > nlinfo_reduced.right) { nlinfo_reduced.right = nl_idx_reduced + nlinfo_reduced.left_nl; } 657 | 658 | // Insert nodepairs 659 | nlinfo_reduced.nodelist(nlinfo_reduced.noderange(nl_idx_reduced), nl_idx_reduced) = np_top_reduced; 660 | nlinfo_reduced.nodelist(nlinfo_reduced.noderange(nl_idx_reduced)+1, nl_idx_reduced) = np_bottom_reduced; 661 | 662 | // Update noderange 663 | nlinfo_reduced.noderange(nl_idx_reduced) += 2; 664 | } 665 | } 666 | } 667 | // Return largest contiguous region since reduced nlinfo may no longer be 668 | // 4-way contiguous 669 | return nlinfo_reduced.largest_contig_nlinfo(mask_buf); 670 | } 671 | 672 | ROI2D::region_nlinfo ROI2D::region_nlinfo::form_union(const Array2D &mask, Array2D &mask_buf) const { 673 | if (!mask.same_size(mask_buf)) { 674 | throw std::invalid_argument("Attempted to form union with mask of size: " + mask.size_2D_string() + 675 | " with mask_buf of size : " + mask_buf.size_2D_string() + ". Mask and mask_buf must have the same size."); 676 | } 677 | 678 | // This function will return a copy of a unioned nlinfo based on input mask. 679 | 680 | if (empty()) { 681 | // Return empty nlinfo 682 | return region_nlinfo(); 683 | } 684 | 685 | // Only *_nl bounds can be set now. Other bounds must be calculated since 686 | // it is indeterminate how many new nodes will be added. 687 | region_nlinfo nlinfo_union(bottom, // top (gets updated) 688 | top, // bottom (gets updated) 689 | right_nl, // left (gets updated) 690 | left_nl, // right (gets updated) 691 | left_nl, // left_nl (correct) 692 | right_nl, // right_nl (correct) 693 | 0, // nodelist height (gets updated) 694 | nodelist.width(), // nodelist width (correct) 695 | 0); // points (gets updated) 696 | 697 | 698 | // Use a vector buffer to hold nodepairs 699 | Array2D> nodelist_buf_vec(nodelist.width(),1); 700 | difference_type max_height = 0; // will be used to allocate array for nodelist 701 | 702 | // Cycle over nlinfo and find new nodepairs 703 | for (difference_type nl_idx = 0; nl_idx < nodelist.width(); ++nl_idx) { 704 | difference_type p2 = nl_idx + left_nl; 705 | for (difference_type np_idx = 0; np_idx < noderange(nl_idx); np_idx += 2) { 706 | difference_type np_top = nodelist(np_idx,nl_idx); 707 | difference_type np_bottom = nodelist(np_idx+1,nl_idx); 708 | bool in_nodepair = false; 709 | for (difference_type p1 = np_top; p1 <= np_bottom; p1++) { 710 | difference_type np_top_new, np_bottom_new; 711 | if (!in_nodepair && mask(p1,p2)) { 712 | in_nodepair = true; 713 | np_top_new = p1; 714 | nodelist_buf_vec(nl_idx).push_back(np_top_new); 715 | } 716 | if (in_nodepair && (!mask(p1,p2) || p1 == np_bottom)) { 717 | in_nodepair = false; 718 | np_bottom_new = ((p1 == np_bottom && mask(p1,p2)) ? p1 : p1-1); 719 | 720 | // update points 721 | nlinfo_union.points += np_bottom_new - np_top_new + 1; 722 | 723 | // update bounds 724 | if (np_top_new < nlinfo_union.top) { nlinfo_union.top = np_top_new; } 725 | if (np_bottom_new > nlinfo_union.bottom) { nlinfo_union.bottom = np_bottom_new; } 726 | if (nl_idx + nlinfo_union.left_nl < nlinfo_union.left) { nlinfo_union.left = nl_idx + nlinfo_union.left_nl; } 727 | if (nl_idx + nlinfo_union.left_nl > nlinfo_union.right) { nlinfo_union.right = nl_idx + nlinfo_union.left_nl; } 728 | 729 | // insert bottom of nodepair 730 | nodelist_buf_vec(nl_idx).push_back(np_bottom_new); 731 | 732 | // Update noderange 733 | nlinfo_union.noderange(nl_idx) += 2; 734 | 735 | // Update max height 736 | if (nlinfo_union.noderange(nl_idx) > max_height) { max_height = nlinfo_union.noderange(nl_idx); } 737 | } 738 | } 739 | } 740 | } 741 | 742 | // Store nodepairs in nodelist_buf_vec in nlinfo_union 743 | nlinfo_union.nodelist = Array2D(max_height, nlinfo_union.nodelist.width()); 744 | for (difference_type nl_idx = 0; nl_idx < nodelist.width(); ++nl_idx) { 745 | for (difference_type np_idx = 0; np_idx < difference_type(nodelist_buf_vec(nl_idx).size()); ++np_idx) { 746 | nlinfo_union.nodelist(np_idx,nl_idx) = nodelist_buf_vec(nl_idx)[np_idx]; 747 | } 748 | } 749 | // Return largest contiguous region since unioned nlinfo may no longer be 750 | // 4-way contiguous 751 | return nlinfo_union.largest_contig_nlinfo(mask_buf); 752 | } 753 | 754 | namespace details { 755 | Array2D calc_boundary(const Array2D &mask, double init_p1, double init_p2, ROI2D::difference_type init_direc = 0) { 756 | typedef ROI2D::difference_type difference_type; 757 | 758 | if (init_direc < 0 || init_direc > 3) { 759 | throw std::invalid_argument("Initial direction of: " + std::to_string(init_direc) + " was provided to calc_boundary function. Initial direction must be between (inclusive) 0 and 3."); 760 | } 761 | 762 | // Note that the returned boundary is "open" (does not contain duplicate 763 | // initial and end points) and is the "outer boundary" of the mask (i.e. 764 | // if the mask contains 1 true pixel, a boundary containing the 4 points 765 | // of the corners of the pixel is returned). This will return a boundary 766 | // for an 8-way connected region. init_p1 and init_p2 need to be the 767 | // location WRT the outer boundary (i.e (1.5, 2.5) for a pixel located 768 | // at (2, 3) assuming its the left-most top pixel). If this function is 769 | // called correctly, boundary is guaranteed to contain at least 4 points. 770 | 771 | // direc is based on: 772 | // 773 | // 1 774 | // | 775 | // 2 ------- 0 776 | // | 777 | // 3 778 | // 779 | // Initial direction of 0 is correct for inputs containing the left-most 780 | // top pixel in an 8-way connected region. 781 | 782 | // Initialize p1, p2, and direc, which keep track of the current point 783 | // location and the direction taken to get there, respectively. 784 | double p1 = init_p1; 785 | double p2 = init_p2; 786 | difference_type direc = init_direc; 787 | 788 | // Initialize boundary 789 | std::vector boundary_vec = {p1,p2}; 790 | bool initpoint_encountered = false; 791 | while (!initpoint_encountered) { 792 | // Get initial new direction 793 | direc = (direc + 3) % 4; 794 | 795 | // Cycle clockwise to find new direction 796 | for (difference_type it = 0; it < 4; ++it) { 797 | // Increment point based on direc 798 | double p1_inc, p2_inc; 799 | difference_type p1_inc_mask, p2_inc_mask; 800 | switch (direc) { 801 | case 0: p1_inc = p1; p2_inc = p2 + 1; 802 | p1_inc_mask = std::round(p1_inc - 0.5); p2_inc_mask = std::round(p2_inc - 0.5); 803 | break; 804 | case 1: p1_inc = p1 - 1; p2_inc = p2; 805 | p1_inc_mask = std::round(p1_inc + 0.5); p2_inc_mask = std::round(p2_inc - 0.5); 806 | break; 807 | case 2: p1_inc = p1; p2_inc = p2 - 1; 808 | p1_inc_mask = std::round(p1_inc + 0.5); p2_inc_mask = std::round(p2_inc + 0.5); 809 | break; 810 | case 3: p1_inc = p1 + 1; p2_inc = p2; 811 | p1_inc_mask = std::round(p1_inc - 0.5); p2_inc_mask = std::round(p2_inc + 0.5); 812 | break; 813 | } 814 | 815 | if (mask.in_bounds(p1_inc_mask,p2_inc_mask) && mask(p1_inc_mask,p2_inc_mask)) { 816 | // This is the next point in the boundary 817 | if (boundary_vec[0] != p1_inc || boundary_vec[1] != p2_inc) { 818 | // Insert point into boundary and then set current point. 819 | boundary_vec.push_back(p1_inc); boundary_vec.push_back(p2_inc); 820 | p1 = p1_inc; p2 = p2_inc; 821 | } else { 822 | initpoint_encountered = true; 823 | } 824 | // break from for loop 825 | break; 826 | } else { 827 | // Increment clockwise one position, then re-check 828 | direc = (direc + 1) % 4; 829 | } 830 | 831 | if (it == 3) { 832 | // This means an empty mask was provided and is a programmer 833 | // error 834 | throw std::invalid_argument("Invalid initial point or empty mask was provided to calc_boundary() function."); 835 | } 836 | } 837 | } 838 | 839 | // Convert vec_boundary to Array2D 840 | Array2D boundary(boundary_vec.size()/2, 2); 841 | for (difference_type idx = 0; idx < difference_type(boundary_vec.size()); idx += 2) { 842 | boundary(idx/2,0) = boundary_vec[idx]; 843 | boundary(idx/2,1) = boundary_vec[idx+1]; 844 | } 845 | 846 | return boundary; 847 | } 848 | } 849 | 850 | ROI2D::region_nlinfo ROI2D::region_nlinfo::largest_contig_nlinfo(Array2D &mask_buf) const { 851 | if (empty()) { 852 | // nlinfo is empty, so return empty nlinfo. 853 | return ROI2D::region_nlinfo(); 854 | } 855 | 856 | // Clear mask buffer and then fill with nlinfo 857 | mask_buf() = false; 858 | fill(mask_buf, *this, true); 859 | 860 | // Form new nlinfos and then return the one with the largest number of points 861 | auto nlinfos_pair = ROI2D::region_nlinfo::form_nlinfos(mask_buf); 862 | // nlinfo is non-empty at this point, so max element is guaranteed to exist. 863 | return *std::max_element(nlinfos_pair.first.begin(), nlinfos_pair.first.end(), [](const ROI2D::region_nlinfo &a, const ROI2D::region_nlinfo &b) { return a.points < b.points; }); 864 | } 865 | 866 | ROI2D::region_boundary ROI2D::region_nlinfo::to_boundary(Array2D &mask_buf) const { 867 | // Converts input nlinfo to a region_boundary. Note that no bounds checking 868 | // nor contiguity checking is done for input nlinfo's, so they must be 869 | // correct. 870 | 871 | if (empty()) { 872 | // If nlinfo is empty, return an empty boundary. 873 | return ROI2D::region_boundary(); 874 | } 875 | 876 | // Form 1 "add" boundary and varying "sub" boundaries per nlinfo. 877 | // Create mask_buf and then fill nlinfo. 878 | mask_buf() = false; 879 | fill(mask_buf, *this, true); 880 | 881 | // Get the "add" boundary ------------------------------------------------// 882 | // Use the left-most top pixel in the region; must use the "outer" position, 883 | // so subtract by 0.5. 884 | auto add_boundary = details::calc_boundary(mask_buf, first_pos_p1() - 0.5, first_pos_p2() - 0.5); 885 | 886 | // Find the "sub" boundaries ---------------------------------------------// 887 | // Use mask_inv_buf to keep track of which sub boundaries have been analyzed. 888 | Array2D mask_inv_buf(mask_buf.height(), mask_buf.width()); 889 | fill(mask_inv_buf, add_boundary, true); 890 | mask_inv_buf = mask_inv_buf & ~mask_buf; // This shows "holes" as white regions 891 | 892 | // Cycle over nlinfo and check for holes in columns with more than 1 nodepair 893 | std::vector> sub_boundaries; 894 | for (difference_type nl_idx = 0; nl_idx < nodelist.width(); ++nl_idx) { 895 | if (noderange(nl_idx) > 2) { 896 | difference_type p2 = nl_idx + left_nl; 897 | // Test one pixel below bottom node of every node pair except 898 | // for the last node pair, since this will be the bottom of the ROI 899 | for (difference_type np_idx = 0; np_idx < noderange(nl_idx) - 2; np_idx += 2) { 900 | difference_type np_bottom = nodelist(np_idx + 1,nl_idx); 901 | if (mask_inv_buf(np_bottom + 1,p2)) { 902 | // This is a boundary which hasn't been analyzed yet 903 | sub_boundaries.push_back(details::calc_boundary(mask_inv_buf, np_bottom + 0.5, p2 - 0.5)); 904 | 905 | // Fill in this boundary so it does not get analyzed again 906 | // Note that sub_boundary cannot have a hole, so it is 907 | // safe to use boundary fill. Also, boundary is guaranteed 908 | // to fill region covered by nlinfo. 909 | fill(mask_inv_buf, sub_boundaries.back(), false); 910 | } 911 | } 912 | } 913 | } 914 | 915 | return ROI2D::region_boundary(std::move(add_boundary), std::move(sub_boundaries)); 916 | } 917 | 918 | // Utility -------------------------------------------------------------------// 919 | void ROI2D::region_nlinfo::chk_nonempty_op(const std::string &op) const { 920 | if (this->empty()) { 921 | throw std::invalid_argument("Attempted to use " + op + " operator on empty nlinfo" + 922 | ". nlinfo must be nonempty."); 923 | } 924 | } 925 | 926 | // Static factory methods ----------------------------------------------------// 927 | ROI2D::region_boundary ROI2D::region_boundary::load(std::ifstream &is) { 928 | // Form empty boundary then fill in values in accordance to how they are saved 929 | region_boundary boundary; 930 | 931 | // Load add boundary 932 | boundary.add = Array2D::load(is); 933 | 934 | // Allocate new sub boundaries and then load them 935 | difference_type num_sub_boundaries = 0; 936 | is.read(reinterpret_cast(&num_sub_boundaries), std::streamsize(sizeof(difference_type))); 937 | boundary.sub.resize(num_sub_boundaries); 938 | for (auto &sub_boundary : boundary.sub) { 939 | sub_boundary = Array2D::load(is); 940 | } 941 | 942 | return boundary; 943 | } 944 | 945 | // Operations interface ------------------------------------------------------// 946 | std::ostream& operator<<(std::ostream &os, const ROI2D::region_boundary &boundary) { 947 | typedef ROI2D::difference_type difference_type; 948 | 949 | os << "Add Boundary: " << '\n' << boundary.add; 950 | for (difference_type boundary_idx = 0; boundary_idx < difference_type(boundary.sub.size()); ++boundary_idx) { 951 | os << '\n' << "Sub Boundary(" << boundary_idx << ") :" << '\n' << boundary.sub[boundary_idx]; 952 | } 953 | 954 | return os; 955 | } 956 | 957 | bool isequal(const ROI2D::region_boundary &boundary1, const ROI2D::region_boundary &boundary2) { 958 | typedef ROI2D::difference_type difference_type; 959 | 960 | if (isequal(boundary1.add,boundary2.add) && boundary1.sub.size() == boundary2.sub.size()) { 961 | for (difference_type boundary_idx = 0; boundary_idx < difference_type(boundary1.sub.size()); ++boundary_idx) { 962 | if (!isequal(boundary1.sub[boundary_idx], boundary2.sub[boundary_idx])) { 963 | return false; 964 | } 965 | } 966 | // Add boundary and sub boundaries are the same 967 | return true; 968 | } 969 | 970 | return false; 971 | } 972 | 973 | void save(const ROI2D::region_boundary &boundary, std::ofstream &os) { 974 | typedef ROI2D::difference_type difference_type; 975 | 976 | // Save add boundary -> vec of sub boundaries 977 | save(boundary.add, os); 978 | 979 | // Save length of vector 980 | difference_type num_sub_boundaries = boundary.sub.size(); 981 | os.write(reinterpret_cast(&num_sub_boundaries), std::streamsize(sizeof(difference_type))); 982 | // Save each sub boundary 983 | for (const auto &sub_boundary : boundary.sub) { 984 | save(sub_boundary, os); 985 | } 986 | } 987 | 988 | // Arithmetic methods --------------------------------------------------------// 989 | ROI2D::region_nlinfo ROI2D::region_boundary::to_nlinfo(Array2D &mask_buf) const { 990 | // Converts boundary to nlinfo. This will form an nlinfo corresponding to 991 | // the largest contiguous enclosing region of the boundary. 992 | 993 | // Clear mask_buf and then draw boundary 994 | mask_buf() = false; 995 | fill(mask_buf, add, true); 996 | for (const auto &sub_boundary : sub) { 997 | fill(mask_buf, sub_boundary, false); 998 | } 999 | 1000 | // Get nlinfos for this boundary 1001 | auto nlinfos_pair = ROI2D::region_nlinfo::form_nlinfos(mask_buf); 1002 | 1003 | if (nlinfos_pair.first.empty()) { 1004 | // Boundary is empty, so return empty nlinfo 1005 | return ROI2D::region_nlinfo(); 1006 | } 1007 | 1008 | // Its possible for boundary to produce multiple nlinfos if it gets "pinched", 1009 | // so return the nlinfo with the largest number of points. nlinfo is 1010 | // non-empty at this point, so max element is guaranteed to exist. 1011 | return *std::max_element(nlinfos_pair.first.begin(), nlinfos_pair.first.end(), [](const ROI2D::region_nlinfo &a, const ROI2D::region_nlinfo &b) { return a.points < b.points; }); 1012 | } 1013 | 1014 | // Static factory methods ----------------------------------------------------// 1015 | ROI2D::region ROI2D::region::load(std::ifstream &is) { 1016 | // Form empty region then fill in values in accordance to how they are saved 1017 | region reg; 1018 | 1019 | // Load nlinfo 1020 | reg.nlinfo = ROI2D::region_nlinfo::load(is); 1021 | 1022 | // Load boundary 1023 | reg.boundary = ROI2D::region_boundary::load(is); 1024 | 1025 | return reg; 1026 | } 1027 | 1028 | // Operations interface ------------------------------------------------------// 1029 | std::ostream& operator<<(std::ostream &os, const ROI2D::region ®) { 1030 | os << reg.nlinfo; 1031 | os << reg.boundary; 1032 | 1033 | return os; 1034 | } 1035 | 1036 | void save(const ROI2D::region ®, std::ofstream &os) { 1037 | // Save nlinfo -> boundary 1038 | save(reg.nlinfo, os); 1039 | save(reg.boundary, os); 1040 | } 1041 | 1042 | namespace details { 1043 | // Arithmetic methods ----------------------------------------------------// 1044 | nlinfo_incrementor& nlinfo_incrementor::operator++() { 1045 | if (p1 == nlinfo_ptr->nodelist(np_idx+1,nl_idx)) { 1046 | // incremented position will go beyond bottom node 1047 | if (np_idx == nlinfo_ptr->noderange(nl_idx)-2) { 1048 | // Incremented nodepair index will go beyond nodepairs in this column, 1049 | // so search columns to find next one with nodepairs 1050 | for (++nl_idx; nl_idx < nlinfo_ptr->nodelist.width() && nlinfo_ptr->noderange(nl_idx) == 0; ++nl_idx) { } 1051 | if (nl_idx == nlinfo_ptr->nodelist.width()) { 1052 | // End has been reached, increment nodepair_idx and p1 to one 1053 | // beyond the end to match the end incrementor. 1054 | np_idx += 2; 1055 | ++p1; 1056 | } else { 1057 | np_idx = 0; 1058 | p1 = nlinfo_ptr->nodelist(np_idx,nl_idx); // top node 1059 | } 1060 | } else { 1061 | np_idx += 2; 1062 | p1 = nlinfo_ptr->nodelist(np_idx,nl_idx); // top node 1063 | } 1064 | } else { 1065 | ++p1; 1066 | } 1067 | 1068 | return *this; 1069 | } 1070 | 1071 | // Additional Constructors -----------------------------------------------// 1072 | ROI2D_incrementor::ROI2D_incrementor(const ROI2D &roi, difference_type region_idx, const ROI2D::region_nlinfo::incrementor &nlinfo_inc) : roi(roi), region_idx(region_idx), nlinfo_inc(nlinfo_inc) { 1073 | // If the initial nlinfo_inc is the beginning incrementor for the first 1074 | // region, increment until the first position is found. This is done for 1075 | // convenience so the begin_inc() can be set easily by the caller. 1076 | if (this->roi.size_regions() > 0 && this->nlinfo_inc == this->roi.get_nlinfo(0).begin_inc() && this->roi.get_nlinfo(0).empty()) { 1077 | // Set to first non-empty region 1078 | for (++this->region_idx; this->region_idx < this->roi.size_regions() && this->roi.get_nlinfo(this->region_idx).empty(); ++this->region_idx) { } 1079 | if (this->region_idx == this->roi.size_regions()) { 1080 | // All regions are empty - set nlinfo incrementor to the end 1081 | // incrementor of the last region in order to match the end 1082 | // incrementor 1083 | this->nlinfo_inc = this->roi.get_nlinfo(this->region_idx-1).end_inc(); 1084 | } else { 1085 | this->nlinfo_inc = this->roi.get_nlinfo(region_idx).begin_inc(); 1086 | } 1087 | } 1088 | } 1089 | 1090 | // Arithmetic methods ----------------------------------------------------// 1091 | ROI2D_incrementor& ROI2D_incrementor::operator++() { 1092 | if (nlinfo_inc.pos_2D().first == roi.get_nlinfo(region_idx).last_pos_p1() && nlinfo_inc.pos_2D().second == roi.get_nlinfo(region_idx).last_pos_p2()) { 1093 | // Reached last position in this region, increment region_idx until 1094 | // nonempty region is found - or until all regions have been checked 1095 | for (++region_idx; region_idx < roi.size_regions() && roi.get_nlinfo(region_idx).empty(); ++region_idx) { } 1096 | if (region_idx == roi.size_regions()) { 1097 | // This is the end, set nlinfo_inc to end_incrementor of last 1098 | // region to match the end incrementor. 1099 | nlinfo_inc = roi.get_nlinfo(region_idx-1).end_inc(); 1100 | } else { 1101 | nlinfo_inc = roi.get_nlinfo(region_idx).begin_inc(); 1102 | } 1103 | } else { 1104 | ++nlinfo_inc; 1105 | } 1106 | 1107 | return *this; 1108 | } 1109 | 1110 | // Additional Constructors -----------------------------------------------// 1111 | ROI2D_contig_subregion_generator::ROI2D_contig_subregion_generator(const ROI2D &roi, SUBREGION subregion_type, difference_type r) : roi(roi), r(r) { 1112 | // Find max height of nodelist in roi so nodelist can be set. 1113 | difference_type max_height = 0; 1114 | for (difference_type region_idx = 0; region_idx < this->roi.size_regions(); ++region_idx) { 1115 | if (this->roi.get_nlinfo(region_idx).nodelist.height() > max_height) { 1116 | max_height = this->roi.get_nlinfo(region_idx).nodelist.height(); 1117 | } 1118 | } 1119 | 1120 | // Set buffers. These buffers are the upperbound for the sizes, so they 1121 | // never need to be resized when forming contiguous region. 1122 | this->nlinfo_subregion.nodelist = Array2D(max_height, 2*this->r + 1); 1123 | this->nlinfo_subregion.noderange = Array2D(1, 2*this->r + 1); 1124 | this->active_nodepairs = Array2D(max_height/2, 2*this->r + 1); 1125 | 1126 | // Note that simple_nlinfo must be 'simple' (look at the requirements 1127 | // for a 'simple' ROI at the simple_* static factory method definitions). 1128 | switch (subregion_type) { 1129 | case SUBREGION::CIRCLE: 1130 | this->nlinfo_simple = ROI2D::simple_circle(r).get_nlinfo(0); // Safe since ROI is simple 1131 | break; 1132 | case SUBREGION::SQUARE: 1133 | this->nlinfo_simple = ROI2D::simple_square(r).get_nlinfo(0); // Safe since ROI is simple 1134 | break; 1135 | } 1136 | } 1137 | 1138 | // Local function --------------------------------------------------------// 1139 | void simple_contig_subregion_add_interacting_nodes(ROI2D::difference_type np_adj_p2, 1140 | ROI2D::difference_type np_loaded_top, 1141 | ROI2D::difference_type np_loaded_bottom, 1142 | const ROI2D::ROI2D::region_nlinfo &nlinfo_roi, 1143 | const ROI2D::ROI2D::region_nlinfo &nlinfo_simple, 1144 | Array2D &active_nodepairs, 1145 | std::stack &queue_np_idx) { 1146 | typedef ROI2D::difference_type difference_type; 1147 | 1148 | // Make sure idx's are in range of both nlinfo_roi and nlinfo_simple 1149 | difference_type nl_adj_idx = np_adj_p2 - nlinfo_roi.left_nl; 1150 | difference_type nl_simple_idx = np_adj_p2 - nlinfo_simple.left_nl; 1151 | if (nlinfo_roi.noderange.in_bounds(nl_adj_idx) && nlinfo_simple.noderange.in_bounds(nl_simple_idx)) { 1152 | // Get simple nodepair at adjacent position - safe since nlinfo is 'simple' 1153 | difference_type np_simple_top = nlinfo_simple.nodelist(0, nl_simple_idx); 1154 | difference_type np_simple_bottom = nlinfo_simple.nodelist(1, nl_simple_idx); 1155 | // Scans nodes from top to bottom 1156 | for (difference_type np_adj_idx = 0; np_adj_idx < nlinfo_roi.noderange(nl_adj_idx); np_adj_idx += 2) { 1157 | difference_type np_adj_top = nlinfo_roi.nodelist(np_adj_idx, nl_adj_idx); 1158 | difference_type np_adj_bottom = nlinfo_roi.nodelist(np_adj_idx + 1, nl_adj_idx); 1159 | if (np_loaded_bottom < np_adj_top || np_loaded_bottom < np_simple_top) { 1160 | return; // top node of adjacent nodepair or simple nodepair is below bottom node of loaded nodepair 1161 | } 1162 | if (active_nodepairs(np_adj_idx/2, nl_simple_idx) && np_loaded_top <= np_adj_bottom && np_loaded_top <= np_simple_bottom) { 1163 | // At this point, loaded nodepair interacts with both adjacent 1164 | // nodepair and simple nodepair. Take the union of the 1165 | // intersection. Note that it is possible for adjacent nodepair 1166 | // and simple nodepair to be disjoint, which results in a 1167 | // "flipped" nodepair, so test for it. 1168 | difference_type np_top = std::max(np_adj_top, np_simple_top); 1169 | difference_type np_bottom = std::min(np_adj_bottom, np_simple_bottom); 1170 | if (np_top <= np_bottom) { 1171 | // Inactivate node pair, and then insert into the queue. 1172 | // Since simple_nlinfo only contains two nodes per column, 1173 | // a nodepair in nlinfo_roi can only interact with it 1174 | // once, so deactivating it is safe. 1175 | active_nodepairs(np_adj_idx/2, nl_simple_idx) = false; 1176 | queue_np_idx.push(np_top); 1177 | queue_np_idx.push(np_bottom); 1178 | queue_np_idx.push(np_adj_p2); 1179 | } 1180 | } 1181 | } 1182 | } 1183 | } 1184 | 1185 | // Arithmetic methods ----------------------------------------------------// 1186 | const ROI2D::region_nlinfo& ROI2D_contig_subregion_generator::operator()(difference_type p1, difference_type p2) const { 1187 | // Clear/initialize values in nlinfo_output - must do this here in case 1188 | // empty nlinfo_output is returned 1189 | nlinfo_subregion.top = roi.height()-1; // Gets updated 1190 | nlinfo_subregion.bottom = 0; // Gets updated 1191 | nlinfo_subregion.left = roi.width()-1; // Gets updated 1192 | nlinfo_subregion.right = 0; // Gets updated 1193 | nlinfo_subregion.left_nl = p2 - r; // Correct 1194 | nlinfo_subregion.right_nl = p2 + r; // Correct 1195 | nlinfo_subregion.noderange() = 0; // Gets updated 1196 | nlinfo_subregion.points = 0; // Gets updated 1197 | 1198 | // Get region idx containing (p1,p2) 1199 | auto region_idx_pair = roi.get_region_idx(p1, p2); 1200 | if (region_idx_pair.first == -1) { 1201 | // ROI does not contain the x,y coordinate - return the empty nlinfo_output 1202 | return nlinfo_subregion; 1203 | } 1204 | 1205 | // Set active_nodepairs to true 1206 | active_nodepairs() = true; 1207 | 1208 | // Get nlinfo corresponding to p1 and p2 1209 | auto &nlinfo_roi = roi.get_nlinfo(region_idx_pair.first); 1210 | 1211 | // Shift nlinfo_simple's position in-place 1212 | nlinfo_simple.shift(p1 - r, p2 - r); 1213 | 1214 | // Get node pairs containing x and y, take their union with simple 1215 | // nodepair, and then add to queue. 1216 | active_nodepairs(region_idx_pair.second/2, r) = false; // Inactivate nodepair 1217 | std::stack queue_np_idx; // Holds all nodepairs (along with their index) which need to be processed 1218 | queue_np_idx.push(std::max(nlinfo_simple.nodelist(0, r), nlinfo_roi.nodelist(region_idx_pair.second, p2 - nlinfo_roi.left_nl))); // Top 1219 | queue_np_idx.push(std::min(nlinfo_simple.nodelist(1, r), nlinfo_roi.nodelist(region_idx_pair.second + 1, p2 - nlinfo_roi.left_nl))); // Bottom 1220 | queue_np_idx.push(p2); // idx 1221 | while (!queue_np_idx.empty()) { 1222 | // Pop nodepair and its position out of queue and compare 1223 | // it to adjacent nodepairs (left and right of np_loaded_p2) 1224 | difference_type np_loaded_p2 = queue_np_idx.top(); queue_np_idx.pop(); 1225 | difference_type np_loaded_bottom = queue_np_idx.top(); queue_np_idx.pop(); 1226 | difference_type np_loaded_top = queue_np_idx.top(); queue_np_idx.pop(); 1227 | 1228 | // Compare to node pairs LEFT. Any node pairs which interact are added to the queue 1229 | details::simple_contig_subregion_add_interacting_nodes(np_loaded_p2 - 1, 1230 | np_loaded_top, 1231 | np_loaded_bottom, 1232 | nlinfo_roi, 1233 | nlinfo_simple, 1234 | active_nodepairs, 1235 | queue_np_idx); 1236 | 1237 | // Compare to node pairs RIGHT. Any node pairs which interact are added to the queue 1238 | details::simple_contig_subregion_add_interacting_nodes(np_loaded_p2 + 1, 1239 | np_loaded_top, 1240 | np_loaded_bottom, 1241 | nlinfo_roi, 1242 | nlinfo_simple, 1243 | active_nodepairs, 1244 | queue_np_idx); 1245 | 1246 | // Update points 1247 | nlinfo_subregion.points += np_loaded_bottom - np_loaded_top + 1; 1248 | 1249 | // Update bounds 1250 | if (np_loaded_top < nlinfo_subregion.top) { nlinfo_subregion.top = np_loaded_top; } // Top 1251 | if (np_loaded_bottom > nlinfo_subregion.bottom) { nlinfo_subregion.bottom = np_loaded_bottom; } // Bottom 1252 | if (np_loaded_p2 < nlinfo_subregion.left) { nlinfo_subregion.left = np_loaded_p2; } // Left 1253 | if (np_loaded_p2 > nlinfo_subregion.right) { nlinfo_subregion.right = np_loaded_p2; } // Right 1254 | 1255 | // Insert node pairs and then sort 1256 | difference_type nl_output_idx = np_loaded_p2 - p2 + r; 1257 | nlinfo_subregion.nodelist(nlinfo_subregion.noderange(nl_output_idx), nl_output_idx) = np_loaded_top; 1258 | nlinfo_subregion.nodelist(nlinfo_subregion.noderange(nl_output_idx) + 1, nl_output_idx) = np_loaded_bottom; 1259 | std::sort(&nlinfo_subregion.nodelist(0, nl_output_idx), &nlinfo_subregion.nodelist(0, nl_output_idx) + nlinfo_subregion.noderange(nl_output_idx) + 2); 1260 | 1261 | // Update noderange 1262 | nlinfo_subregion.noderange(nl_output_idx) += 2; 1263 | } 1264 | 1265 | // Shift nlinfo_simple's position back to original place 1266 | nlinfo_simple.shift(r - p1, r - p2); 1267 | 1268 | return nlinfo_subregion; 1269 | } 1270 | } 1271 | 1272 | } -------------------------------------------------------------------------------- /src/Strain2D.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * File: Strain2D.cpp 3 | * Author: justin 4 | * 5 | * Created on June 7, 2015, 10:14 PM 6 | */ 7 | 8 | #include "Strain2D.h" 9 | 10 | namespace ncorr { 11 | 12 | // Static factory methods ----------------------------------------------------// 13 | Strain2D Strain2D::load(std::ifstream &is) { 14 | // Form empty Strain2D then fill in values in accordance to how they are saved 15 | Strain2D strain; 16 | 17 | // Load eyy 18 | strain.eyy = Data2D::load(is); 19 | 20 | // Load exy 21 | strain.exy = Data2D::load(is); 22 | 23 | // Load exx 24 | strain.exx = Data2D::load(is); 25 | 26 | return strain; 27 | } 28 | 29 | // Operators interface -------------------------------------------------------// 30 | std::ostream& operator<<(std::ostream &os, const Strain2D &strain) { 31 | os << "Eyy data: " << '\n' << strain.get_eyy(); 32 | os << '\n' << "Exy data: " << '\n' << strain.get_exy(); 33 | os << '\n' << "Exx data: " << '\n' << strain.get_exx(); 34 | 35 | return os; 36 | } 37 | 38 | void imshow(const Strain2D &strain, Strain2D::difference_type delay) { 39 | // Just show each separately for now. If you combine into one buffer, you 40 | // must scale each as their ranges might be different. 41 | imshow(strain.eyy, delay); 42 | imshow(strain.exy, delay); 43 | imshow(strain.exx, delay); 44 | } 45 | 46 | bool isequal(const Strain2D &strain1, const Strain2D &strain2) { 47 | return isequal(strain1.eyy, strain2.eyy) && isequal(strain1.exy, strain2.exy) && isequal(strain1.exx, strain2.exx); 48 | } 49 | 50 | void save(const Strain2D &strain, std::ofstream &os) { 51 | // Save eyy -> exy -> exx 52 | save(strain.eyy, os); 53 | save(strain.exy, os); 54 | save(strain.exx, os); 55 | } 56 | 57 | // Interpolator --------------------------------------------------------------// 58 | Strain2D::nlinfo_interpolator Strain2D::get_nlinfo_interpolator(difference_type region_idx, INTERP interp_type) const { 59 | return details::Strain2D_nlinfo_interpolator(*this, region_idx, interp_type); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /test/bin/images/ohtcfrp_00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justinblaber/ncorr_2D_cpp/d645565b5e34acba723658cb585e2152a61f1c5f/test/bin/images/ohtcfrp_00.png -------------------------------------------------------------------------------- /test/bin/images/ohtcfrp_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justinblaber/ncorr_2D_cpp/d645565b5e34acba723658cb585e2152a61f1c5f/test/bin/images/ohtcfrp_01.png -------------------------------------------------------------------------------- /test/bin/images/ohtcfrp_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justinblaber/ncorr_2D_cpp/d645565b5e34acba723658cb585e2152a61f1c5f/test/bin/images/ohtcfrp_02.png -------------------------------------------------------------------------------- /test/bin/images/ohtcfrp_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justinblaber/ncorr_2D_cpp/d645565b5e34acba723658cb585e2152a61f1c5f/test/bin/images/ohtcfrp_03.png -------------------------------------------------------------------------------- /test/bin/images/ohtcfrp_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justinblaber/ncorr_2D_cpp/d645565b5e34acba723658cb585e2152a61f1c5f/test/bin/images/ohtcfrp_04.png -------------------------------------------------------------------------------- /test/bin/images/ohtcfrp_05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justinblaber/ncorr_2D_cpp/d645565b5e34acba723658cb585e2152a61f1c5f/test/bin/images/ohtcfrp_05.png -------------------------------------------------------------------------------- /test/bin/images/ohtcfrp_06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justinblaber/ncorr_2D_cpp/d645565b5e34acba723658cb585e2152a61f1c5f/test/bin/images/ohtcfrp_06.png -------------------------------------------------------------------------------- /test/bin/images/ohtcfrp_07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justinblaber/ncorr_2D_cpp/d645565b5e34acba723658cb585e2152a61f1c5f/test/bin/images/ohtcfrp_07.png -------------------------------------------------------------------------------- /test/bin/images/ohtcfrp_08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justinblaber/ncorr_2D_cpp/d645565b5e34acba723658cb585e2152a61f1c5f/test/bin/images/ohtcfrp_08.png -------------------------------------------------------------------------------- /test/bin/images/ohtcfrp_09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justinblaber/ncorr_2D_cpp/d645565b5e34acba723658cb585e2152a61f1c5f/test/bin/images/ohtcfrp_09.png -------------------------------------------------------------------------------- /test/bin/images/ohtcfrp_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justinblaber/ncorr_2D_cpp/d645565b5e34acba723658cb585e2152a61f1c5f/test/bin/images/ohtcfrp_10.png -------------------------------------------------------------------------------- /test/bin/images/ohtcfrp_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justinblaber/ncorr_2D_cpp/d645565b5e34acba723658cb585e2152a61f1c5f/test/bin/images/ohtcfrp_11.png -------------------------------------------------------------------------------- /test/bin/images/roi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justinblaber/ncorr_2D_cpp/d645565b5e34acba723658cb585e2152a61f1c5f/test/bin/images/roi.png -------------------------------------------------------------------------------- /test/build/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | CMAKE_MINIMUM_REQUIRED(VERSION 3.2) 2 | PROJECT(ncorr_test) 3 | 4 | # Only tested for g++ on Ubuntu 12.04. This assumes all required libraries have been 5 | # installed, so directories to dependent libraries and their headers are not explicitly 6 | # included, since the install directories are searched automatically by g++. 7 | 8 | # Set output for executable 9 | SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ../bin) 10 | 11 | # Add executable 12 | ADD_EXECUTABLE(ncorr_test ../src/ncorr_test.cpp) 13 | 14 | # Set C++11 support 15 | set_property(TARGET ncorr_test PROPERTY CXX_STANDARD 11) 16 | set_property(TARGET ncorr_test PROPERTY CXX_STANDARD_REQUIRED ON) 17 | 18 | # Set -03 optimization 19 | INCLUDE(CheckCXXCompilerFlag) 20 | CHECK_CXX_COMPILER_FLAG("-O3" COMPILER_SUPPORTS_O3) 21 | if (COMPILER_SUPPORTS_O3) 22 | SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3") 23 | endif() 24 | 25 | # Disable debugging 26 | ADD_DEFINITIONS(-DNDEBUG) 27 | 28 | # Add ncorr library 29 | FIND_LIBRARY(NCORR_LIBRARY NAMES ncorr) 30 | TARGET_LINK_LIBRARIES(ncorr_test ${NCORR_LIBRARY}) 31 | 32 | # Add opencv libraries 33 | FIND_LIBRARY(OPENCV_LIBRARY1 NAMES opencv_core) 34 | TARGET_LINK_LIBRARIES(ncorr_test ${OPENCV_LIBRARY1}) 35 | 36 | FIND_LIBRARY(OPENCV_LIBRARY2 NAMES opencv_highgui) 37 | TARGET_LINK_LIBRARIES(ncorr_test ${OPENCV_LIBRARY2}) 38 | 39 | FIND_LIBRARY(OPENCV_LIBRARY3 NAMES opencv_imgcodecs) 40 | TARGET_LINK_LIBRARIES(ncorr_test ${OPENCV_LIBRARY3}) 41 | 42 | FIND_LIBRARY(OPENCV_LIBRARY4 NAMES opencv_imgproc) 43 | TARGET_LINK_LIBRARIES(ncorr_test ${OPENCV_LIBRARY4}) 44 | 45 | FIND_LIBRARY(OPENCV_LIBRARY5 NAMES opencv_videoio) 46 | TARGET_LINK_LIBRARIES(ncorr_test ${OPENCV_LIBRARY5}) 47 | 48 | # Add fftw library 49 | FIND_LIBRARY(FFTW_LIBRARY NAMES fftw3) 50 | TARGET_LINK_LIBRARIES(ncorr_test ${FFTW_LIBRARY}) 51 | 52 | # Add suite sparse libraries 53 | FIND_LIBRARY(SUITESPARSE_LIBRARY1 NAMES spqr) 54 | TARGET_LINK_LIBRARIES(ncorr_test ${SUITESPARSE_LIBRARY1}) 55 | 56 | FIND_LIBRARY(SUITESPARSE_LIBRARY2 NAMES cholmod) 57 | TARGET_LINK_LIBRARIES(ncorr_test ${SUITESPARSE_LIBRARY2}) 58 | 59 | FIND_LIBRARY(SUITESPARSE_LIBRARY3 NAMES suitesparseconfig) 60 | TARGET_LINK_LIBRARIES(ncorr_test ${SUITESPARSE_LIBRARY3}) 61 | 62 | FIND_LIBRARY(SUITESPARSE_LIBRARY4 NAMES amd) 63 | TARGET_LINK_LIBRARIES(ncorr_test ${SUITESPARSE_LIBRARY4}) 64 | 65 | FIND_LIBRARY(SUITESPARSE_LIBRARY5 NAMES colamd) 66 | TARGET_LINK_LIBRARIES(ncorr_test ${SUITESPARSE_LIBRARY5}) 67 | 68 | # Add lapack library 69 | FIND_LIBRARY(LAPACK_LIBRARY NAMES lapack) 70 | TARGET_LINK_LIBRARIES(ncorr_test ${LAPACK_LIBRARY}) 71 | 72 | # Add blas library 73 | FIND_LIBRARY(BLAS_LIBRARY NAMES blas) 74 | TARGET_LINK_LIBRARIES(ncorr_test ${BLAS_LIBRARY}) 75 | 76 | # Add gfortran library (required from blas) 77 | FIND_LIBRARY(GFORTRAN_LIBRARY NAMES gfortran) 78 | TARGET_LINK_LIBRARIES(ncorr_test ${GFORTRAN_LIBRARY}) 79 | 80 | # Add pthreads 81 | FIND_PACKAGE(Threads) 82 | TARGET_LINK_LIBRARIES(ncorr_test ${CMAKE_THREAD_LIBS_INIT}) 83 | -------------------------------------------------------------------------------- /test/src/ncorr_test.cpp: -------------------------------------------------------------------------------- 1 | #include "ncorr.h" 2 | 3 | using namespace ncorr; 4 | 5 | int main(int argc, char *argv[]) { 6 | if (argc != 2) { 7 | throw std::invalid_argument("Must have 1 command line input of either 'calculate' or 'load'"); 8 | } 9 | 10 | // Initialize DIC and strain information ---------------// 11 | DIC_analysis_input DIC_input; 12 | DIC_analysis_output DIC_output; 13 | strain_analysis_input strain_input; 14 | strain_analysis_output strain_output; 15 | 16 | // Determine whether or not to perform calculations or 17 | // load data (only load data if analysis has already 18 | // been done and saved or else throw an exception). 19 | std::string input(argv[1]); 20 | if (input == "load") { 21 | // Load inputs 22 | DIC_input = DIC_analysis_input::load("save/DIC_input.bin"); 23 | DIC_output = DIC_analysis_output::load("save/DIC_output.bin"); 24 | strain_input = strain_analysis_input::load("save/strain_input.bin"); 25 | strain_output = strain_analysis_output::load("save/strain_output.bin"); 26 | } else if (input == "calculate") { 27 | // Set images 28 | std::vector imgs; 29 | for (int i = 0; i <= 11; ++i) { 30 | std::ostringstream ostr; 31 | ostr << "images/ohtcfrp_" << std::setfill('0') << std::setw(2) << i << ".png"; 32 | imgs.push_back(ostr.str()); 33 | } 34 | 35 | // Set DIC_input 36 | DIC_input = DIC_analysis_input(imgs, // Images 37 | ROI2D(Image2D("images/roi.png").get_gs() > 0.5), // ROI 38 | 3, // scalefactor 39 | INTERP::QUINTIC_BSPLINE_PRECOMPUTE, // Interpolation 40 | SUBREGION::CIRCLE, // Subregion shape 41 | 20, // Subregion radius 42 | 4, // # of threads 43 | DIC_analysis_config::NO_UPDATE, // DIC configuration for reference image updates 44 | true); // Debugging enabled/disabled 45 | 46 | // Perform DIC_analysis 47 | DIC_output = DIC_analysis(DIC_input); 48 | 49 | // Convert DIC_output to Eulerian perspective 50 | DIC_output = change_perspective(DIC_output, INTERP::QUINTIC_BSPLINE_PRECOMPUTE); 51 | 52 | // Set units of DIC_output (provide units/pixel) 53 | DIC_output = set_units(DIC_output, "mm", 0.2); 54 | 55 | // Set strain input 56 | strain_input = strain_analysis_input(DIC_input, 57 | DIC_output, 58 | SUBREGION::CIRCLE, // Strain subregion shape 59 | 5); // Strain subregion radius 60 | 61 | // Perform strain_analysis 62 | strain_output = strain_analysis(strain_input); 63 | 64 | // Save outputs as binary 65 | save(DIC_input, "save/DIC_input.bin"); 66 | save(DIC_output, "save/DIC_output.bin"); 67 | save(strain_input, "save/strain_input.bin"); 68 | save(strain_output, "save/strain_output.bin"); 69 | } else { 70 | throw std::invalid_argument("Input of " + input + " is not recognized. Must be either 'calculate' or 'load'"); 71 | } 72 | 73 | // Create Videos ---------------------------------------// 74 | // Note that more inputs can be used to modify plots. 75 | // If video is not saving correctly, try changing the 76 | // input codec using cv::VideoWriter::fourcc(...)). Check 77 | // the opencv documentation on video codecs. By default, 78 | // ncorr uses cv::VideoWriter::fourcc('M','J','P','G')). 79 | save_DIC_video("video/test_v_eulerian.avi", 80 | DIC_input, 81 | DIC_output, 82 | DISP::V, 83 | 0.5, // Alpha 84 | 15); // FPS 85 | 86 | save_DIC_video("video/test_u_eulerian.avi", 87 | DIC_input, 88 | DIC_output, 89 | DISP::U, 90 | 0.5, // Alpha 91 | 15); // FPS 92 | 93 | save_strain_video("video/test_eyy_eulerian.avi", 94 | strain_input, 95 | strain_output, 96 | STRAIN::EYY, 97 | 0.5, // Alpha 98 | 15); // FPS 99 | 100 | save_strain_video("video/test_exy_eulerian.avi", 101 | strain_input, 102 | strain_output, 103 | STRAIN::EXY, 104 | 0.5, // Alpha 105 | 15); // FPS 106 | 107 | save_strain_video("video/test_exx_eulerian.avi", 108 | strain_input, 109 | strain_output, 110 | STRAIN::EXX, 111 | 0.5, // Alpha 112 | 15); // FPS 113 | 114 | return 0; 115 | } 116 | --------------------------------------------------------------------------------