contrek 1.2.0 → 1.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b46d3ae57168cfeb52a0788b6e11af74a164c8e19b4414783f95ac96d1507ed2
4
- data.tar.gz: e0637093c914f2426a74b4a47ebeac34152ae473de619fda2e164f22c5d03bc8
3
+ metadata.gz: 3e7576fef74116c62a018f66a13d9e29d215f7b505637b40bcb16631250fed0d
4
+ data.tar.gz: 7ba2f39e97a44eaddd13361b4dc5a8f8cb1790a93dab327c1794d61e0ffc45a0
5
5
  SHA512:
6
- metadata.gz: 472da77db1202e4cf38416e7c5dcfbd483a1f9b9fdb414afb3583a441c3ea180107741d1bc939270fb2aa46e54541712a0efc56f542abe9afd5f39d66e7c847a
7
- data.tar.gz: 66ee95022392360b2cb9dbd9757d669a7f01cc95512ee4a74a288e7dc5c94657ebfc0ebe1107090b3e24df490462073f9ea3f2fd6cb57f7432214a809e486b0b
6
+ metadata.gz: 748e4750b607bc2a9e9e916fc0a00a96db530e94658822d1d6eb97508fa9e302e5def958a63e621a2f97be1396bb0acad4971aea936cbca700c1ea93ac5acdaf
7
+ data.tar.gz: 804a329359ea6648e4bcbf4babdb8af53a12205851d669b562fa37d5aa69a49f8b6988a143f2783c85b2681432f65ff52b82db8f207718bd29b16c46882f518d
data/README.md CHANGED
@@ -49,6 +49,17 @@ The core strength of Contrek is its **Topologically Consistent Merging** algorit
49
49
  </tr>
50
50
  </table>
51
51
 
