CppNCorr
C++ ncorr Digital Image Correlation engine
Loading...
Searching...
No Matches
ROI2D.cpp
Go to the documentation of this file.
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
10namespace ncorr {
11
12// Additional Constructors ---------------------------------------------------//
13ROI2D::ROI2D(Array2D<bool> mask, difference_type cutoff) : mask_ptr(std::make_shared<Array2D<bool>>(std::move(mask))), regions_ptr(std::make_shared<std::vector<region>>()) {
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<bool> 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
39ROI2D::ROI2D(region_nlinfo nlinfo, difference_type h, difference_type w) : mask_ptr(std::make_shared<Array2D<bool>>(h,w)), regions_ptr(std::make_shared<std::vector<region>>()) {
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
53ROI2D::ROI2D(region_boundary boundary, difference_type h, difference_type w) : mask_ptr(std::make_shared<Array2D<bool>>(h,w)), regions_ptr(std::make_shared<std::vector<region>>()) {
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
67ROI2D::ROI2D(std::vector<region_nlinfo> nlinfos, difference_type h, difference_type w) : mask_ptr(std::make_shared<Array2D<bool>>(h,w)), regions_ptr(std::make_shared<std::vector<region>>()) {
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
83ROI2D::ROI2D(std::vector<region_boundary> boundaries, difference_type h, difference_type w) : mask_ptr(std::make_shared<Array2D<bool>>(h,w)), regions_ptr(std::make_shared<std::vector<region>>()) {
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.
107 difference_type length = 2*r + 1;
108 double r_squared = std::pow(r,2);
109
110 Array2D<bool> 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
124 Array2D<bool> mask(2*r+1, 2*r+1, true);
125
126 return ROI2D(std::move(mask));
127}
128
129ROI2D 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<bool>>(Array2D<bool>::load(is));
135
136 // Load regions
137 difference_type num_regions = 0;
138 is.read(reinterpret_cast<char*>(&num_regions), std::streamsize(sizeof(difference_type)));
139 roi.regions_ptr = std::make_shared<std::vector<ROI2D::region>>(num_regions);
140 for (auto &reg : *roi.regions_ptr) {
141 reg = ROI2D::region::load(is);
142 }
143
144 // Load points
145 is.read(reinterpret_cast<char*>(&roi.points), std::streamsize(sizeof(difference_type)));
146
147 return roi;
148}
149
150// Operators Interface -------------------------------------------------------//
151std::ostream& operator<<(std::ostream &os, const ROI2D &roi) {
152 os << "Mask :" << '\n';
153 os << *roi.mask_ptr;
154
155 for (const auto &region : *roi.regions_ptr) {
156 os << region << '\n';
157 }
158
159 os << "Total points: " << roi.points;
160
161 return os;
162}
163
164bool isequal(const ROI2D &roi1, const ROI2D &roi2) {
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
182void save(const ROI2D &roi, std::ofstream &os) {
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<const char*>(&num_regions), std::streamsize(sizeof(difference_type)));
190 for (const auto &reg : *roi.regions_ptr) {
191 save(reg, os);
192 }
193
194 os.write(reinterpret_cast<const char*>(&roi.points), std::streamsize(sizeof(difference_type)));
195}
196
197// Access --------------------------------------------------------------------//
199 chk_region_idx_in_bounds(region_idx, "get_nlinfo()");
200
201 return (*regions_ptr)[region_idx].nlinfo;
202}
203
205 chk_region_idx_in_bounds(region_idx, "get_boundary()");
206
207 return (*regions_ptr)[region_idx].boundary;
208}
209
210std::pair<ROI2D::difference_type,ROI2D::difference_type> 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 ------------------------------------------------------//
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<region_nlinfo> nlinfos_reduced;
250 Array2D<bool> 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
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<region_nlinfo> nlinfos_unioned;
270 Array2D<bool> 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 ---------------------------------------------------------------//
280 return (regions_ptr->empty() ? incrementor(*this, 0, { }) : incrementor(*this, 0, regions_ptr->front().nlinfo.begin_inc()));
281}
282
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 ------------------------------------------------//
291
292// Utility methods -----------------------------------------------------------//
293void ROI2D::set_points() {
294 points = 0;
295 for (const auto &region : *regions_ptr) {
296 points += region.nlinfo.points;
297 }
298}
299
300void ROI2D::draw_mask() {
301 (*this->mask_ptr)() = false;
302 for (const auto &region : *regions_ptr) {
303 fill(*this->mask_ptr, region.nlinfo, true);
304 }
305}
306
307// Checks --------------------------------------------------------------------//
308void 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 ----------------------------------------------------//
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<char*>(&nlinfo.top), std::streamsize(sizeof(difference_type)));
321 is.read(reinterpret_cast<char*>(&nlinfo.bottom), std::streamsize(sizeof(difference_type)));
322 is.read(reinterpret_cast<char*>(&nlinfo.left), std::streamsize(sizeof(difference_type)));
323 is.read(reinterpret_cast<char*>(&nlinfo.right), std::streamsize(sizeof(difference_type)));
324 is.read(reinterpret_cast<char*>(&nlinfo.left_nl), std::streamsize(sizeof(difference_type)));
325 is.read(reinterpret_cast<char*>(&nlinfo.right_nl), std::streamsize(sizeof(difference_type)));
326
327 // Load nodelist and noderange
330
331 // Load points
332 is.read(reinterpret_cast<char*>(&nlinfo.points), std::streamsize(sizeof(difference_type)));
333
334 return nlinfo;
335}
336
337// Operations interface ------------------------------------------------------//
338std::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
357bool 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
369void save(const ROI2D::region_nlinfo &nlinfo, std::ofstream &os) {
371
372 // Save bounds -> nodelist -> noderange -> points
373 os.write(reinterpret_cast<const char*>(&nlinfo.top), std::streamsize(sizeof(difference_type)));
374 os.write(reinterpret_cast<const char*>(&nlinfo.bottom), std::streamsize(sizeof(difference_type)));
375 os.write(reinterpret_cast<const char*>(&nlinfo.left), std::streamsize(sizeof(difference_type)));
376 os.write(reinterpret_cast<const char*>(&nlinfo.right), std::streamsize(sizeof(difference_type)));
377 os.write(reinterpret_cast<const char*>(&nlinfo.left_nl), std::streamsize(sizeof(difference_type)));
378 os.write(reinterpret_cast<const char*>(&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<const char*>(&nlinfo.points), std::streamsize(sizeof(difference_type)));
384}
385
386// Arithmetic methods --------------------------------------------------------//
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
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 ---------------------------------------------------------------//
428 return (empty() ? incrementor(*this, 0, 0, 0) : incrementor(*this, first_pos_idx(), 0, first_pos_p1()));
429}
430
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 ----------------------------------------------------//
436namespace details {
438 ROI2D::difference_type np_loaded_top,
439 ROI2D::difference_type np_loaded_bottom,
440 Array2D<std::vector<ROI2D::difference_type>> &overall_nodelist,
441 Array2D<std::vector<bool>> &overall_active_nodepairs,
442 std::stack<ROI2D::difference_type> &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
466std::pair<std::vector<ROI2D::region_nlinfo>,bool> ROI2D::region_nlinfo::form_nlinfos(const Array2D<bool> &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<std::vector<difference_type>> overall_nodelist(1,mask.width());
474 Array2D<std::vector<bool>> 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<ROI2D::region_nlinfo> 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<std::vector<difference_type>> separate_nodelist(1,mask.width());
528
529 // Initialize queue and enter while loop - exit when queue is empty
530 std::stack<difference_type> 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<difference_type>(max_nodes, nlinfo_buf.right_nl - nlinfo_buf.left_nl + 1);
584 nlinfo_buf.noderange = Array2D<difference_type>(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 --------------------------------------------------------//
612ROI2D::region_nlinfo ROI2D::region_nlinfo::reduce(difference_type scalefactor, Array2D<bool> &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
672ROI2D::region_nlinfo ROI2D::region_nlinfo::form_union(const Array2D<bool> &mask, Array2D<bool> &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<std::vector<difference_type>> 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<difference_type>(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
754namespace details {
755 Array2D<double> calc_boundary(const Array2D<bool> &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<double> 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<double> 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
850ROI2D::region_nlinfo ROI2D::region_nlinfo::largest_contig_nlinfo(Array2D<bool> &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
866ROI2D::region_boundary ROI2D::region_nlinfo::to_boundary(Array2D<bool> &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<bool> 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<Array2D<double>> 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 -------------------------------------------------------------------//
919void 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 ----------------------------------------------------//
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<double>::load(is);
933
934 // Allocate new sub boundaries and then load them
935 difference_type num_sub_boundaries = 0;
936 is.read(reinterpret_cast<char*>(&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<double>::load(is);
940 }
941
942 return boundary;
943}
944
945// Operations interface ------------------------------------------------------//
946std::ostream& operator<<(std::ostream &os, const ROI2D::region_boundary &boundary) {
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
957bool isequal(const ROI2D::region_boundary &boundary1, const ROI2D::region_boundary &boundary2) {
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
973void save(const ROI2D::region_boundary &boundary, std::ofstream &os) {
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<const char*>(&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 --------------------------------------------------------//
989ROI2D::region_nlinfo ROI2D::region_boundary::to_nlinfo(Array2D<bool> &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 ----------------------------------------------------//
1016 // Form empty region then fill in values in accordance to how they are saved
1017 region reg;
1018
1019 // Load nlinfo
1021
1022 // Load boundary
1024
1025 return reg;
1026}
1027
1028// Operations interface ------------------------------------------------------//
1029std::ostream& operator<<(std::ostream &os, const ROI2D::region &reg) {
1030 os << reg.nlinfo;
1031 os << reg.boundary;
1032
1033 return os;
1034}
1035
1036void save(const ROI2D::region &reg, std::ofstream &os) {
1037 // Save nlinfo -> boundary
1038 save(reg.nlinfo, os);
1039 save(reg.boundary, os);
1040}
1041
1042namespace 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 ----------------------------------------------------//
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 -----------------------------------------------//
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<difference_type>(max_height, 2*this->r + 1);
1123 this->nlinfo_subregion.noderange = Array2D<difference_type>(1, 2*this->r + 1);
1124 this->active_nodepairs = Array2D<bool>(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 --------------------------------------------------------//
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<bool> &active_nodepairs,
1145 std::stack<ROI2D::difference_type> &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 ----------------------------------------------------//
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<difference_type> 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
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
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}
difference_type width() const
Definition Array2D.h:410
std::string size_2D_string() const
Definition Array2D.h:423
static std::enable_if< std::is_arithmetic< T >::value, T_output >::type load(const std::string &)
Definition Array2D.h:1931
difference_type height() const
Definition Array2D.h:409
difference_type size() const
Definition Array2D.h:411
bool in_bounds(difference_type p) const
Definition Array2D.h:420
friend void save(const ROI2D &, std::ofstream &)
Definition ROI2D.cpp:182
bool empty() const
Definition ROI2D.h:73
contig_subregion_generator get_contig_subregion_generator(SUBREGION, difference_type) const
Definition ROI2D.cpp:288
std::ptrdiff_t difference_type
Definition ROI2D.h:31
ROI2D reduce(difference_type) const
Definition ROI2D.cpp:238
difference_type size_regions() const
Definition ROI2D.h:85
difference_type width() const
Definition ROI2D.h:72
friend std::ostream & operator<<(std::ostream &, const ROI2D &)
Definition ROI2D.cpp:151
details::ROI2D_incrementor incrementor
Definition ROI2D.h:33
incrementor end_inc() const
Definition ROI2D.cpp:283
static ROI2D simple_square(difference_type)
Definition ROI2D.cpp:123
incrementor begin_inc() const
Definition ROI2D.cpp:279
const region_nlinfo & get_nlinfo(difference_type) const
Definition ROI2D.cpp:198
friend bool isequal(const ROI2D &, const ROI2D &)
Definition ROI2D.cpp:164
static ROI2D load(std::ifstream &)
Definition ROI2D.cpp:129
static ROI2D simple_circle(difference_type)
Definition ROI2D.cpp:106
ROI2D form_union(const Array2D< bool > &) const
Definition ROI2D.cpp:258
details::ROI2D_contig_subregion_generator contig_subregion_generator
Definition ROI2D.h:34
difference_type height() const
Definition ROI2D.h:71
std::pair< difference_type, difference_type > get_region_idx(difference_type, difference_type) const
Definition ROI2D.cpp:210
std::string size_2D_string() const
Definition ROI2D.h:101
const region_boundary & get_boundary(difference_type) const
Definition ROI2D.cpp:204
const ROI2D::region_nlinfo & operator()(difference_type, difference_type) const
Definition ROI2D.cpp:1186
ROI2D::difference_type difference_type
Definition ROI2D.h:304
ROI2D_incrementor & operator++()
Definition ROI2D.cpp:1091
void add_interacting_nodes(ROI2D::difference_type np_adj_p2, ROI2D::difference_type np_loaded_top, ROI2D::difference_type np_loaded_bottom, Array2D< std::vector< ROI2D::difference_type > > &overall_nodelist, Array2D< std::vector< bool > > &overall_active_nodepairs, std::stack< ROI2D::difference_type > &queue_np_idx)
Definition ROI2D.cpp:437
void simple_contig_subregion_add_interacting_nodes(ROI2D::difference_type np_adj_p2, ROI2D::difference_type np_loaded_top, ROI2D::difference_type np_loaded_bottom, const ROI2D::ROI2D::region_nlinfo &nlinfo_roi, const ROI2D::ROI2D::region_nlinfo &nlinfo_simple, Array2D< bool > &active_nodepairs, std::stack< ROI2D::difference_type > &queue_np_idx)
Definition ROI2D.cpp:1139
Array2D< double > calc_boundary(const Array2D< bool > &mask, double init_p1, double init_p2, ROI2D::difference_type init_direc=0)
Definition ROI2D.cpp:755
bool isequal(const Data2D &data1, const Data2D &data2)
Definition Data2D.cpp:72
T_container & fill(T_container &A, const ROI2D::region_nlinfo &nlinfo, const T &val)
Definition ROI2D.h:422
Disp2D add(const std::vector< Disp2D > &, INTERP)
Definition ncorr.cpp:1126
SUBREGION
Definition ROI2D.h:27
void save(const Data2D &data, std::ofstream &os)
Definition Data2D.cpp:78
std::ostream & operator<<(std::ostream &os, const Data2D &data)
Definition Data2D.cpp:50
static region_boundary load(std::ifstream &)
Definition ROI2D.cpp:927
std::vector< Array2D< double > > sub
Definition ROI2D.h:228
Array2D< double > add
Definition ROI2D.h:227
difference_type top
Definition ROI2D.h:180
difference_type last_pos_p2() const
Definition ROI2D.h:412
incrementor begin_inc() const
Definition ROI2D.cpp:427
Array2D< difference_type > noderange
Definition ROI2D.h:187
difference_type points
Definition ROI2D.h:188
difference_type bottom
Definition ROI2D.h:181
bool in_nlinfo(difference_type, difference_type) const
Definition ROI2D.cpp:387
difference_type right
Definition ROI2D.h:183
difference_type last_pos_p1() const
Definition ROI2D.h:404
static region_nlinfo load(std::ifstream &)
Definition ROI2D.cpp:315
Array2D< difference_type > nodelist
Definition ROI2D.h:186
incrementor end_inc() const
Definition ROI2D.cpp:431
region_nlinfo & shift(difference_type, difference_type)
Definition ROI2D.cpp:405
difference_type left_nl
Definition ROI2D.h:184
static std::pair< std::vector< ROI2D::region_nlinfo >, bool > form_nlinfos(const Array2D< bool > &, ROI2D::difference_type=0)
Definition ROI2D.cpp:466
difference_type right_nl
Definition ROI2D.h:185
difference_type left
Definition ROI2D.h:182
static region load(std::ifstream &)
Definition ROI2D.cpp:1015
region_nlinfo nlinfo
Definition ROI2D.h:258
region_boundary boundary
Definition ROI2D.h:259