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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +28 -9
  5. data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.cpp +1 -1
  6. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/FinderUtils.cpp +1 -0
  7. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.cpp +23 -26
  8. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Node.h +2 -2
  9. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.cpp +7 -4
  10. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/NodeCluster.h +1 -1
  11. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/Polygon.h +1 -0
  12. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.cpp +24 -11
  13. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.h +68 -1
  14. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cluster.cpp +1 -1
  15. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.cpp +8 -2
  16. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Finder.cpp +3 -0
  17. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/GeoJsonStreamingMerger.h +3 -5
  18. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Merger.cpp +11 -30
  19. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Merger.h +3 -3
  20. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/MergerValidator.cpp +104 -0
  21. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/MergerValidator.h +35 -0
  22. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Polyline.cpp +3 -2
  23. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Polyline.h +3 -1
  24. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Shape.cpp +1 -0
  25. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/ShapePool.cpp +2 -2
  26. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/ShapePool.h +1 -1
  27. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Tile.cpp +19 -21
  28. data/ext/cpp_polygon_finder/cpp_polygon_finder.cpp +11 -3
  29. data/lib/contrek/cpp/cpp_geo_json_concurrent_streaming_merger.rb +2 -2
  30. data/lib/contrek/cpp/cpp_result.rb +2 -1
  31. data/lib/contrek/finder/concurrent/cluster.rb +1 -1
  32. data/lib/contrek/finder/concurrent/cursor.rb +3 -1
  33. data/lib/contrek/finder/concurrent/finder.rb +2 -1
  34. data/lib/contrek/finder/concurrent/geo_json_streaming_merger.rb +3 -4
  35. data/lib/contrek/finder/concurrent/merger.rb +6 -25
  36. data/lib/contrek/finder/concurrent/merger_validator.rb +96 -0
  37. data/lib/contrek/finder/concurrent/polyline.rb +3 -2
  38. data/lib/contrek/finder/concurrent/shape.rb +2 -1
  39. data/lib/contrek/finder/concurrent/streaming_merger.rb +1 -1
  40. data/lib/contrek/finder/concurrent/tile.rb +9 -3
  41. data/lib/contrek/finder/node.rb +13 -23
  42. data/lib/contrek/finder/node_cluster.rb +17 -5
  43. data/lib/contrek/finder/polygon_finder.rb +29 -1
  44. data/lib/contrek/version.rb +1 -1
  45. data/lib/contrek.rb +1 -0
  46. metadata +4 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9350d60698a69dda805ede85e42b80fcc38022a361e52674fd2780d696e0f9ff
4
- data.tar.gz: '08d164d2e53bb3c96e7212ac6bc9d46bb673de8f10bb950d43f3d66d0c86d793'
3
+ metadata.gz: 2d28d3b71b3d867f2899e2f49654decf11a0da8d5d7fa2fff5e08cb3eba265ab
4
+ data.tar.gz: acee94a59b33f775cd35f0bd28fa1f76b9cf1bce254cb51ff728c7f20b56dcb3
5
5
  SHA512:
6
- metadata.gz: 1c6d0b73ab8affa0ed17aa3fd803f7aed81cec846db6edd3de5dfcec8103bf23c9e23484819c4a981a4cb0c46da18f0b169122124213a9b4417df108f54c468a
7
- data.tar.gz: 57817011679288782915a50ca1adf4155b71732ab73e330e2ac696ea504d7204c08935a6b83487000faac68b37f38aff5b5761d78fc85aab7703752f2700d65f
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- contrek (1.4.5)
4
+ contrek (1.4.7)
5
5
  chunky_png (~> 1.4)
6
6
  concurrent-ruby (~> 1.3.5)
7
7
  ffi (~> 1.17)
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
  ![alt text](contrek.png "Contour tracing")
@@ -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
- merging externally supplied geometries. Boundary coordinates must match
731
- exactly, including intermediate vertices. Otherwise, the merger raises a
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
- This validation can be disabled by setting `unsafe_mode: true`, avoiding
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 does not verify all the Laws of the Tiles.
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
- : cluster(cluster),
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
- tangs_sequence.push_back(-(upper_pos + 1));
72
- }
73
- if (this->lower_end >= 0) {
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
- tangs_sequence.push_back(lower_pos);
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
- unsigned int last_node_index;
85
- if (last->y < this->y) last_node_index = last->abs_x_index + this->up_indexer;
86
- else last_node_index = this->down_indexer - last->abs_x_index;
87
- if (versus == Node::O) last_node_index == 0 ? last_node_index = this->tangs_sequence.size() - 1 : last_node_index--;
88
- else last_node_index == this->tangs_sequence.size() - 1 ? last_node_index = 0 : last_node_index++;
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
- unsigned int last_node_index;
94
- if (last->y < this->y) last_node_index = last->abs_x_index + this->up_indexer;
95
- else last_node_index = this->down_indexer - last->abs_x_index;
96
- if (versus == Node::O) last_node_index == this->tangs_sequence.size() - 1 ? last_node_index = 0 : last_node_index++;
97
- else last_node_index == 0 ? last_node_index = this->tangs_sequence.size() - 1 : last_node_index--;
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 enter_to_index;
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.add_intersection(*it, it->abs_x_index);
108
- it->add_intersection(node, node.abs_x_index);
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);
@@ -17,5 +17,6 @@ struct Polygon {
17
17
  std::vector<Point> outer;
18
18
  std::list<std::vector<Point>> inner;
19
19
  RectBounds bounds;
20
+ unsigned int class_id = 0;
20
21
  Polygon() : bounds(RectBounds::empty()) {}
21
22
  };
@@ -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
- int bpp = this->source_bitmap->get_bytes_per_pixel();
93
- int offset = options.connectivity_offset;
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
- RGBMatcher* rgb_m = dynamic_cast<RGBMatcher*>(this->matcher);
108
+ void PolygonFinder::multiclass_scan() {
109
+ int bpp = this->source_bitmap->get_bytes_per_pixel();
110
+ int offset = options.connectivity_offset;
96
111
 
97
- if (bpp == 1) {
112
+ if (bpp == 1) {
98
113
  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 {
114
+ run_multiclass_loop(fetcher, offset);
115
+ } else {
102
116
  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
- }
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, height;
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 versus = act_part->versus();
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
- return (p->versus() == -versus) && p->polyline()->tile != act_part->polyline()->tile;
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();
@@ -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
- unsigned int pixel_value)
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\":" << target_value
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 (!this->safe()) {
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 (this->height == 0) {
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
- void validate(const ProcessResult& result);
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
  };