52
+ ## 📊 Benchmarking & Performance
53
+ The **Stripe-Merging** algorithm has been validated through a dedicated testing suite comparing **Contrek** against **OpenCV** (industry-standard contour extraction).
54
+
55
+ ### Key Metrics:
56
+ * **Execution Latency:** Single-threaded OpenCV vs. Contrek's parallel thread management.
57
+ * **Memory Footprint:** RAM consumption during ultra-high-resolution processing.
58
+ * **Extraction Fidelity:** Verifying polygon precision across both engines.
59
+
60
+ The complete testing suite, source code, and raw benchmarks are available here:
61
+ 👉 **[test_opencv_contrek](https://github.com/runout77/test_opencv_contrek)**
62
+
52
63
  ## Prerequisites
53
64
 
54
65
  For optimal performance and efficient memory management with large images (20k+), it is highly recommended to install **tcmalloc**.
@@ -57,7 +68,11 @@ For optimal performance and efficient memory management with large images (20k+)
57
68
  ```bash
58
69
  sudo apt-get install libgoogle-perftools-dev
59
70
  ```
71
+ > For advanced performance tuning (zlib-ng, tcmalloc, thread configuration) see [PERFORMANCE.md](PERFORMANCE.md).
60
72
 
73
+ > ⚠️ **Platform support:** Contrek native extensions are supported on **Linux** and **macOS** only.
74
+ > Windows is not supported due to the use of POSIX threading primitives and platform-specific
75
+ > memory management. On Windows, consider using WSL2 (Windows Subsystem for Linux).
61
76
 
62
77
  ## Install
63
78
 
@@ -167,7 +182,7 @@ Regarding multithreading:
167
182
 
168
183
  - The algorithm splits the contour-detection workflow into multiple phases that can be executed in parallel. The initial contour extraction on each band and the subsequent merging of coordinates between adjacent bands—performed pairwise, recursively, and in a non-deterministic order—results in a final output that is not idempotent. Idempotence is guaranteed only when the exact same merging sequence is repeated.
169
184
 
170
- By not declaring native option CPP Multithreading optimized code is used. In the above example a [105 MP image](spec/files/images/sample_10240x10240.png) is examined by 4 threads working on 4 tiles (total compute time about 1.1 secs with image load).
185
+ By not declaring native option CPP Multithreading optimized code is used. In the above example a [105 MP image](spec/files/images/sample_10240x10240.png) is examined by 4 threads working on 4 tiles (total compute time about 0.816 secs with image load (0.37 secs)).
171
186
 
172
187
  ```ruby
173
188
  result = Contrek.contour!(
@@ -232,9 +247,10 @@ Engineered for **Pixel-Perfect** precision.
232
247
  * **Result:** 100% topologically faithful geometry with no micro-gaps between adjacent polygons.
233
248
 
234
249
  Below are two images illustrating the difference in tracing modes. In the first case, with **strict_bounds ON**, the anti-clockwise sequence includes two additional points, **H** and **I**, which trace the shape more accurately. In the second case, the transition between **G** and **H** is approximated, omitting the indentation.
250
+
235
251
  | Strict Bounds ON | Strict Bounds OFF |
236
252
  |:---:|:---:|
237
- | ![Originale](./docs/images/strict_bounds_on.png) | ![Poligoni](./docs/images/strict_bounds_off.png) |
253
+ | <img src="./docs/images/strict_bounds_on.png" alt="Originale" width="60%"/> | <img src="./docs/images/strict_bounds_off.png" alt="Poligoni" width="60%"/> |
238
254
 
239
255
 
240
256
  ## Result
@@ -411,6 +427,8 @@ This the one for the native C++
411
427
 
412
428
  About 130x faster. Times are in microseconds; system: AMD Ryzen 7 3700X 8-Core Processor (BogoMIPS: 7199,99) on Ubuntu distro.
413
429
 
430
+ **Note:** Benchmarks were measured inside a VMware virtual machine.
431
+
414
432
  ## 🛠 C++ Standalone Library Usage
415
433
 
416
434
  The core of **Contrek** is a high-performance `C++17` library. It is designed to be **standalone**, meaning it has zero dependencies on Ruby and can be integrated into any `C++` project.
data/Rakefile CHANGED
@@ -6,7 +6,7 @@ task :compile do |t|
6
6
  Dir.glob("**/*.o").each { |f| File.delete(f) }
7
7
  File.delete("Makefile") if File.exist?("Makefile")
8
8
  system "ruby", "extconf.rb"
9
- system "make", "-B"
9
+ system "make", "-j#{`nproc`.strip}", "-B"
10
10
  Dir.glob("**/*.o").each { |f| File.delete(f) }
11
11
  system "cp cpp_polygon_finder.so ./../../lib"
12
12
  end
@@ -1,6 +1,5 @@
1
1
  cmake_minimum_required(VERSION 3.10)
2
2
  project(ContrekCore C CXX)
3
-
4
3
  set(CMAKE_CXX_STANDARD 17)
5
4
  set(CMAKE_C_STANDARD 11)
6
5
  if(CMAKE_BUILD_TYPE STREQUAL "Debug")
@@ -22,28 +21,27 @@ else()
22
21
  message(WARNING "Contrek: tcmalloc not found; standard one will be used.")
23
22
  endif()
24
23
  endif()
25
-
26
24
  find_package(ZLIB REQUIRED)
27
-
25
+ message(STATUS "Contrek: ZLIB path found at ${ZLIB_LIBRARIES}")
28
26
  file(GLOB_RECURSE CPP_SOURCES "*.cpp")
29
27
  file(GLOB_RECURSE C_SOURCES "*.c")
30
-
31
28
  list(FILTER CPP_SOURCES EXCLUDE REGEX "examples/.*\\.cpp")
32
-
33
29
  add_library(ContrekLib STATIC ${CPP_SOURCES} ${C_SOURCES})
34
-
35
30
  file(GLOB_RECURSE ALL_HEADERS "*.h")
36
31
  foreach(header_file ${ALL_HEADERS})
37
32
  get_filename_component(header_dir ${header_file} DIRECTORY)
38
33
  list(APPEND ALL_INCLUDE_DIRS ${header_dir})
39
34
  endforeach()
40
35
  list(REMOVE_DUPLICATES ALL_INCLUDE_DIRS)
41
-
42
- target_include_directories(ContrekLib PUBLIC ${ALL_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS})
43
- target_link_libraries(ContrekLib PRIVATE ${ZLIB_LIBRARIES} pthread)
44
-
36
+ target_include_directories(ContrekLib PUBLIC
37
+ ${ALL_INCLUDE_DIRS}
38
+ ${ZLIB_INCLUDE_DIRS}
39
+ )
40
+ target_link_libraries(ContrekLib PRIVATE
41
+ ${ZLIB_LIBRARIES}
42
+ pthread
43
+ )
45
44
  option(BUILD_EXAMPLES "Build the example application" OFF)
46
-
47
45
  if(BUILD_EXAMPLES)
48
46
  if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/examples/example.cpp")
49
47
  message(STATUS "Contrek: Compiling example option ON")
@@ -56,4 +54,4 @@ if(BUILD_EXAMPLES)
56
54
  else()
57
55
  message(WARNING "Contrek: examples/example.cpp not found!")
58
56
  endif()
59
- endif()
57
+ endif()
@@ -8,6 +8,7 @@
8
8
  */
9
9
 
10
10
  #include <iostream>
11
+ #include <gperftools/malloc_extension.h>
11
12
  #include "ContrekApi.h"
12
13
  #include "Tests.h"
13
14
 
@@ -29,6 +30,11 @@ void run_test() {
29
30
  }
30
31
 
31
32
  int main() {
33
+ MallocExtension::instance()->SetNumericProperty(
34
+ "tcmalloc.max_total_thread_cache_bytes",
35
+ 1024 * 1024 * 1024
36
+ );
37
+
32
38
  Contrek::Config cfg;
33
39
  cfg.threads = 4;
34
40
  cfg.tiles = 4;
@@ -98,7 +98,7 @@ void Tests::test_c()
98
98
  Point* p2 = new Point({2, 2});
99
99
  Point* p3 = new Point({3, 3});
100
100
 
101
- Hub* hub = new Hub(4, 0, 3);
101
+ Hub* hub = new Hub(4);
102
102
 
103
103
  Position* pos1 = new Position(hub, p1);
104
104
  Position* pos2 = new Position(hub, p2);
@@ -17,6 +17,8 @@
17
17
  #include "Node.h"
18
18
  #include "NodeCluster.h"
19
19
 
20
+ static const int TURNER[2][2] = {{Node::OMAX, Node::OMIN}, {Node::TURN_MAX, Node::TURN_MIN}};
21
+
20
22
  Node::Node(int min_x, int max_x, int y, NodeCluster* cluster, char name)
21
23
  : start_point(min_x, y),
22
24
  end_point(max_x, y),
@@ -14,8 +14,43 @@
14
14
  #include <limits>
15
15
  #include <algorithm>
16
16
  #include <map>
17
+ #include <cstring>
18
+ #include <cstddef>
17
19
  #include "List.h"
18
20
 
21
+ struct SmallVec {
22
+ static constexpr int INLINE_CAP = 6;
23
+ int buf[INLINE_CAP];
24
+ int* ptr = buf;
25
+ int sz = 0, cap = INLINE_CAP;
26
+ int front() const { return ptr[0]; }
27
+ int back() const { return ptr[sz - 1]; }
28
+ void push_back(int v) {
29
+ if (sz == cap) {
30
+ cap *= 2;
31
+ int* np = new int[cap];
32
+ std::memcpy(np, ptr, sz * sizeof(int));
33
+ if (ptr != buf) delete[] ptr;
34
+ ptr = np;
35
+ }
36
+ ptr[sz++] = v;
37
+ }
38
+ void reserve(int n) {
39
+ if (n <= cap) return;
40
+ int* np = new int[n];
41
+ std::memcpy(np, ptr, sz * sizeof(int));
42
+ if (ptr != buf) delete[] ptr;
43
+ ptr = np; cap = n;
44
+ }
45
+ void clear() { sz = 0; ptr = buf; cap = INLINE_CAP; }
46
+ int size() const { return sz; }
47
+ int& operator[](int i) { return ptr[i]; }
48
+ int operator[](int i) const { return ptr[i]; }
49
+ int* begin() { return ptr; }
50
+ int* end() { return ptr + sz; }
51
+ ~SmallVec() { if (ptr != buf) delete[] ptr; }
52
+ };
53
+
19
54
  class NodeCluster;
20
55
  struct Point {
21
56
  int x;
@@ -42,7 +77,6 @@ class Node : public Listable {
42
77
  static const int OCOMPLETE = OMIN | OMAX;
43
78
  static const int TURN_MAX = IMAX | OMAX;
44
79
  static const int TURN_MIN = IMIN | OMIN;
45
- const int TURNER[2][2] = {{OMAX, OMIN}, {TURN_MAX, TURN_MIN}};
46
80
  static const int OUTER = 0;
47
81
  static const int INNER = 1;
48
82
 
@@ -60,7 +94,7 @@ class Node : public Listable {
60
94
  Point start_point, end_point;
61
95
  NodeCluster* cluster;
62
96
  void add_intersection(Node& other_node, int other_node_index);
63
- std::vector<int> tangs_sequence;
97
+ SmallVec tangs_sequence;
64
98
  Point* coords_entering_to(Node *enter_to, int mode, int tracking);
65
99
  Node* my_next_outer(Node *last, int versus);
66
100
  Node* my_next_inner(Node *last, int versus);
@@ -35,6 +35,7 @@ NodeCluster::NodeCluster(int h, int w, pf_Options *options) {
35
35
  this->root_nodes = this->lists.add_list();
36
36
  this->inner_plot = this->lists.add_list();
37
37
  this->inner_new = this->lists.add_list();
38
+ this->plot_sequence.reserve(1024);
38
39
  }
39
40
 
40
41
  NodeCluster::~NodeCluster() {
@@ -68,8 +69,8 @@ void NodeCluster::compress_coords(std::list<Polygon>& polygons, pf_Options optio
68
69
  }
69
70
 
70
71
  void NodeCluster::build_tangs_sequence() {
71
- for (auto& line : vert_nodes) {
72
- for (Node& node : line) {
72
+ for (int y = 0; y < (int)vert_nodes.size(); y++) {
73
+ for (Node& node : vert_nodes[y]) {
73
74
  node.precalc_tangs_sequences(*this);
74
75
  }
75
76
  }
@@ -94,8 +95,7 @@ Node* NodeCluster::add_node(int min_x, int max_x, int y, char name, int offset)
94
95
 
95
96
  while (it != up_nodes.end()) {
96
97
  if ((it->min_x - offset) > node.max_x) break;
97
- int current_index = std::distance(up_nodes.begin(), it);
98
- node.add_intersection(*it, current_index);
98
+ node.add_intersection(*it, it->abs_x_index);
99
99
  it->add_intersection(node, node.abs_x_index);
100
100
  ++it;
101
101
  }
@@ -20,7 +20,7 @@
20
20
  Cluster::Cluster(Finder *finder, int height, int start_x, int end_x)
21
21
  : finder(finder)
22
22
  { tiles_.reserve(2); // only two (left|right)
23
- this->hub_ = new Hub(height, start_x, end_x);
23
+ this->hub_ = new Hub(height);
24
24
  }
25
25
 
26
26
  Cluster::~Cluster() {
@@ -10,8 +10,8 @@
10
10
  #include "Hub.h"
11
11
  #include <cstring>
12
12
 
13
- Hub::Hub(int height, int start_x, int end_x)
14
- : width_(end_x - start_x), height_(height), start_x_(start_x)
13
+ Hub::Hub(int height)
14
+ : height_(height)
15
15
  { size_t total_pixels = static_cast<size_t>(height);
16
16
  payloads_.resize(total_pixels);
17
17
  bitset_.resize((total_pixels + 63) / 64, 0);
@@ -15,10 +15,8 @@
15
15
  class EndPoint;
16
16
  class Hub {
17
17
  public:
18
- explicit Hub(int height, int start_x, int end_x);
18
+ explicit Hub(int height);
19
19
  int spawn_end_point();
20
- const int width() const { return width_; }
21
- const int start_x() const { return start_x_; }
22
20
  inline EndPoint* get(int key) {
23
21
  if (!is_set(key)) return nullptr;
24
22
  int index = payloads_[key];
@@ -34,9 +32,7 @@ class Hub {
34
32
  }
35
33
 
36
34
  private:
37
- int width_;
38
35
  int height_;
39
- int start_x_;
40
36
  std::vector<int> payloads_;
41
37
  std::vector<EndPoint> endpoint_pool_;
42
38
  Hub(const Hub&) = delete;
@@ -6,6 +6,9 @@
6
6
  * Copyright 2025 Emanuele Cesaroni
7
7
  */
8
8
 
9
+ #ifdef HAVE_TCMALLOC
10
+ #include <gperftools/malloc_extension.h>
11
+ #endif
9
12
  #include <iostream>
10
13
  #include <list>
11
14
  #include <vector>
@@ -286,6 +289,13 @@ ProcessResult ruby_result_to_process_result(Rice::Object rb_result) {
286
289
 
287
290
  extern "C"
288
291
  void Init_cpp_polygon_finder() {
292
+ #ifdef HAVE_TCMALLOC
293
+ MallocExtension::instance()->SetNumericProperty(
294
+ "tcmalloc.max_total_thread_cache_bytes",
295
+ 1024 * 1024 * 1024
296
+ );
297
+ #endif
298
+
289
299
  Data_Type<Bitmap> rb_cBitmap =
290
300
  define_class<Bitmap>("CPPBitMap")
291
301
  .define_constructor(Constructor<Bitmap, std::string, int>())
@@ -6,7 +6,7 @@ module Contrek
6
6
  def initialize(finder:, height:, start_x:, end_x:)
7
7
  @finder = finder
8
8
  @tiles = []
9
- @hub = Hub.new(height:, start_x:, end_x:)
9
+ @hub = Hub.new(height: )
10
10
  end
11
11
 
12
12
  def add(tile)
@@ -2,7 +2,7 @@ module Contrek
2
2
  module Concurrent
3
3
  class Hub
4
4
  attr_reader :payloads
5
- def initialize(height:, start_x:, end_x:)
5
+ def initialize(height:)
6
6
  # @payloads = Array.new(height)
7
7
  @payloads = {}
8
8
  end
@@ -1,3 +1,3 @@
1
1
  module Contrek
2
- VERSION = "1.2.0"
2
+ VERSION = "1.2.1"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: contrek
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emanuele Cesaroni
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-05-02 00:00:00.000000000 Z
11
+ date: 2026-05-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec