contrek 1.4.5 → 1.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/Gemfile.lock +1 -1
- data/README.md +28 -9
- data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.cpp +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/FinderUtils.cpp +1 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.cpp +23 -26
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.h +2 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.cpp +7 -4
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.h +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Polygon.h +1 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.cpp +24 -11
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.h +68 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cluster.cpp +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.cpp +8 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Finder.cpp +3 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/GeoJsonStreamingMerger.h +3 -5
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Merger.cpp +11 -30
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Merger.h +3 -3
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/MergerValidator.cpp +104 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/MergerValidator.h +35 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Polyline.cpp +3 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Polyline.h +3 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Shape.cpp +1 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/ShapePool.cpp +2 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/ShapePool.h +1 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Tile.cpp +19 -21
- data/ext/cpp_polygon_finder/cpp_polygon_finder.cpp +11 -3
- data/lib/contrek/cpp/cpp_geo_json_concurrent_streaming_merger.rb +2 -2
- data/lib/contrek/cpp/cpp_result.rb +2 -1
- data/lib/contrek/finder/concurrent/cluster.rb +1 -1
- data/lib/contrek/finder/concurrent/cursor.rb +3 -1
- data/lib/contrek/finder/concurrent/finder.rb +2 -1
- data/lib/contrek/finder/concurrent/geo_json_streaming_merger.rb +3 -4
- data/lib/contrek/finder/concurrent/merger.rb +6 -25
- data/lib/contrek/finder/concurrent/merger_validator.rb +96 -0
- data/lib/contrek/finder/concurrent/polyline.rb +3 -2
- data/lib/contrek/finder/concurrent/shape.rb +2 -1
- data/lib/contrek/finder/concurrent/streaming_merger.rb +1 -1
- data/lib/contrek/finder/concurrent/tile.rb +9 -3
- data/lib/contrek/finder/node.rb +13 -23
- data/lib/contrek/finder/node_cluster.rb +17 -5
- data/lib/contrek/finder/polygon_finder.rb +29 -1
- data/lib/contrek/version.rb +1 -1
- data/lib/contrek.rb +1 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2d28d3b71b3d867f2899e2f49654decf11a0da8d5d7fa2fff5e08cb3eba265ab
|
|
4
|
+
data.tar.gz: acee94a59b33f775cd35f0bd28fa1f76b9cf1bce254cb51ff728c7f20b56dcb3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f43fbf8121cabf433296aaf9a2ea52a77a9564271a3a34a6954600ebc06004602b9a81a53ec784de49ec32e0a5dd70e015d405359627ce97c28df969d29c5c71
|
|
7
|
+
data.tar.gz: de2c37ca93f6096d202360995b752300a7c69680a50e9a06f2f4e23f9b983afc3ed51f06b05ef6e2a96f2066e1c477896b83cb1e6c0c184237ba8ba9a80ea31c
|
data/CHANGELOG.md
CHANGED
|
@@ -170,3 +170,9 @@ All notable changes to this project will be documented in this file.
|
|
|
170
170
|
|
|
171
171
|
## [1.4.5] - 2026-10-11
|
|
172
172
|
- Added tile boundary validation for external geometries, with support for bypassing checks via `unsafe_mode: true`.
|
|
173
|
+
|
|
174
|
+
## [1.4.6] - 2026-10-18
|
|
175
|
+
- Added tile metadata, polygon bounds, ordering, and shared boundary validation for Ruby and C++ mergers, with `unsafe_mode: true` to bypass validation.
|
|
176
|
+
|
|
177
|
+
## [1.4.7] - 2026-11-07
|
|
178
|
+
- Added multiclass raster polygonization support, preserving class IDs throughout polygon extraction and tile merging, including streaming mergers.
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -18,6 +18,8 @@ Besides extracting contours, Contrek can also determine the nesting relationship
|
|
|
18
18
|
|
|
19
19
|
Several coordinate-compression algorithms are available to reduce polygon size after tracing. The library can also split the work across multiple threads by processing independent vertical stripes and merging the results afterwards into a single topologically consistent representation.
|
|
20
20
|
|
|
21
|
+
Contrek also supports multiclass polygonization, extracting multiple raster classes in a single pass while preserving their identifiers throughout the merging process.
|
|
22
|
+
|
|
21
23
|
The image below shows a simple example. Every non-white pixel is considered part of the traced region. The resulting outer contour is shown in red, while inner contours are shown in green.
|
|
22
24
|
|
|
23
25
|

