contrek 1.4.6 → 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 +3 -0
- data/Gemfile.lock +1 -1
- data/README.md +23 -0
- 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 +66 -0
- 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/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 +1 -1
- data/ext/cpp_polygon_finder/cpp_polygon_finder.cpp +9 -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/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 -2
- 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
- metadata +1 -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
|
@@ -173,3 +173,6 @@ All notable changes to this project will be documented in this file.
|
|
|
173
173
|
|
|
174
174
|
## [1.4.6] - 2026-10-18
|
|
175
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
|
|
@@ -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;
|
|
@@ -181,6 +182,7 @@ class PolygonFinder {
|
|
|
181
182
|
pf_Options options;
|
|
182
183
|
std::map<std::string, double> reports;
|
|
183
184
|
void scan();
|
|
185
|
+
void multiclass_scan();
|
|
184
186
|
CpuTimer cpu_timer;
|
|
185
187
|
|
|
186
188
|
template <typename M, typename F>
|
|
@@ -280,6 +282,70 @@ class PolygonFinder {
|
|
|
280
282
|
}
|
|
281
283
|
}
|
|
282
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
|
+
|
|
283
349
|
public:
|
|
284
350
|
PolygonFinder(Bitmap *bitmap,
|
|
285
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();
|
|
@@ -17,10 +17,11 @@
|
|
|
17
17
|
#include "Tile.h"
|
|
18
18
|
#include "Shape.h"
|
|
19
19
|
|
|
20
|
-
Polyline::Polyline(ShapePool* shape_pool, Tile* tile, std::vector<Point> polygon, const std::optional<RectBounds>& bounds)
|
|
20
|
+
Polyline::Polyline(ShapePool* shape_pool, Tile* tile, std::vector<Point> polygon, const std::optional<RectBounds>& bounds, unsigned int class_id)
|
|
21
21
|
: tile(tile),
|
|
22
22
|
shape_pool(shape_pool),
|
|
23
|
-
raw_(std::move(polygon))
|
|
23
|
+
raw_(std::move(polygon)),
|
|
24
|
+
class_id_(class_id)
|
|
24
25
|
{ if (bounds.has_value()) {
|
|
25
26
|
min_x = bounds->min_x;
|
|
26
27
|
max_x_ = bounds->max_x;
|
|
@@ -29,7 +29,7 @@ class InnerPolyline;
|
|
|
29
29
|
class Polyline : public Partitionable {
|
|
30
30
|
public:
|
|
31
31
|
using Partitionable::Partitionable;
|
|
32
|
-
Polyline(ShapePool* shape_pool, Tile* tile, std::vector<Point> polygon, const std::optional<RectBounds>& bounds = std::nullopt);
|
|
32
|
+
Polyline(ShapePool* shape_pool, Tile* tile, std::vector<Point> polygon, const std::optional<RectBounds>& bounds = std::nullopt, unsigned int class_id = 0);
|
|
33
33
|
enum Flags : uint32_t {
|
|
34
34
|
TRACKED_OUTER = 1 << 0
|
|
35
35
|
};
|
|
@@ -47,6 +47,7 @@ class Polyline : public Partitionable {
|
|
|
47
47
|
int max_y() const { return max_y_; }
|
|
48
48
|
int min_y() const { return min_y_; }
|
|
49
49
|
int max_x() const { return max_x_; }
|
|
50
|
+
unsigned int class_id() const { return class_id_; }
|
|
50
51
|
void clear();
|
|
51
52
|
bool is_empty();
|
|
52
53
|
bool any_ancients = false;
|
|
@@ -63,5 +64,6 @@ class Polyline : public Partitionable {
|
|
|
63
64
|
void find_boundary();
|
|
64
65
|
uint32_t flags_ = 0;
|
|
65
66
|
int name;
|
|
67
|
+
unsigned int class_id_;
|
|
66
68
|
std::string named_;
|
|
67
69
|
};
|
|
@@ -49,6 +49,7 @@ std::string Shape::name() {
|
|
|
49
49
|
Polygon Shape::to_raw_polygon(bool bounds) {
|
|
50
50
|
Polygon poly;
|
|
51
51
|
poly.outer = this->outer_polyline->raw();
|
|
52
|
+
poly.class_id = this->outer_polyline->class_id();
|
|
52
53
|
if (bounds) {
|
|
53
54
|
this->outer_polyline->fill_bounds(poly.bounds);
|
|
54
55
|
}
|
|
@@ -19,8 +19,8 @@ Shape* ShapePool::acquire_shape(Polyline* outer_polyline, const std::vector<Inne
|
|
|
19
19
|
return shape;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
Polyline* ShapePool::acquire_polyline(Tile* tile, std::vector<Point> polygon, const std::optional<RectBounds>& bounds = std::nullopt) {
|
|
23
|
-
return this->polylines.acquire(this, tile, std::move(polygon), bounds);
|
|
22
|
+
Polyline* ShapePool::acquire_polyline(Tile* tile, std::vector<Point> polygon, unsigned int class_id, const std::optional<RectBounds>& bounds = std::nullopt) {
|
|
23
|
+
return this->polylines.acquire(this, tile, std::move(polygon), bounds, class_id);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
InnerPolyline* ShapePool::acquire_inner_polyline(std::vector<Point> coords, Shape* shape) {
|
|
@@ -29,7 +29,7 @@ class ShapePool {
|
|
|
29
29
|
Shape* acquire_shape(Polyline* outer_polyline, const std::vector<InnerPolyline*>& inner_polylines);
|
|
30
30
|
InnerPolyline* acquire_inner_polyline(std::vector<Point> coords, Shape* s);
|
|
31
31
|
InnerPolyline* acquire_inner_polyline(Sequence* seq);
|
|
32
|
-
Polyline* acquire_polyline(Tile* tile, std::vector<Point> polygon, const std::optional<RectBounds>& bounds);
|
|
32
|
+
Polyline* acquire_polyline(Tile* tile, std::vector<Point> polygon, unsigned int class_id, const std::optional<RectBounds>& bounds);
|
|
33
33
|
void set_owner(Tile* tile);
|
|
34
34
|
void detach_shape();
|
|
35
35
|
void detach_polyline();
|
|
@@ -60,7 +60,7 @@ void Tile::assign_raw_polygons(const std::list<Polygon>& raw_polylines, const st
|
|
|
60
60
|
std::unordered_map<int, Shape*> shapes_map;
|
|
61
61
|
int polyline_index = 0;
|
|
62
62
|
for (const auto& raw_polyline : raw_polylines)
|
|
63
|
-
{ Polyline* polyline = this->shapes_pool->acquire_polyline(this, raw_polyline.outer, raw_polyline.bounds);
|
|
63
|
+
{ Polyline* polyline = this->shapes_pool->acquire_polyline(this, raw_polyline.outer, raw_polyline.class_id, raw_polyline.bounds);
|
|
64
64
|
std::vector<InnerPolyline*> inner_polylines_list;
|
|
65
65
|
for (auto& raw_points : raw_polyline.inner) {
|
|
66
66
|
inner_polylines_list.push_back(this->shapes_pool->acquire_inner_polyline(raw_points, nullptr));
|
|
@@ -277,6 +277,9 @@ class To_Ruby<ProcessResult*>
|
|
|
277
277
|
poly_hash[Symbol("bounds")] = bounds_hash;
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
+
if(pr->options.contains("multiclass")) {
|
|
281
|
+
poly_hash[Symbol("class")] = x.class_id;
|
|
282
|
+
}
|
|
280
283
|
out.push(poly_hash);
|
|
281
284
|
}
|
|
282
285
|
rr->polygons = out;
|
|
@@ -317,6 +320,10 @@ ProcessResult ruby_result_to_process_result(Rice::Object rb_result) {
|
|
|
317
320
|
for (size_t i = 0; i < rb_polygons.size(); ++i) {
|
|
318
321
|
Rice::Hash rb_poly = (Rice::Hash)rb_polygons[i];
|
|
319
322
|
Polygon poly;
|
|
323
|
+
Rice::Object rb_class = rb_poly[Symbol("class")];
|
|
324
|
+
if (!rb_class.is_nil()) {
|
|
325
|
+
poly.class_id = detail::From_Ruby<unsigned int>().convert(rb_class.value());
|
|
326
|
+
}
|
|
320
327
|
// BOUNDS
|
|
321
328
|
Rice::Object rb_bounds_obj = rb_poly[Symbol("bounds")];
|
|
322
329
|
if (!rb_bounds_obj.is_nil()) {
|
|
@@ -382,10 +389,9 @@ SvgStreamingMerger* create_svg_streaming_merger(Object self,
|
|
|
382
389
|
GeoJsonStreamingMerger* create_geo_json_streaming_merger(Object self,
|
|
383
390
|
int number_of_threads,
|
|
384
391
|
Options options,
|
|
385
|
-
Object stream_obj
|
|
386
|
-
int pixel_val) {
|
|
392
|
+
Object stream_obj) {
|
|
387
393
|
OfstreamWrapper* wrapper = Rice::detail::From_Ruby<OfstreamWrapper*>().convert(stream_obj.value());
|
|
388
|
-
return new GeoJsonStreamingMerger(number_of_threads, options, &wrapper->get_stream()
|
|
394
|
+
return new GeoJsonStreamingMerger(number_of_threads, options, &wrapper->get_stream());
|
|
389
395
|
}
|
|
390
396
|
|
|
391
397
|
OfstreamWrapper* create_ofstream(Object self, std::string path) {
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
module Contrek
|
|
4
4
|
module Cpp
|
|
5
5
|
class CPPGeoJsonConcurrentStreamingMerger < CPPGeoJsonStreamingMerger
|
|
6
|
-
def self.new(stream_to:, number_of_threads: 0, options: nil
|
|
7
|
-
super(number_of_threads, options, stream_to
|
|
6
|
+
def self.new(stream_to:, number_of_threads: 0, options: nil)
|
|
7
|
+
super(number_of_threads, options, stream_to)
|
|
8
8
|
end
|
|
9
9
|
end
|
|
10
10
|
end
|
|
@@ -23,7 +23,8 @@ module Contrek
|
|
|
23
23
|
@to_points ||= raw_list.map do |polygon|
|
|
24
24
|
{bounds: (polygon[:bounds] if polygon.key?(:bounds)),
|
|
25
25
|
outer: self.class.to_points(polygon[:outer]),
|
|
26
|
-
inner: polygon[:inner].map { |s| self.class.to_points(s) }
|
|
26
|
+
inner: polygon[:inner].map { |s| self.class.to_points(s) },
|
|
27
|
+
class: (polygon[:class] if polygon.key?(:class))}.compact
|
|
27
28
|
end
|
|
28
29
|
end
|
|
29
30
|
|
|
@@ -69,7 +69,7 @@ module Contrek
|
|
|
69
69
|
end.real
|
|
70
70
|
shape.clear_inner!
|
|
71
71
|
|
|
72
|
-
polyline = Polyline.new(tile: tile, polygon: new_outer.to_a)
|
|
72
|
+
polyline = Polyline.new(tile: tile, polygon: new_outer.to_a, class_id: shape.outer_polyline.class_id)
|
|
73
73
|
inserting_new_shape = Shape.init_by(polyline, new_inners)
|
|
74
74
|
new_shapes << inserting_new_shape
|
|
75
75
|
polyline.shape = inserting_new_shape
|
|
@@ -91,7 +91,9 @@ module Contrek
|
|
|
91
91
|
new_position.end_point.tracked_outer = true
|
|
92
92
|
versus = act_part.versus
|
|
93
93
|
part = new_position.end_point.queues.find do |p|
|
|
94
|
-
(p.versus == -versus) &&
|
|
94
|
+
(p.versus == -versus) &&
|
|
95
|
+
p.polyline.tile != act_part.polyline.tile &&
|
|
96
|
+
p.polyline.class_id == act_part.polyline.class_id
|
|
95
97
|
end
|
|
96
98
|
if part
|
|
97
99
|
if all_parts[-2] != part
|
|
@@ -55,7 +55,8 @@ module Contrek
|
|
|
55
55
|
versus: current_versus,
|
|
56
56
|
bounds: true,
|
|
57
57
|
treemap: @options[:treemap],
|
|
58
|
-
connectivity: @options[:connectivity]
|
|
58
|
+
connectivity: @options[:connectivity],
|
|
59
|
+
multiclass: @options[:multiclass]
|
|
59
60
|
}.compact,
|
|
60
61
|
start_x: payload[:tile_start_x],
|
|
61
62
|
end_x: payload[:tile_end_x]
|
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
module Contrek
|
|
4
4
|
module Concurrent
|
|
5
5
|
class GeoJsonStreamingMerger < StreamingMerger
|
|
6
|
-
def initialize(stream_to:,
|
|
7
|
-
@pixel_val = pixel_val
|
|
6
|
+
def initialize(stream_to:, options: {})
|
|
8
7
|
@point_converter = self.class.coordinates_converter(options[:geo_localization])
|
|
9
8
|
|
|
10
|
-
super
|
|
9
|
+
super
|
|
11
10
|
end
|
|
12
11
|
|
|
13
12
|
def write_header
|
|
@@ -35,7 +34,7 @@ module Contrek
|
|
|
35
34
|
end
|
|
36
35
|
feature_hash = {
|
|
37
36
|
type: "Feature",
|
|
38
|
-
properties: {PixelVal:
|
|
37
|
+
properties: {PixelVal: polygon[:class]},
|
|
39
38
|
geometry: {
|
|
40
39
|
type: "Polygon",
|
|
41
40
|
coordinates: polygon_coordinates
|
|
@@ -7,10 +7,10 @@ module Contrek
|
|
|
7
7
|
|
|
8
8
|
TRACKED_OUTER = 1 << 0
|
|
9
9
|
|
|
10
|
-
attr_reader :raw, :name, :min_y, :max_y
|
|
10
|
+
attr_reader :raw, :name, :min_y, :max_y, :class_id
|
|
11
11
|
attr_accessor :shape, :tile, :any_ancients, :inside_inner_polyline
|
|
12
12
|
|
|
13
|
-
def initialize(tile:, polygon:, shape: nil, bounds: nil, force_named: nil)
|
|
13
|
+
def initialize(tile:, polygon:, shape: nil, bounds: nil, force_named: nil, class_id: 0)
|
|
14
14
|
@tile = tile
|
|
15
15
|
@name = tile.shapes.count
|
|
16
16
|
@named = force_named
|
|
@@ -19,6 +19,7 @@ module Contrek
|
|
|
19
19
|
@flags = 0
|
|
20
20
|
@any_ancients = false
|
|
21
21
|
@inside_inner_polyline = nil
|
|
22
|
+
@class_id = class_id
|
|
22
23
|
|
|
23
24
|
if bounds.nil?
|
|
24
25
|
find_boundary
|
|
@@ -40,8 +40,9 @@ module Contrek
|
|
|
40
40
|
"Shape (outer_polyline = #{outer_polyline.named}, children count = #{@children_shapes.size}"
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
def to_raw_polygon(bounds: false)
|
|
43
|
+
def to_raw_polygon(bounds: false, multiclass: false)
|
|
44
44
|
{bounds: (outer_polyline.get_bounds if bounds),
|
|
45
|
+
class: (outer_polyline.class_id if multiclass),
|
|
45
46
|
outer: outer_polyline.raw,
|
|
46
47
|
inner: inner_polylines.map(&:raw)}.compact
|
|
47
48
|
end
|
|
@@ -57,7 +57,8 @@ module Contrek
|
|
|
57
57
|
|
|
58
58
|
def to_raw_polygons
|
|
59
59
|
bounds = @finder.options[:bounds]
|
|
60
|
-
|
|
60
|
+
multiclass = @finder.options[:multiclass]
|
|
61
|
+
@shapes.map { |s| s.to_raw_polygon(bounds:, multiclass:) }
|
|
61
62
|
end
|
|
62
63
|
|
|
63
64
|
def compute_treemap
|
|
@@ -93,7 +94,13 @@ module Contrek
|
|
|
93
94
|
shapes_map = {}
|
|
94
95
|
raw_polylines.each_with_index do |raw_polyline, polyline_index|
|
|
95
96
|
shape = Shape.new.tap do |shape|
|
|
96
|
-
shape.outer_polyline = Polyline.new(
|
|
97
|
+
shape.outer_polyline = Polyline.new(
|
|
98
|
+
tile: self,
|
|
99
|
+
polygon: raw_polyline[:outer],
|
|
100
|
+
shape: shape,
|
|
101
|
+
bounds: raw_polyline[:bounds],
|
|
102
|
+
class_id: raw_polyline[:class] || 0
|
|
103
|
+
)
|
|
97
104
|
shape.inner_polylines = raw_polyline[:inner].map { |raw| InnerPolyline.new(shape: shape, raw_coordinates: raw) }
|
|
98
105
|
end
|
|
99
106
|
@shapes << shape
|
data/lib/contrek/finder/node.rb
CHANGED
|
@@ -6,7 +6,7 @@ module Contrek
|
|
|
6
6
|
include Listable
|
|
7
7
|
|
|
8
8
|
attr_reader :min_x, :max_x, :y, :name, :tangs_sequence, :tangs_count, :data_pointer,
|
|
9
|
-
:upper_start, :upper_end, :lower_start, :lower_end
|
|
9
|
+
:upper_start, :upper_end, :lower_start, :lower_end, :class_id
|
|
10
10
|
attr_accessor :track, :abs_x_index, :outer_index, :inner_index, :inner_left_index, :inner_right_index
|
|
11
11
|
|
|
12
12
|
T_UP = -1
|
|
@@ -27,8 +27,9 @@ module Contrek
|
|
|
27
27
|
OUTER = 0
|
|
28
28
|
INNER = 1
|
|
29
29
|
|
|
30
|
-
def initialize(cluster, min_x, max_x, y, name, connectivity_offset = 0)
|
|
30
|
+
def initialize(cluster, min_x, max_x, y, name, connectivity_offset = 0, class_id = 0)
|
|
31
31
|
@name = name
|
|
32
|
+
@class_id = class_id
|
|
32
33
|
@min_x = min_x
|
|
33
34
|
@max_x = max_x + 1
|
|
34
35
|
@y = y
|
|
@@ -37,8 +38,6 @@ module Contrek
|
|
|
37
38
|
@track = 0
|
|
38
39
|
@abs_x_index = 0
|
|
39
40
|
@data_pointer = cluster.lists.get_data_pointer
|
|
40
|
-
@up_indexer = 0
|
|
41
|
-
@down_indexer = 0
|
|
42
41
|
@outer_index = -1
|
|
43
42
|
@inner_index = -1
|
|
44
43
|
@inner_left_index = -1
|
|
@@ -81,11 +80,8 @@ module Contrek
|
|
|
81
80
|
end
|
|
82
81
|
|
|
83
82
|
def my_next(last, versus, mode)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
else
|
|
87
|
-
@down_indexer - last.abs_x_index
|
|
88
|
-
end
|
|
83
|
+
virtual_index = (last.y < y) ? -(last.abs_x_index + 1) : last.abs_x_index
|
|
84
|
+
last_node_index = @tangs_sequence.index(virtual_index)
|
|
89
85
|
|
|
90
86
|
case mode
|
|
91
87
|
when :outer
|
|
@@ -117,13 +113,8 @@ module Contrek
|
|
|
117
113
|
end
|
|
118
114
|
|
|
119
115
|
def coords_entering_to(enter_to, enter_mode, tracking)
|
|
120
|
-
|
|
121
|
-
enter_to.abs_x_index + @up_indexer
|
|
122
|
-
else
|
|
123
|
-
@down_indexer - enter_to.abs_x_index
|
|
124
|
-
end
|
|
116
|
+
tg_index = (enter_to.y < y) ? -(enter_to.abs_x_index + 1) : enter_to.abs_x_index
|
|
125
117
|
|
|
126
|
-
tg_index = @tangs_sequence[enter_to_index]
|
|
127
118
|
if tg_index < 0
|
|
128
119
|
node_up = @cluster.vert_nodes[y + T_UP][-(tg_index + 1)]
|
|
129
120
|
if enter_mode == :a
|
|
@@ -166,20 +157,19 @@ module Contrek
|
|
|
166
157
|
|
|
167
158
|
def precalc_tangs_sequences(cluster:)
|
|
168
159
|
@tangs_sequence = []
|
|
169
|
-
@up_indexer = -cluster.vert_nodes[@y + T_UP][@upper_start].abs_x_index if @upper_end >= 0
|
|
170
160
|
if @upper_end >= 0
|
|
171
161
|
(@upper_start..@upper_end).each do |upper_pos|
|
|
172
|
-
@
|
|
162
|
+
if cluster.vert_nodes[@y + T_UP][upper_pos].class_id == class_id
|
|
163
|
+
@tangs_sequence << -(upper_pos + 1)
|
|
164
|
+
end
|
|
173
165
|
end
|
|
174
166
|
end
|
|
175
|
-
|
|
176
|
-
lower_size = (@lower_end >= 0) ? (@lower_end - @lower_start + 1) : 0
|
|
177
|
-
upper_size = (@upper_end >= 0) ? (@upper_end - @upper_start + 1) : 0
|
|
178
|
-
@down_indexer = (cluster.vert_nodes[@y + T_DOWN][@lower_start].abs_x_index + lower_size + upper_size - 1)
|
|
179
|
-
end
|
|
167
|
+
|
|
180
168
|
if @lower_end >= 0
|
|
181
169
|
@lower_end.downto(@lower_start).each do |lower_pos|
|
|
182
|
-
@
|
|
170
|
+
if cluster.vert_nodes[@y + T_DOWN][lower_pos].class_id == class_id
|
|
171
|
+
@tangs_sequence << lower_pos
|
|
172
|
+
end
|
|
183
173
|
end
|
|
184
174
|
end
|
|
185
175
|
@tangs_count = tangs_sequence.size
|
|
@@ -57,6 +57,8 @@ module Contrek
|
|
|
57
57
|
versus = @options[:versus]
|
|
58
58
|
inner_v = VERSUS_INVERTER[versus]
|
|
59
59
|
index_order = 0
|
|
60
|
+
multiclass = @options[:multiclass]
|
|
61
|
+
|
|
60
62
|
while @root_nodes.size > 0
|
|
61
63
|
root_node = @root_nodes.shift
|
|
62
64
|
|
|
@@ -110,7 +112,12 @@ module Contrek
|
|
|
110
112
|
draw_sequence(bitmap, "X") unless bitmap.nil?
|
|
111
113
|
|
|
112
114
|
if @sequence_coords.any?
|
|
113
|
-
@polygons << {
|
|
115
|
+
@polygons << {
|
|
116
|
+
outer: @sequence_coords,
|
|
117
|
+
inner: [],
|
|
118
|
+
bounds: (bounds.to_h if @options[:bounds]),
|
|
119
|
+
class: (root_node.class_id if multiclass)
|
|
120
|
+
}.compact
|
|
114
121
|
@sequences << @plot_sequence
|
|
115
122
|
|
|
116
123
|
@count = 0
|
|
@@ -332,6 +339,7 @@ module Contrek
|
|
|
332
339
|
end
|
|
333
340
|
|
|
334
341
|
def add_node(node, offset)
|
|
342
|
+
multiclass = options[:multiclass]
|
|
335
343
|
@nodes += 1
|
|
336
344
|
node.abs_x_index = @vert_nodes[node.y].size
|
|
337
345
|
|
|
@@ -348,15 +356,19 @@ module Contrek
|
|
|
348
356
|
up_node = up_nodes[index]
|
|
349
357
|
if ((up_node.max_x - 1) + offset) >= node.min_x
|
|
350
358
|
if (up_node.min_x - offset) <= (node.max_x - 1)
|
|
351
|
-
node.
|
|
352
|
-
|
|
359
|
+
unless multiclass && node.class_id != up_node.class_id
|
|
360
|
+
node.add_intersection(up_node, index)
|
|
361
|
+
up_node.add_intersection(node, node.abs_x_index)
|
|
362
|
+
end
|
|
353
363
|
end
|
|
354
364
|
return if (index += 1) == up_nodes_count
|
|
355
365
|
loop do
|
|
356
366
|
up_node = up_nodes[index]
|
|
357
367
|
if (up_node.min_x - offset) <= (node.max_x - 1)
|
|
358
|
-
node.
|
|
359
|
-
|
|
368
|
+
unless multiclass && node.class_id != up_node.class_id
|
|
369
|
+
node.add_intersection(up_node, index)
|
|
370
|
+
up_node.add_intersection(node, node.abs_x_index)
|
|
371
|
+
end
|
|
360
372
|
else
|
|
361
373
|
return
|
|
362
374
|
end
|
|
@@ -21,7 +21,7 @@ module Contrek
|
|
|
21
21
|
|
|
22
22
|
# 1 finds matching blocks
|
|
23
23
|
@reports[:scan] = Benchmark.measure do
|
|
24
|
-
scan
|
|
24
|
+
@options[:multiclass] ? multiclass_scan : scan
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
# 2 builds relational spatially tree map
|
|
@@ -129,6 +129,34 @@ module Contrek
|
|
|
129
129
|
end
|
|
130
130
|
end
|
|
131
131
|
|
|
132
|
+
def multiclass_scan
|
|
133
|
+
offset = (@node_cluster.options[:connectivity] == 8) ? 1 : 0
|
|
134
|
+
current_color = nil
|
|
135
|
+
min_x = start_x
|
|
136
|
+
current_y = nil
|
|
137
|
+
last_x = end_x - 1
|
|
138
|
+
|
|
139
|
+
@source_bitmap.scan(start_x: start_x, end_x: end_x, end_y: @processing_height) do |x, y, color|
|
|
140
|
+
if current_y != y || (current_color && color != current_color)
|
|
141
|
+
unless current_color.nil?
|
|
142
|
+
Contrek::Finder::Node.new(@node_cluster, min_x, ((current_y == y) ? x - 1 : last_x), current_y, current_color, offset, current_color.ord)
|
|
143
|
+
end
|
|
144
|
+
min_x = x if current_y == y
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
if current_y != y
|
|
148
|
+
current_y = y
|
|
149
|
+
min_x = x
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
current_color = color
|
|
153
|
+
if x == last_x && current_color
|
|
154
|
+
Contrek::Finder::Node.new(@node_cluster, min_x, last_x, current_y, current_color, offset, current_color.ord)
|
|
155
|
+
current_color = nil
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
132
160
|
def format_benchmarks
|
|
133
161
|
r = {}
|
|
134
162
|
total = 0
|
data/lib/contrek/version.rb
CHANGED