|
|
@@ -676,6 +678,27 @@ result = Contrek.contour!(
|
|
|
676
678
|
|
|
677
679
|
The choice mainly affects how diagonally touching regions are interpreted.
|
|
678
680
|
|
|
681
|
+
|
|
682
|
+
## Multiclass Polygonization
|
|
683
|
+
|
|
684
|
+
Contrek supports multiclass polygonization, extracting contours for multiple pixel classes in a single processing pass.
|
|
685
|
+
Enable it using the `multiclass: true` option:
|
|
686
|
+
|
|
687
|
+
```ruby
|
|
688
|
+
result = Contrek.contour!(
|
|
689
|
+
png_file_path: "classified.png",
|
|
690
|
+
options: {
|
|
691
|
+
finder: {
|
|
692
|
+
multiclass: true,
|
|
693
|
+
connectivity: 8
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
)
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
Each polygon retains its original pixel value in the `class` attribute. Regions belonging to different classes remain separate, while regions of the same class can be merged across tile boundaries.
|
|
700
|
+
Multiclass processing is supported by both the Ruby and native C++ implementations, including streaming mergers.
|
|
701
|
+
|
|
679
702
|
## Merging External Geometry
|
|
680
703
|
|
|
681
704
|
Contrek's merger can process polygon data produced by external contour
|
|
@@ -726,17 +749,13 @@ extractors, provided that the input geometry follows the requirements below.
|
|
|
726
749
|
|
|
727
750
|
### Boundary Validation
|
|
728
751
|
|
|
729
|
-
By default, Contrek validates the shared boundaries of adjacent tiles when
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
`Tile boundary mismatch` error.
|
|
752
|
+
By default, Contrek validates the shared boundaries of adjacent tiles when merging externally supplied geometries. Boundary coordinates must match exactly, including intermediate vertices. Otherwise, the merger raises a `Tile boundary mismatch` error.
|
|
753
|
+
|
|
754
|
+
This validation can be disabled by setting `unsafe_mode: true`, avoiding validation overhead when the input geometries are known to be valid.
|
|
733
755
|
|
|
734
|
-
|
|
735
|
-
validation overhead when the input geometries are known to be valid.
|
|
756
|
+
**Important:** Boundary validation does not verify all the Laws of the Tiles. Users supplying external geometries remain responsible for satisfying every requirement listed above.
|
|
736
757
|
|
|
737
|
-
**Important:** Boundary validation
|
|
738
|
-
Users supplying external geometries remain responsible for satisfying every
|
|
739
|
-
requirement listed above.
|
|
758
|
+
**Important:** Boundary validation prioritizes correctness over performance and introduce additional processing overhead. Experienced users who can guarantee that their input geometries satisfy the Laws of the Tiles can enable `unsafe_mode: true` to bypass validation entirely and achieve maximum merging performance. This option can also be useful during debugging to isolate merging issues from validation errors.
|
|
740
759
|
|
|
741
760
|
# Results
|
|
742
761
|
|
|
@@ -413,7 +413,7 @@ void stream_progressive_png_image(const std::string& filepath, uint32_t stripe_h
|
|
|
413
413
|
|
|
414
414
|
SvgStreamingMerger merger(
|
|
415
415
|
0,
|
|
416
|
-
{{"bounds", true},{"unsafe_mode", true}},
|
|
416
|
+
{{"bounds", true}, {"unsafe_mode", true}},
|
|
417
417
|
&stream,
|
|
418
418
|
source.width(),
|
|
419
419
|
source.height());
|
|
@@ -24,6 +24,7 @@ void FinderUtils::sanitize_options(pf_Options& options, const Options& incoming_
|
|
|
24
24
|
options.named_sequences = incoming_options.get<bool>(
|
|
25
25
|
"named_sequences", options.named_sequences);
|
|
26
26
|
options.bounds = incoming_options.get<bool>("bounds", options.bounds);
|
|
27
|
+
options.multiclass = incoming_options.get<bool>("multiclass", options.multiclass);
|
|
27
28
|
options.unsafe_mode = incoming_options.get<bool>("unsafe_mode", options.unsafe_mode);
|
|
28
29
|
options.deterministic = incoming_options.get<bool>("deterministic", options.deterministic);
|
|
29
30
|
options.processing_height = static_cast<int>(incoming_options.get<std::int64_t>(
|
|
@@ -19,16 +19,15 @@
|
|
|
19
19
|
|
|
20
20
|
static const int TURNER[2][2] = {{Node::OMAX, Node::OMIN}, {Node::TURN_MAX, Node::TURN_MIN}};
|
|
21
21
|
|
|
22
|
-
Node::Node(int min_x, int max_x, int y, NodeCluster* cluster, char name)
|
|
23
|
-
:
|
|
22
|
+
Node::Node(int min_x, int max_x, int y, NodeCluster* cluster, char name, unsigned int class_id)
|
|
23
|
+
: class_id(class_id),
|
|
24
|
+
cluster(cluster),
|
|
24
25
|
min_x(min_x),
|
|
25
26
|
max_x(max_x + 1) {
|
|
26
27
|
this->y = y;
|
|
27
28
|
this->name = name;
|
|
28
29
|
this->tangs_count = 0;
|
|
29
30
|
this->abs_x_index = 0;
|
|
30
|
-
this->down_indexer = 0;
|
|
31
|
-
this->up_indexer = 0;
|
|
32
31
|
this->track = 0;
|
|
33
32
|
this->outer_index = -1;
|
|
34
33
|
this->inner_index = -1;
|
|
@@ -63,47 +62,45 @@ void Node::precalc_tangs_sequences(NodeCluster& cluster) {
|
|
|
63
62
|
int upper_size = this->upper_end >= 0 ? (this->upper_end - this->upper_start + 1) : 0;
|
|
64
63
|
this->tangs_sequence.reserve(lower_size + upper_size);
|
|
65
64
|
|
|
66
|
-
if (this->upper_end >= 0) {
|
|
67
|
-
this->up_indexer = -cluster.vert_nodes[y + T_UP][this->upper_start].abs_x_index;
|
|
68
|
-
}
|
|
69
65
|
// --- CLOCKWISE (UP) ---
|
|
70
66
|
for (int upper_pos = this->upper_start; upper_pos <= this->upper_end; upper_pos++) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
this->down_indexer = (cluster.vert_nodes[y + T_DOWN][this->lower_start].abs_x_index + lower_size + upper_size - 1);
|
|
67
|
+
if (cluster.vert_nodes[y + T_UP][upper_pos].class_id == this->class_id) {
|
|
68
|
+
tangs_sequence.push_back(-(upper_pos + 1));
|
|
69
|
+
}
|
|
75
70
|
}
|
|
71
|
+
|
|
76
72
|
// --- COUNTER-CLOCKWISE (DOWN) ---
|
|
77
73
|
for (int lower_pos = this->lower_end; lower_pos >= this->lower_start; lower_pos--) {
|
|
78
|
-
|
|
74
|
+
if (cluster.vert_nodes[y + T_DOWN][lower_pos].class_id == this->class_id) {
|
|
75
|
+
tangs_sequence.push_back(lower_pos);
|
|
76
|
+
}
|
|
79
77
|
}
|
|
80
78
|
this->tangs_count = this->tangs_sequence.size();
|
|
81
79
|
}
|
|
82
80
|
|
|
83
81
|
Node* Node::my_next_inner(Node *last, int versus) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
82
|
+
int virtual_index = (last->y < this->y) ? -(last->abs_x_index + 1) : last->abs_x_index;
|
|
83
|
+
auto it = std::find(this->tangs_sequence.begin(), this->tangs_sequence.end(), virtual_index);
|
|
84
|
+
size_t last_node_index = std::distance(this->tangs_sequence.begin(), it);
|
|
85
|
+
size_t size = this->tangs_sequence.size();
|
|
86
|
+
if (versus == Node::O) last_node_index = (last_node_index == 0) ? size - 1 : last_node_index - 1;
|
|
87
|
+
else last_node_index = (last_node_index == size - 1) ? 0 : last_node_index + 1;
|
|
89
88
|
return get_tangent_node_by_virtual_index(this->tangs_sequence[last_node_index]);
|
|
90
89
|
}
|
|
91
90
|
|
|
92
91
|
Node* Node::my_next_outer(Node *last, int versus) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
92
|
+
int virtual_index = (last->y < this->y) ? -(last->abs_x_index + 1) : last->abs_x_index;
|
|
93
|
+
auto it = std::find(this->tangs_sequence.begin(), this->tangs_sequence.end(), virtual_index);
|
|
94
|
+
size_t last_node_index = std::distance(this->tangs_sequence.begin(), it);
|
|
95
|
+
size_t size = this->tangs_sequence.size();
|
|
96
|
+
if (versus == Node::O) last_node_index = (last_node_index == size - 1) ? 0 : last_node_index + 1;
|
|
97
|
+
else last_node_index = (last_node_index == 0) ? size - 1 : last_node_index - 1;
|
|
98
98
|
return get_tangent_node_by_virtual_index(this->tangs_sequence[last_node_index]);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
Point Node::coords_entering_to(Node *enter_to, int mode, int tracking) {
|
|
102
|
-
int
|
|
103
|
-
if (enter_to->y < this->y) enter_to_index = enter_to->abs_x_index + this->up_indexer;
|
|
104
|
-
else enter_to_index = this->down_indexer - enter_to->abs_x_index;
|
|
102
|
+
int tg_index = (enter_to->y < this->y) ? -(enter_to->abs_x_index + 1) : enter_to->abs_x_index;
|
|
105
103
|
|
|
106
|
-
int tg_index = this->tangs_sequence[enter_to_index];
|
|
107
104
|
if (tg_index < 0) {
|
|
108
105
|
Node& node_up = cluster->vert_nodes[y + T_UP][-(tg_index + 1)];
|
|
109
106
|
if (mode == Node::A) {
|
|
@@ -86,7 +86,6 @@ class Node : public Listable {
|
|
|
86
86
|
|
|
87
87
|
int y;
|
|
88
88
|
int abs_x_index;
|
|
89
|
-
int up_indexer, down_indexer;
|
|
90
89
|
int tangs_count;
|
|
91
90
|
int track;
|
|
92
91
|
int outer_index, inner_index;
|
|
@@ -94,6 +93,7 @@ class Node : public Listable {
|
|
|
94
93
|
int upper_end = -1;
|
|
95
94
|
int lower_start = std::numeric_limits<int>::max();
|
|
96
95
|
int lower_end = -1;
|
|
96
|
+
unsigned int class_id;
|
|
97
97
|
NodeCluster* cluster;
|
|
98
98
|
void add_intersection(Node& other_node, int other_node_index);
|
|
99
99
|
SmallVec tangs_sequence;
|
|
@@ -111,7 +111,7 @@ class Node : public Listable {
|
|
|
111
111
|
|
|
112
112
|
public:
|
|
113
113
|
int min_x, max_x;
|
|
114
|
-
Node(int min_x, int max_x, int y, NodeCluster* cluster, char name);
|
|
114
|
+
Node(int min_x, int max_x, int y, NodeCluster* cluster, char name, unsigned int class_id = 0);
|
|
115
115
|
void precalc_tangs_sequences(NodeCluster& cluster);
|
|
116
116
|
void assign_lateral_inner_index(Node* my_prev_node, Node* end_node, int versus);
|
|
117
117
|
bool processed = false;
|
|
@@ -85,8 +85,8 @@ void NodeCluster::build_tangs_sequence() {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
Node* NodeCluster::add_node(int min_x, int max_x, int y, char name, int offset) {
|
|
89
|
-
vert_nodes[y].emplace_back(min_x, max_x, y, this, name);
|
|
88
|
+
Node* NodeCluster::add_node(int min_x, int max_x, int y, char name, int offset, unsigned int class_id) {
|
|
89
|
+
vert_nodes[y].emplace_back(min_x, max_x, y, this, name, class_id);
|
|
90
90
|
|
|
91
91
|
Node& node = vert_nodes[y].back();
|
|
92
92
|
node.abs_x_index = vert_nodes[y].size() - 1;
|
|
@@ -104,8 +104,10 @@ Node* NodeCluster::add_node(int min_x, int max_x, int y, char name, int offset)
|
|
|
104
104
|
|
|
105
105
|
while (it != up_nodes.end()) {
|
|
106
106
|
if ((it->min_x - offset) > (node.max_x - 1)) break;
|
|
107
|
-
node.
|
|
108
|
-
|
|
107
|
+
if (!this->options->multiclass || node.class_id == it->class_id) {
|
|
108
|
+
node.add_intersection(*it, it->abs_x_index);
|
|
109
|
+
it->add_intersection(node, node.abs_x_index);
|
|
110
|
+
}
|
|
109
111
|
++it;
|
|
110
112
|
}
|
|
111
113
|
}
|
|
@@ -123,6 +125,7 @@ void NodeCluster::plot(int versus) {
|
|
|
123
125
|
this->plot_sequence.clear();
|
|
124
126
|
this->plot_sequence.push_back(root_node);
|
|
125
127
|
Polygon poly;
|
|
128
|
+
poly.class_id = root_node->class_id;
|
|
126
129
|
Node *next_node = nullptr;
|
|
127
130
|
if ((root_node)->tangs_sequence.size() > 0) // front() or back() on empty list is undefined
|
|
128
131
|
{ versus == Node::A ?
|
|
@@ -47,7 +47,7 @@ class NodeCluster {
|
|
|
47
47
|
std::list<Polygon> polygons;
|
|
48
48
|
NodeCluster(int h, int w, pf_Options *options);
|
|
49
49
|
virtual ~NodeCluster();
|
|
50
|
-
Node* add_node(int min_x, int max_x, int y, char name, int offset);
|
|
50
|
+
Node* add_node(int min_x, int max_x, int y, char name, int offset, unsigned int class_id = 0);
|
|
51
51
|
void calc_root_nodes();
|
|
52
52
|
void build_tangs_sequence();
|
|
53
53
|
void plot(int versus);
|
|
@@ -49,7 +49,7 @@ PolygonFinder::PolygonFinder(Bitmap *bitmap,
|
|
|
49
49
|
|
|
50
50
|
//= SCAN ==============//
|
|
51
51
|
cpu_timer.start();
|
|
52
|
-
scan();
|
|
52
|
+
this->options.multiclass ? multiclass_scan() : scan();
|
|
53
53
|
reports["scan"] = cpu_timer.stop();
|
|
54
54
|
//=====================//
|
|
55
55
|
|
|
@@ -89,20 +89,33 @@ PolygonFinder::~PolygonFinder() {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
void PolygonFinder::scan() {
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
int bpp = this->source_bitmap->get_bytes_per_pixel();
|
|
93
|
+
int offset = options.connectivity_offset;
|
|
94
|
+
|
|
95
|
+
RGBMatcher* rgb_m = dynamic_cast<RGBMatcher*>(this->matcher);
|
|
96
|
+
|
|
97
|
+
if (bpp == 1) {
|
|
98
|
+
auto fetcher = [](const unsigned char* p) { return static_cast<unsigned int>(p[0]); };
|
|
99
|
+
if (rgb_m) run_loop(rgb_m, fetcher, offset);
|
|
100
|
+
else run_loop(this->matcher, fetcher, offset);
|
|
101
|
+
} else {
|
|
102
|
+
auto fetcher = [](const unsigned char* p) { return *reinterpret_cast<const uint32_t*>(p); };
|
|
103
|
+
if (rgb_m) run_loop(rgb_m, fetcher, offset);
|
|
104
|
+
else run_loop(this->matcher, fetcher, offset);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
94
107
|
|
|
95
|
-
|
|
108
|
+
void PolygonFinder::multiclass_scan() {
|
|
109
|
+
int bpp = this->source_bitmap->get_bytes_per_pixel();
|
|
110
|
+
int offset = options.connectivity_offset;
|
|
96
111
|
|
|
97
|
-
|
|
112
|
+
if (bpp == 1) {
|
|
98
113
|
auto fetcher = [](const unsigned char* p) { return static_cast<unsigned int>(p[0]); };
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
} else {
|
|
114
|
+
run_multiclass_loop(fetcher, offset);
|
|
115
|
+
} else {
|
|
102
116
|
auto fetcher = [](const unsigned char* p) { return *reinterpret_cast<const uint32_t*>(p); };
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
117
|
+
run_multiclass_loop(fetcher, offset);
|
|
118
|
+
}
|
|
106
119
|
}
|
|
107
120
|
|
|
108
121
|
ProcessResult* PolygonFinder::process_info() {
|
|
@@ -53,6 +53,7 @@ struct pf_Options {
|
|
|
53
53
|
bool named_sequences = false;
|
|
54
54
|
bool unsafe_mode = false;
|
|
55
55
|
bool bounds = false;
|
|
56
|
+
bool multiclass = false;
|
|
56
57
|
bool deterministic = false;
|
|
57
58
|
int processing_height = 0;
|
|
58
59
|
int connectivity_offset = 0;
|
|
@@ -68,7 +69,8 @@ struct pf_Options {
|
|
|
68
69
|
|
|
69
70
|
struct ProcessResult {
|
|
70
71
|
int groups;
|
|
71
|
-
int width
|
|
72
|
+
int width = 0;
|
|
73
|
+
int height = 0;
|
|
72
74
|
int versus;
|
|
73
75
|
int number_of_threads;
|
|
74
76
|
bool has_bounds = false;
|
|
@@ -180,6 +182,7 @@ class PolygonFinder {
|
|
|
180
182
|
pf_Options options;
|
|
181
183
|
std::map<std::string, double> reports;
|
|
182
184
|
void scan();
|
|
185
|
+
void multiclass_scan();
|
|
183
186
|
CpuTimer cpu_timer;
|
|
184
187
|
|
|
185
188
|
template <typename M, typename F>
|
|
@@ -279,6 +282,70 @@ class PolygonFinder {
|
|
|
279
282
|
}
|
|
280
283
|
}
|
|
281
284
|
|
|
285
|
+
template <typename F>
|
|
286
|
+
void run_multiclass_loop(F&& fetch_color, int offset) {
|
|
287
|
+
int img_h = this->processing_height;
|
|
288
|
+
int bpp = this->source_bitmap->get_bytes_per_pixel();
|
|
289
|
+
|
|
290
|
+
for (int y = 0; y < img_h; y++) {
|
|
291
|
+
const unsigned char* row_ptr = this->source_bitmap->get_row_ptr(y);
|
|
292
|
+
const unsigned char* p = row_ptr + (this->start_x * bpp);
|
|
293
|
+
|
|
294
|
+
int min_x = this->start_x;
|
|
295
|
+
bool has_color = false;
|
|
296
|
+
unsigned int current_color = 0;
|
|
297
|
+
int x = this->start_x;
|
|
298
|
+
if (bpp == 4) {
|
|
299
|
+
for (; x <= this->end_x - 4; x += 4) {
|
|
300
|
+
unsigned int c0 = fetch_color(p);
|
|
301
|
+
unsigned int c1 = fetch_color(p + 4);
|
|
302
|
+
unsigned int c2 = fetch_color(p + 8);
|
|
303
|
+
unsigned int c3 = fetch_color(p + 12);
|
|
304
|
+
p += 16;
|
|
305
|
+
|
|
306
|
+
if (!has_color) {
|
|
307
|
+
min_x = x; current_color = c0; has_color = true;
|
|
308
|
+
} else if (c0 != current_color) {
|
|
309
|
+
this->node_cluster->add_node(min_x, x - 1, y, current_color, offset, current_color);
|
|
310
|
+
min_x = x; current_color = c0;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (c1 != current_color) {
|
|
314
|
+
this->node_cluster->add_node(min_x, x, y, current_color, offset, current_color);
|
|
315
|
+
min_x = x + 1; current_color = c1;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (c2 != current_color) {
|
|
319
|
+
this->node_cluster->add_node(min_x, x + 1, y, current_color, offset, current_color);
|
|
320
|
+
min_x = x + 2; current_color = c2;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (c3 != current_color) {
|
|
324
|
+
this->node_cluster->add_node(min_x, x + 2, y, current_color, offset, current_color);
|
|
325
|
+
min_x = x + 3; current_color = c3;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
for (; x < this->end_x; x++) {
|
|
331
|
+
unsigned int color = fetch_color(p);
|
|
332
|
+
p += bpp;
|
|
333
|
+
if (!has_color) {
|
|
334
|
+
min_x = x;
|
|
335
|
+
current_color = color;
|
|
336
|
+
has_color = true;
|
|
337
|
+
} else if (color != current_color) {
|
|
338
|
+
this->node_cluster->add_node(min_x, x - 1, y, current_color, offset, current_color);
|
|
339
|
+
min_x = x;
|
|
340
|
+
current_color = color;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (has_color) {
|
|
344
|
+
this->node_cluster->add_node(min_x, this->end_x - 1, y, current_color, offset, current_color);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
282
349
|
public:
|
|
283
350
|
PolygonFinder(Bitmap *bitmap,
|
|
284
351
|
Matcher *matcher,
|
|
@@ -100,7 +100,7 @@ Tile* Cluster::merge_tiles() {
|
|
|
100
100
|
}
|
|
101
101
|
shape->clear_inner();
|
|
102
102
|
|
|
103
|
-
Polyline* polyline = tile->shapes_pool->acquire_polyline(tile, new_outer.to_vector(), std::nullopt);
|
|
103
|
+
Polyline* polyline = tile->shapes_pool->acquire_polyline(tile, new_outer.to_vector(), shape->outer_polyline->class_id(), std::nullopt);
|
|
104
104
|
Shape* inserting_new_shape = tile->shapes_pool->acquire_shape(polyline, new_inners);
|
|
105
105
|
new_shapes.push_back(inserting_new_shape);
|
|
106
106
|
polyline->shape = inserting_new_shape;
|
|
@@ -102,11 +102,17 @@ void Cursor::traverse_outer(Part* start_part,
|
|
|
102
102
|
return;
|
|
103
103
|
}
|
|
104
104
|
new_position->end_point()->tracked_outer = true;
|
|
105
|
-
int
|
|
105
|
+
int opposite_versus = -act_part->versus();
|
|
106
106
|
auto& q_set = new_position->end_point()->queues();
|
|
107
|
+
auto* act_part_polyline = act_part->polyline();
|
|
107
108
|
auto it = std::find_if(q_set.begin(), q_set.end(), [&](Queueable<Point>* q) {
|
|
108
109
|
Part* p = static_cast<Part*>(q);
|
|
109
|
-
|
|
110
|
+
if (p->versus() != opposite_versus) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
auto* polyline = p->polyline();
|
|
114
|
+
return polyline->tile != act_part_polyline->tile &&
|
|
115
|
+
polyline->class_id() == act_part_polyline->class_id();
|
|
110
116
|
});
|
|
111
117
|
|
|
112
118
|
Part* part = nullptr;
|
|
@@ -60,6 +60,9 @@ Finder::Finder(int number_of_threads, Bitmap *bitmap, Matcher *matcher, const Op
|
|
|
60
60
|
if (this->options_.processing_height != 0) {
|
|
61
61
|
base_arguments["processing_height"] = this->options_.processing_height;
|
|
62
62
|
}
|
|
63
|
+
if (this->options_.multiclass) {
|
|
64
|
+
base_arguments["multiclass"] = true;
|
|
65
|
+
}
|
|
63
66
|
|
|
64
67
|
CpuTimer t;
|
|
65
68
|
t.start();
|
data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/GeoJsonStreamingMerger.h
CHANGED
|
@@ -76,10 +76,8 @@ class GeoJsonStreamingMerger : public StreamingMerger {
|
|
|
76
76
|
public:
|
|
77
77
|
GeoJsonStreamingMerger(int number_of_threads,
|
|
78
78
|
const Options& options,
|
|
79
|
-
std::ofstream* stream_to
|
|
80
|
-
|
|
81
|
-
: StreamingMerger(number_of_threads, options, stream_to),
|
|
82
|
-
target_value(pixel_value) {
|
|
79
|
+
std::ofstream* stream_to)
|
|
80
|
+
: StreamingMerger(number_of_threads, options, stream_to) {
|
|
83
81
|
configure_point_writer(options);
|
|
84
82
|
}
|
|
85
83
|
|
|
@@ -90,7 +88,7 @@ class GeoJsonStreamingMerger : public StreamingMerger {
|
|
|
90
88
|
}
|
|
91
89
|
is_first_feature = false;
|
|
92
90
|
|
|
93
|
-
*stream << "{\"type\":\"Feature\",\"properties\":{\"PixelVal\":" <<
|
|
91
|
+
*stream << "{\"type\":\"Feature\",\"properties\":{\"PixelVal\":" << polygon.class_id
|
|
94
92
|
<< "},\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[";
|
|
95
93
|
const std::vector<Point>& points = polygon.outer;
|
|
96
94
|
const size_t points_size = points.size();
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
#include <vector>
|
|
13
13
|
#include <algorithm>
|
|
14
14
|
#include <list>
|
|
15
|
+
#include <memory>
|
|
15
16
|
#include "Merger.h"
|
|
16
17
|
|
|
17
18
|
Merger::Merger(int number_of_threads, const Options& options)
|
|
@@ -19,7 +20,9 @@ Merger::Merger(int number_of_threads, const Options& options)
|
|
|
19
20
|
if (options.contains("versus")) {
|
|
20
21
|
std::cerr << "[Contrek WARNING] The given options 'versus' will be ignored." << std::endl;
|
|
21
22
|
}
|
|
22
|
-
if (
|
|
23
|
+
if (safe()) {
|
|
24
|
+
validator_ = std::make_unique<MergerValidator>(*this);
|
|
25
|
+
} else {
|
|
23
26
|
std::cerr << "[Contrek WARNING] Processing tile with 'unsafe_mode: true'. "
|
|
24
27
|
<< "Incompatible result options might lead to unexpected vector geometry.\n";
|
|
25
28
|
}
|
|
@@ -30,7 +33,10 @@ bool Merger::safe() {
|
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
void Merger::add_tile(ProcessResult& result)
|
|
33
|
-
{ if (
|
|
36
|
+
{ if (validator_) {
|
|
37
|
+
validator_->validate_metadata(result);
|
|
38
|
+
}
|
|
39
|
+
if (this->height == 0) {
|
|
34
40
|
this->height = result.height;
|
|
35
41
|
} else {
|
|
36
42
|
if (result.height != this->height && safe()) {
|
|
@@ -51,17 +57,15 @@ void Merger::add_tile(ProcessResult& result)
|
|
|
51
57
|
}
|
|
52
58
|
}
|
|
53
59
|
}
|
|
54
|
-
if (safe()) validate(result);
|
|
55
60
|
|
|
56
61
|
int end_x = this->current_x + result.width;
|
|
62
|
+
if (validator_) {
|
|
63
|
+
validator_->validate(result, this->current_x, end_x);
|
|
64
|
+
}
|
|
57
65
|
Tile* tile = new Tile(this, this->current_x, end_x, std::to_string(tiles_.size()), tiles_.size(), Benchmarks {0, 0});
|
|
58
66
|
tile->assign_raw_polygons(result.polygons, result.treemap);
|
|
59
67
|
tiles_.queue_push(tile);
|
|
60
68
|
|
|
61
|
-
if (safe()) {
|
|
62
|
-
previous_boundary_ = boundary_coordinates(result.polygons, end_x);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
69
|
this->maximum_width_ = end_x;
|
|
66
70
|
this->current_x = end_x - 1;
|
|
67
71
|
}
|
|
@@ -84,26 +88,3 @@ void Merger::translate(ProcessResult& result, int offset) {
|
|
|
84
88
|
}
|
|
85
89
|
}
|
|
86
90
|
|
|
87
|
-
std::vector<int> Merger::boundary_coordinates(const std::list<Polygon>& polygons, int boundary_x) {
|
|
88
|
-
std::vector<int> coordinates;
|
|
89
|
-
|
|
90
|
-
for (const auto& polygon : polygons) {
|
|
91
|
-
for (const auto& point : polygon.outer) {
|
|
92
|
-
if (point.x == boundary_x) {
|
|
93
|
-
coordinates.push_back(point.y);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
std::sort(coordinates.begin(), coordinates.end());
|
|
99
|
-
return coordinates;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
void Merger::validate(const ProcessResult& result) {
|
|
103
|
-
if (tiles_.size() == 0) return;
|
|
104
|
-
|
|
105
|
-
const auto current = boundary_coordinates(result.polygons, current_x);
|
|
106
|
-
if (previous_boundary_ != current) {
|
|
107
|
-
throw std::invalid_argument("Tile boundary mismatch: adjacent tiles have different edge coordinates");
|
|
108
|
-
}
|
|
109
|
-
}
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
#include <string>
|
|
12
12
|
#include <vector>
|
|
13
13
|
#include <list>
|
|
14
|
+
#include <memory>
|
|
14
15
|
#include "Finder.h"
|
|
16
|
+
#include "MergerValidator.h"
|
|
15
17
|
#include "../Options.h"
|
|
16
18
|
|
|
17
19
|
class Merger : public Finder {
|
|
@@ -24,7 +26,5 @@ class Merger : public Finder {
|
|
|
24
26
|
protected:
|
|
25
27
|
void translate(ProcessResult& result, int offset);
|
|
26
28
|
int current_x = 0;
|
|
27
|
-
|
|
28
|
-
static std::vector<int> boundary_coordinates(const std::list<Polygon>& polygons, int boundary_x);
|
|
29
|
-
std::vector<int> previous_boundary_;
|
|
29
|
+
std::unique_ptr<MergerValidator> validator_;
|
|
30
30
|
};
|