contrek 1.2.1 → 1.2.2
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 +9 -1
- data/Gemfile.lock +1 -1
- data/PERFORMANCE.md +177 -0
- data/README.md +2 -1
- data/contrek.gemspec +5 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cluster.cpp +32 -41
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cluster.h +2 -1
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.cpp +33 -5
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Cursor.h +3 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/InnerPolyline.cpp +1 -2
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/InnerPolyline.h +1 -3
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Polyline.cpp +16 -20
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Polyline.h +8 -3
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Shape.cpp +5 -0
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Shape.h +3 -1
- 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 +5 -3
- data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Tile.h +4 -4
- data/ext/cpp_polygon_finder/cpp_polygon_finder.cpp +2 -3
- data/lib/contrek/finder/concurrent/cluster.rb +30 -29
- data/lib/contrek/finder/concurrent/cursor.rb +35 -12
- data/lib/contrek/finder/concurrent/inner_polyline.rb +2 -3
- data/lib/contrek/finder/concurrent/polyline.rb +5 -7
- data/lib/contrek/finder/concurrent/shape.rb +7 -3
- data/lib/contrek/finder/concurrent/tile.rb +5 -4
- data/lib/contrek/version.rb +1 -1
- metadata +3 -6
- data/ext/cpp_polygon_finder/PolygonFinder/examples/example.cpp +0 -56
- data/ext/cpp_polygon_finder/PolygonFinder/images/graphs_1024x1024.png +0 -0
- data/ext/cpp_polygon_finder/PolygonFinder/images/labyrinth.png +0 -0
- data/ext/cpp_polygon_finder/PolygonFinder/images/sample_10240x10240.png +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0d4e8a9a3c94ae345edb0ecbb6087020141ad20cd2661e2b58578323a721f66e
|
|
4
|
+
data.tar.gz: ec99c9629d41d589e90ff39d8305c7e0355743b3f51caeb1e0a9da4702007fde
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9180029576fc846f3cbc8adcd68e5f68374b49fb734db8352e9ced637a447cfad93beb37cb5206084b411f4cb5cc26de9a1ac075b09f8ce1b39bb6e33fadd018
|
|
7
|
+
data.tar.gz: 163a00611440eb83d538b4dcd3f3c36745350c1d34e657fcdae069dd8c8020f36eec005264bd581e2024fca772e7f53dc0f856d35eca4717b741b0bcbaa91e5a
|
data/CHANGELOG.md
CHANGED
|
@@ -83,4 +83,12 @@ All notable changes to this project will be documented in this file.
|
|
|
83
83
|
|
|
84
84
|
## [1.2.0] - 2026-05-02
|
|
85
85
|
### Changed
|
|
86
|
-
- Further improvements have been applied to the internal parts joining algorithm using a new structural approach. This update is faster and resolves edge cases where inner parts were mistakenly classified as outer perimeters, ensuring precise contour hierarchy. The simplified logic has led to a significant reduction in codebase complexity and the removal of substantial redundant code.
|
|
86
|
+
- Further improvements have been applied to the internal parts joining algorithm using a new structural approach. This update is faster and resolves edge cases where inner parts were mistakenly classified as outer perimeters, ensuring precise contour hierarchy. The simplified logic has led to a significant reduction in codebase complexity and the removal of substantial redundant code.
|
|
87
|
+
|
|
88
|
+
## [1.2.1] - 2026-05-09
|
|
89
|
+
### Changed
|
|
90
|
+
- Some c++ optimizations.
|
|
91
|
+
|
|
92
|
+
## [1.2.2] - 2026-05-20
|
|
93
|
+
### Changed
|
|
94
|
+
- The treemap determination algorithm has been heavily optimized. Calls to the geometric routine that checks whether a newly generated inner polyline encloses other already-existing ones have been reduced to the minimum. Polylines adjacent to the shared overlap stripe are now excluded from these checks, as they are already identified during the initial polygon detection phase. The geometric approach remains unavoidable in this context and is still a performance bottleneck. It will certainly be the subject of future optimizations.
|
data/Gemfile.lock
CHANGED
data/PERFORMANCE.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# ⚡ Contrek Performance Tuning
|
|
2
|
+
|
|
3
|
+
This document describes optional dependencies and configuration tips to get the best performance out of Contrek on large images.
|
|
4
|
+
|
|
5
|
+
All optimizations are **optional** — Contrek works correctly without any of them. However, on high-resolution images (10k×10k and above), the combined effect is significant.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Benchmark Reference
|
|
10
|
+
|
|
11
|
+
> System: AMD Ryzen 7 3700X 8-Core Processor (BogoMIPS: 7199,99) on Ubuntu distro
|
|
12
|
+
> Image: 20480×20480 pixels — 8 threads / 8 tiles
|
|
13
|
+
>
|
|
14
|
+
> **Note:** Benchmarks were measured inside a VMware virtual machine.
|
|
15
|
+
|
|
16
|
+
| Configuration | Time |
|
|
17
|
+
|---|---|
|
|
18
|
+
| Baseline (no tuning) | 5316 ms |
|
|
19
|
+
| **Fully tuned** | **2938.05 ms** |
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 1. zlib-ng — Faster PNG Decoding
|
|
24
|
+
|
|
25
|
+
**Impact: ~57% reduction in PNG decode time**
|
|
26
|
+
|
|
27
|
+
Contrek uses [libspng](https://libspng.org/) for PNG decoding, which internally relies on zlib for decompression. [zlib-ng](https://github.com/zlib-ng/zlib-ng) is a high-performance, drop-in replacement for zlib that uses modern CPU instructions (AVX2, SSE4) to significantly accelerate deflate decompression.
|
|
28
|
+
|
|
29
|
+
If zlib-ng is not installed, standard zlib is used automatically — no errors, just slower PNG decoding.
|
|
30
|
+
|
|
31
|
+
### Installation
|
|
32
|
+
|
|
33
|
+
**Ubuntu / Debian** — not available in standard repos, build from source:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
git clone https://github.com/zlib-ng/zlib-ng.git
|
|
37
|
+
cd zlib-ng && mkdir build && cd build
|
|
38
|
+
cmake .. -DZLIB_COMPAT=ON -DCMAKE_BUILD_TYPE=Release
|
|
39
|
+
make -j$(nproc)
|
|
40
|
+
sudo make install
|
|
41
|
+
sudo ldconfig
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
> ⚠️ The `-DZLIB_COMPAT=ON` flag is mandatory. Without it, zlib-ng uses a different ABI and CMake's `find_package(ZLIB)` won't detect it.
|
|
45
|
+
|
|
46
|
+
**macOS:**
|
|
47
|
+
```bash
|
|
48
|
+
brew install zlib-ng
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Arch Linux:**
|
|
52
|
+
```bash
|
|
53
|
+
sudo pacman -S zlib-ng
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
After installation, rebuild Contrek — CMake will automatically detect zlib-ng in `/usr/local` and use it instead of system zlib.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 2. tcmalloc — Faster Memory Allocation
|
|
61
|
+
|
|
62
|
+
**Impact: significant reduction in allocator contention under multithreaded load**
|
|
63
|
+
|
|
64
|
+
Contrek creates and destroys large numbers of small objects during processing. Under multithreaded workloads, the standard glibc allocator serializes many of these operations, causing thread contention. [tcmalloc](https://github.com/google/tcmalloc) (Thread-Caching Malloc) is Google's high-performance allocator that maintains per-thread caches, dramatically reducing lock contention.
|
|
65
|
+
|
|
66
|
+
### Installation
|
|
67
|
+
|
|
68
|
+
**Ubuntu / Debian:**
|
|
69
|
+
```bash
|
|
70
|
+
sudo apt-get install libgoogle-perftools-dev
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**macOS:**
|
|
74
|
+
```bash
|
|
75
|
+
brew install gperftools
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
CMake will detect tcmalloc automatically. You will see this confirmation during the build:
|
|
79
|
+
```
|
|
80
|
+
-- Contrek: tcmalloc found in /usr/lib/x86_64-linux-gnu/libtcmalloc.so
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Tuning tcmalloc cache size
|
|
84
|
+
|
|
85
|
+
For large images with many threads, increasing the per-thread cache size reduces requests to the central allocator. Add this at the very beginning of your `main()`:
|
|
86
|
+
|
|
87
|
+
```cpp
|
|
88
|
+
#include <gperftools/malloc_extension.h>
|
|
89
|
+
|
|
90
|
+
int main() {
|
|
91
|
+
MallocExtension::instance()->SetNumericProperty(
|
|
92
|
+
"tcmalloc.max_total_thread_cache_bytes",
|
|
93
|
+
1024 * 1024 * 1024 // 1GB total thread cache
|
|
94
|
+
);
|
|
95
|
+
// ...
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The default is 32MB total. On systems with 16GB+ RAM, 1GB is a safe value that virtually eliminates allocator contention.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 3. Thread and Tile Configuration
|
|
104
|
+
|
|
105
|
+
**Impact: up to ~35% reduction in processing time on multi-core systems**
|
|
106
|
+
|
|
107
|
+
Contrek splits the image into vertical tiles processed in parallel. The optimal configuration depends on your hardware.
|
|
108
|
+
|
|
109
|
+
### General rule
|
|
110
|
+
|
|
111
|
+
Set both `threads` and `tiles` to the number of **physical cores** on your machine.
|
|
112
|
+
|
|
113
|
+
```cpp
|
|
114
|
+
Contrek::Config cfg;
|
|
115
|
+
cfg.threads = 8; // match your physical core count
|
|
116
|
+
cfg.tiles = 8; // same as threads for best results
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
result = Contrek.contour!(
|
|
121
|
+
png_file_path: "image.png",
|
|
122
|
+
options: {
|
|
123
|
+
number_of_threads: 8,
|
|
124
|
+
finder: { number_of_tiles: 8 }
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Why threads == tiles?
|
|
130
|
+
|
|
131
|
+
- **Fewer tiles than threads**: some cores sit idle waiting for others to finish
|
|
132
|
+
- **More tiles than threads**: merge overhead increases without adding parallelism
|
|
133
|
+
- **threads == tiles**: optimal balance between parallel scan and merge cost
|
|
134
|
+
|
|
135
|
+
Consider this depends your system. Probably is better not to saturate all cores leaving one ot two to the system and the others to Contrek. So on 8 cpu core 6 thread/tiles at maximum.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## 4. Combining All Optimizations
|
|
140
|
+
|
|
141
|
+
Install zlib-ng and tcmalloc, then configure:
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
# Ruby
|
|
145
|
+
result = Contrek.contour!(
|
|
146
|
+
png_file_path: "large_image.png",
|
|
147
|
+
options: {
|
|
148
|
+
number_of_threads: 8, # match your core count (or 1-2 less)
|
|
149
|
+
class: "value_not_matcher",
|
|
150
|
+
color: { r: 255, g: 255, b: 255, a: 255 },
|
|
151
|
+
finder: {
|
|
152
|
+
number_of_tiles: 8, # same as threads
|
|
153
|
+
compress: { uniq: true }
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
```cpp
|
|
160
|
+
// C++ standalone
|
|
161
|
+
#include <gperftools/malloc_extension.h>
|
|
162
|
+
#include "ContrekApi.h"
|
|
163
|
+
|
|
164
|
+
int main() {
|
|
165
|
+
MallocExtension::instance()->SetNumericProperty(
|
|
166
|
+
"tcmalloc.max_total_thread_cache_bytes",
|
|
167
|
+
1024 * 1024 * 1024
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
Contrek::Config cfg;
|
|
171
|
+
cfg.threads = 8;
|
|
172
|
+
cfg.tiles = 8;
|
|
173
|
+
|
|
174
|
+
auto result = Contrek::trace("large_image.png", cfg);
|
|
175
|
+
std::cout << "Time: " << result->total_time << " ms" << std::endl;
|
|
176
|
+
}
|
|
177
|
+
```
|
data/README.md
CHANGED
|
@@ -158,7 +158,8 @@ You can process from a raw stream
|
|
|
158
158
|
[{:outer=>[{:x=>5, :y=>4}, {:x=>5, :y=>5}, {:x=>8, :y=>5}, {:x=>8, :y=>4}], :inner=>[]}]
|
|
159
159
|
```
|
|
160
160
|
|
|
161
|
-
Multithreaded contour processing is supported
|
|
161
|
+
Multithreaded contour processing is supported by both the native C++ and pure Ruby implementations. When using the C++ engine (default), multithreading works as expected and fully utilizes all available cores.
|
|
162
|
+
When running the pure Ruby implementation, however, the Global Interpreter Lock (GIL) in Ruby MRI (the standard Ruby interpreter, up to at least version 3.x) prevents true parallel execution — threads are serialized even on multicore systems. Switching to JRuby or TruffleRuby would bypass this limitation, though these runtimes have not been tested with Contrek.
|
|
162
163
|
|
|
163
164
|
```ruby
|
|
164
165
|
result = Contrek.contour!(
|
data/contrek.gemspec
CHANGED
|
@@ -11,7 +11,11 @@ Gem::Specification.new do |s|
|
|
|
11
11
|
s.homepage = "https://github.com/runout77/contrek"
|
|
12
12
|
s.licenses = ["MIT", "AGPL-3.0-only"]
|
|
13
13
|
s.files = Dir.chdir(File.expand_path("..", __FILE__)) do
|
|
14
|
-
`git ls-files -z`.split("\x0").reject
|
|
14
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
15
|
+
f.match(%r{^(docs|pkg|spec)/}) ||
|
|
16
|
+
f.include?("PolygonFinder/images/") ||
|
|
17
|
+
f.include?("PolygonFinder/examples/")
|
|
18
|
+
end
|
|
15
19
|
end
|
|
16
20
|
s.metadata = {
|
|
17
21
|
"homepage_uri" => "https://github.com/runout77/contrek",
|
|
@@ -54,7 +54,7 @@ Tile* Cluster::merge_tiles() {
|
|
|
54
54
|
double tot_outer = 0;
|
|
55
55
|
CpuTimer timer;
|
|
56
56
|
|
|
57
|
-
std::
|
|
57
|
+
std::vector<Shape*> new_shapes;
|
|
58
58
|
std::vector<InnerPolyline*> all_new_inner_polylines;
|
|
59
59
|
|
|
60
60
|
timer.start();
|
|
@@ -69,7 +69,7 @@ Tile* Cluster::merge_tiles() {
|
|
|
69
69
|
tot_outer += timer.stop();
|
|
70
70
|
|
|
71
71
|
for (Tile* tile : tiles_) {
|
|
72
|
-
std::
|
|
72
|
+
std::vector<Shape*>& src = tile->shapes();
|
|
73
73
|
|
|
74
74
|
for (Shape* shape : src) {
|
|
75
75
|
if (shape->outer_polyline->is_on(Polyline::TRACKED_OUTER) || shape->outer_polyline->width() == 0) {
|
|
@@ -86,7 +86,7 @@ Tile* Cluster::merge_tiles() {
|
|
|
86
86
|
|
|
87
87
|
timer.start();
|
|
88
88
|
std::vector<InnerPolyline*> new_inners = shape->inner_polylines;
|
|
89
|
-
std::vector<InnerPolyline*> new_inner_polylines = cursor.join_inners(new_outer);
|
|
89
|
+
std::vector<InnerPolyline*> new_inner_polylines = cursor.join_inners(new_outer, treemap);
|
|
90
90
|
tot_inner += timer.stop();
|
|
91
91
|
|
|
92
92
|
for (InnerPolyline* inner_polyline : new_inner_polylines) {
|
|
@@ -94,59 +94,44 @@ Tile* Cluster::merge_tiles() {
|
|
|
94
94
|
if (treemap) {
|
|
95
95
|
inner_polyline->sequence()->compute_vertical_bounds();
|
|
96
96
|
all_new_inner_polylines.push_back(inner_polyline);
|
|
97
|
-
for (const auto orphan_inner : cursor.orphan_inners()) {
|
|
98
|
-
if (orphan_inner->recombined()) {
|
|
99
|
-
all_new_inner_polylines.push_back(orphan_inner);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
97
|
}
|
|
103
98
|
}
|
|
104
|
-
|
|
105
99
|
for (auto s : cursor.orphan_inners()) {
|
|
106
100
|
new_inners.push_back(s);
|
|
107
101
|
}
|
|
108
|
-
|
|
109
102
|
Polyline* polyline = tile->shapes_pool->acquire_polyline(tile, new_outer->to_vector(), std::nullopt);
|
|
110
103
|
Shape* inserting_new_shape = tile->shapes_pool->acquire_shape(polyline, new_inners);
|
|
111
|
-
|
|
112
104
|
new_shapes.push_back(inserting_new_shape);
|
|
113
105
|
polyline->shape = inserting_new_shape;
|
|
114
|
-
inserting_new_shape->set_parent_shape(shape->parent_shape());
|
|
115
106
|
|
|
116
107
|
for (InnerPolyline* inner_polyline : new_inner_polylines) {
|
|
117
108
|
inner_polyline->sequence()->shape = inserting_new_shape;
|
|
118
109
|
}
|
|
119
|
-
|
|
120
110
|
if (treemap) {
|
|
121
111
|
for (const auto merged_shape : cursor.shapes_sequence()) {
|
|
122
112
|
merged_shape->merged_to_shape = inserting_new_shape;
|
|
123
113
|
}
|
|
124
|
-
|
|
114
|
+
InnerPolyline* inside_inner_polyline = shape->outer_polyline->inside_inner_polyline;
|
|
115
|
+
if (inside_inner_polyline) {
|
|
116
|
+
assign_ancestry(inserting_new_shape, inside_inner_polyline);
|
|
117
|
+
}
|
|
125
118
|
}
|
|
126
|
-
|
|
127
119
|
} else {
|
|
128
|
-
if (treemap
|
|
129
|
-
|
|
120
|
+
if (treemap) {
|
|
121
|
+
if (shape->fixed) {
|
|
122
|
+
Shape* ms = shape->parent_shape()->merged_to_shape;
|
|
123
|
+
if (ms) {
|
|
124
|
+
shape->set_parent_shape(ms);
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
is_children(shape, all_new_inner_polylines);
|
|
128
|
+
}
|
|
130
129
|
}
|
|
131
130
|
new_shapes.push_back(shape);
|
|
132
131
|
}
|
|
133
132
|
}
|
|
134
133
|
}
|
|
135
134
|
|
|
136
|
-
if (treemap) {
|
|
137
|
-
for (Tile* tile : tiles_) {
|
|
138
|
-
for (Shape* shape : tile->shapes()) {
|
|
139
|
-
Shape* parent = shape->parent_shape();
|
|
140
|
-
while (parent && parent->merged_to_shape != nullptr) {
|
|
141
|
-
parent = parent->merged_to_shape;
|
|
142
|
-
}
|
|
143
|
-
if (parent != shape->parent_shape()) {
|
|
144
|
-
shape->set_parent_shape(parent);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
135
|
double past_tot_outer = tiles_.front()->benchmarks.outer + tiles_.back()->benchmarks.outer;
|
|
151
136
|
double past_tot_inner = tiles_.front()->benchmarks.inner + tiles_.back()->benchmarks.inner;
|
|
152
137
|
|
|
@@ -165,16 +150,22 @@ Tile* Cluster::merge_tiles() {
|
|
|
165
150
|
return tile;
|
|
166
151
|
}
|
|
167
152
|
|
|
168
|
-
void Cluster::assign_ancestry(Shape *shape,
|
|
169
|
-
{
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
153
|
+
void Cluster::assign_ancestry(Shape *shape, InnerPolyline* inner_polyline)
|
|
154
|
+
{ shape->set_parent_shape(inner_polyline->sequence()->shape);
|
|
155
|
+
shape->parent_inner_polyline = inner_polyline;
|
|
156
|
+
shape->fixed = true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
void Cluster::is_children(Shape* shape, std::vector<InnerPolyline*> inner_polylines) {
|
|
160
|
+
int shape_max_y = shape->outer_polyline->max_y();
|
|
161
|
+
int shape_min_y = shape->outer_polyline->min_y();
|
|
162
|
+
for (InnerPolyline* inner_polyline : inner_polylines) {
|
|
163
|
+
Bounds bounds = inner_polyline->vertical_bounds();
|
|
164
|
+
int min_y = bounds.min;
|
|
165
|
+
int max_y = bounds.max;
|
|
166
|
+
if (shape_max_y < min_y || shape_min_y > max_y ) continue;
|
|
167
|
+
if (shape->outer_polyline->within(inner_polyline->raw())) {
|
|
168
|
+
assign_ancestry(shape, inner_polyline);
|
|
178
169
|
}
|
|
179
170
|
}
|
|
180
171
|
}
|
|
@@ -21,7 +21,8 @@ class Cluster {
|
|
|
21
21
|
Finder *finder;
|
|
22
22
|
std::vector<Tile*> tiles_;
|
|
23
23
|
Hub *hub_ = nullptr;
|
|
24
|
-
void assign_ancestry(Shape *shape,
|
|
24
|
+
void assign_ancestry(Shape *shape, InnerPolyline* inner_polyline);
|
|
25
|
+
void is_children(Shape* shape, std::vector<InnerPolyline*> inner_polylines);
|
|
25
26
|
|
|
26
27
|
public:
|
|
27
28
|
Cluster(Finder *finder, int height, int start_x, int end_x);
|
|
@@ -129,7 +129,7 @@ void Cursor::traverse_outer(Part* act_part,
|
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
std::vector<InnerPolyline*> Cursor::join_inners(Sequence* outer_seq) {
|
|
132
|
+
std::vector<InnerPolyline*> Cursor::join_inners(Sequence* outer_seq, bool treemap) {
|
|
133
133
|
std::vector<InnerPolyline*> return_inner_polylines;
|
|
134
134
|
std::vector<Shape*> processing_queue = shapes_sequence_;
|
|
135
135
|
for (size_t i = 0; i < shapes_sequence_.size(); ++i)
|
|
@@ -138,11 +138,12 @@ std::vector<InnerPolyline*> Cursor::join_inners(Sequence* outer_seq) {
|
|
|
138
138
|
for (Part* part : polyline->parts())
|
|
139
139
|
{ if (part->innerable())
|
|
140
140
|
{ std::vector<Part*> all_parts;
|
|
141
|
+
std::vector<EndPoint*> tracked_end_points;
|
|
141
142
|
Bounds bounds{
|
|
142
143
|
.min = polyline->max_y(),
|
|
143
144
|
.max = 0
|
|
144
145
|
};
|
|
145
|
-
traverse_inner(part, all_parts, bounds);
|
|
146
|
+
traverse_inner(part, all_parts, bounds, tracked_end_points);
|
|
146
147
|
Sequence* retme_sequence = shape->outer_polyline->tile->shapes_pool->acquire_sequence();
|
|
147
148
|
for (Part* part : all_parts)
|
|
148
149
|
{ part->touch();
|
|
@@ -157,7 +158,11 @@ std::vector<InnerPolyline*> Cursor::join_inners(Sequence* outer_seq) {
|
|
|
157
158
|
});
|
|
158
159
|
}
|
|
159
160
|
if (retme_sequence->is_not_vertical()) {
|
|
160
|
-
|
|
161
|
+
InnerPolyline* inner_polyline = polyline->tile->shapes_pool->acquire_inner_polyline(retme_sequence);
|
|
162
|
+
return_inner_polylines.push_back(inner_polyline);
|
|
163
|
+
if (treemap) {
|
|
164
|
+
mark_children(tracked_end_points, polyline, inner_polyline);
|
|
165
|
+
}
|
|
161
166
|
}
|
|
162
167
|
}
|
|
163
168
|
}
|
|
@@ -165,7 +170,26 @@ std::vector<InnerPolyline*> Cursor::join_inners(Sequence* outer_seq) {
|
|
|
165
170
|
return(return_inner_polylines);
|
|
166
171
|
}
|
|
167
172
|
|
|
168
|
-
void Cursor::
|
|
173
|
+
void Cursor::mark_children(std::vector<EndPoint*>& end_points, const Polyline* outer_polyline, InnerPolyline* inner_polyline) {
|
|
174
|
+
for (size_t i = 0; i + 1 < end_points.size(); i += 2) {
|
|
175
|
+
const auto& a = end_points[i];
|
|
176
|
+
const auto& b = end_points[i + 1];
|
|
177
|
+
auto [y_min, y_max] = std::minmax(a->get_point()->y, b->get_point()->y);
|
|
178
|
+
for (int y = y_min + 1; y < y_max; ++y) {
|
|
179
|
+
EndPoint* end_point = this->cluster.hub()->get(y);
|
|
180
|
+
if (end_point) {
|
|
181
|
+
for (auto part_p : end_point->queues())
|
|
182
|
+
{ Part* part = static_cast<Part*>(part_p);
|
|
183
|
+
if (part->polyline() != outer_polyline) {
|
|
184
|
+
part->polyline()->inside_inner_polyline = inner_polyline;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
void Cursor::traverse_inner(Part* act_part, std::vector<Part*>& all_parts, Bounds& bounds, std::vector<EndPoint*>& tracked_end_points) {
|
|
169
193
|
PartPool& pool = act_part->polyline()->tile->cluster->parts_pool;
|
|
170
194
|
while (act_part != nullptr) {
|
|
171
195
|
if (!all_parts.empty() && act_part == all_parts.front()) return;
|
|
@@ -186,13 +210,17 @@ void Cursor::traverse_inner(Part* act_part, std::vector<Part*>& all_parts, Bound
|
|
|
186
210
|
all_parts.push_back(act_part);
|
|
187
211
|
} else {
|
|
188
212
|
if (act_part->head)
|
|
189
|
-
{
|
|
213
|
+
{ EndPoint* current_end_point = static_cast<Position*>(act_part->head)->end_point();
|
|
214
|
+
for (auto dest_part_p : current_end_point->queues()) {
|
|
190
215
|
Part* dest_part = static_cast<Part*>(dest_part_p);
|
|
191
216
|
if (dest_part->polyline()->tile == act_part->polyline()->tile) {
|
|
192
217
|
continue;
|
|
193
218
|
}
|
|
194
219
|
int dest_part_versus = dest_part->versus();
|
|
195
220
|
if (dest_part_versus != 0 && dest_part_versus == act_part->versus()) continue;
|
|
221
|
+
|
|
222
|
+
tracked_end_points.push_back(current_end_point);
|
|
223
|
+
|
|
196
224
|
std::vector<EndPoint*> link_seq = dest_part->continuum_to(*act_part);
|
|
197
225
|
if (!link_seq.empty()) {
|
|
198
226
|
Part* ins_part = pool.acquire(Part::ADDED, act_part->polyline());
|
|
@@ -21,7 +21,7 @@ class Cursor {
|
|
|
21
21
|
public:
|
|
22
22
|
Cursor(Cluster& cluster, Shape* shape);
|
|
23
23
|
Sequence* join_outers();
|
|
24
|
-
std::vector<InnerPolyline*> join_inners(Sequence* outer_seq);
|
|
24
|
+
std::vector<InnerPolyline*> join_inners(Sequence* outer_seq, bool treemap);
|
|
25
25
|
std::list<InnerPolyline*> orphan_inners() { return orphan_inners_; }
|
|
26
26
|
const std::vector<Shape*>& shapes_sequence() const { return shapes_sequence_; }
|
|
27
27
|
|
|
@@ -35,6 +35,7 @@ class Cursor {
|
|
|
35
35
|
std::vector<Part*>& all_parts,
|
|
36
36
|
std::vector<Shape*>& shapes_sequence,
|
|
37
37
|
Sequence* outer_joined_polyline);
|
|
38
|
-
void traverse_inner(Part* act_part, std::vector<Part*> &all_parts, Bounds& bounds);
|
|
38
|
+
void traverse_inner(Part* act_part, std::vector<Part*> &all_parts, Bounds& bounds, std::vector<EndPoint*>& tracked_end_points);
|
|
39
39
|
std::vector<Shape*> connect_missings(std::vector<Shape*> shapes_sequence, std::vector<Shape*> missing_shapes);
|
|
40
|
+
void mark_children(std::vector<EndPoint*>& end_points, const Polyline* outer_polyline, InnerPolyline* inner_polyline);
|
|
40
41
|
};
|
|
@@ -11,9 +11,8 @@
|
|
|
11
11
|
#include <vector>
|
|
12
12
|
#include "InnerPolyline.h"
|
|
13
13
|
|
|
14
|
-
InnerPolyline::InnerPolyline(std::vector<Point*> raw_coordinates, Shape* shape
|
|
14
|
+
InnerPolyline::InnerPolyline(std::vector<Point*> raw_coordinates, Shape* shape)
|
|
15
15
|
: raw_coordinates_(std::move(raw_coordinates)),
|
|
16
|
-
recombined_(recombined),
|
|
17
16
|
shape_(shape) {}
|
|
18
17
|
InnerPolyline::InnerPolyline(Sequence* sequence)
|
|
19
18
|
: sequence_(sequence) {
|
|
@@ -14,16 +14,14 @@
|
|
|
14
14
|
|
|
15
15
|
class InnerPolyline {
|
|
16
16
|
public:
|
|
17
|
-
explicit InnerPolyline(std::vector<Point*> raw_coordinates, Shape* shape
|
|
17
|
+
explicit InnerPolyline(std::vector<Point*> raw_coordinates, Shape* shape);
|
|
18
18
|
explicit InnerPolyline(Sequence* sequence);
|
|
19
19
|
std::vector<Point*>& raw();
|
|
20
20
|
Sequence* sequence() { return this->sequence_; }
|
|
21
21
|
Bounds& vertical_bounds();
|
|
22
|
-
bool recombined() { return this->recombined_; }
|
|
23
22
|
Shape* shape();
|
|
24
23
|
Shape* assigned_shape = nullptr;
|
|
25
24
|
private:
|
|
26
|
-
bool recombined_ = false;
|
|
27
25
|
std::vector<Point*> raw_coordinates_;
|
|
28
26
|
Sequence* sequence_ = nullptr;
|
|
29
27
|
Shape* shape_;
|
|
@@ -22,15 +22,12 @@ Polyline::Polyline(Tile* tile, const std::vector<Point*>& polygon, const std::op
|
|
|
22
22
|
{ if (bounds.has_value()) {
|
|
23
23
|
min_x = bounds->min_x;
|
|
24
24
|
max_x = bounds->max_x;
|
|
25
|
-
|
|
25
|
+
min_y_ = bounds->min_y;
|
|
26
26
|
max_y_ = bounds->max_y;
|
|
27
27
|
} else {
|
|
28
28
|
this->find_boundary(); // TODO(ema): optimize when merging the bounds are the sum of the previouses
|
|
29
29
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
bool Polyline::vert_intersect(Polyline& other) {
|
|
33
|
-
return( !(this->max_y_ < other.min_y || other.max_y_ < this->min_y));
|
|
30
|
+
this->name = tile->shapes().size();
|
|
34
31
|
}
|
|
35
32
|
|
|
36
33
|
int Polyline::width() {
|
|
@@ -46,7 +43,7 @@ void Polyline::find_boundary() {
|
|
|
46
43
|
if (raw_.empty()) return;
|
|
47
44
|
min_x = std::numeric_limits<int>::max();
|
|
48
45
|
max_x = -std::numeric_limits<int>::max();
|
|
49
|
-
|
|
46
|
+
min_y_ = std::numeric_limits<int>::max();
|
|
50
47
|
max_y_ = -std::numeric_limits<int>::max();
|
|
51
48
|
for (Point* p : raw_) {
|
|
52
49
|
if (!p) continue;
|
|
@@ -54,7 +51,7 @@ void Polyline::find_boundary() {
|
|
|
54
51
|
int y = p->y;
|
|
55
52
|
if (x < min_x) min_x = x;
|
|
56
53
|
if (x > max_x) max_x = x;
|
|
57
|
-
if (y <
|
|
54
|
+
if (y < min_y_) min_y_ = y;
|
|
58
55
|
if (y > max_y_) max_y_ = y;
|
|
59
56
|
}
|
|
60
57
|
}
|
|
@@ -67,20 +64,8 @@ bool Polyline::is_empty() {
|
|
|
67
64
|
return raw_.empty();
|
|
68
65
|
}
|
|
69
66
|
|
|
70
|
-
std::string Polyline::info() {
|
|
71
|
-
Shape* shape = this->shape;
|
|
72
|
-
size_t part_index = 0;
|
|
73
|
-
auto it = std::find(this->tile->shapes().begin(), this->tile->shapes().end(), shape);
|
|
74
|
-
if (it != this->tile->shapes().end()) {
|
|
75
|
-
part_index = std::distance(this->tile->shapes().begin(), it);
|
|
76
|
-
}
|
|
77
|
-
std::stringstream ss;
|
|
78
|
-
ss << "b" << this->tile->name() << " S" << part_index;
|
|
79
|
-
return ss.str();
|
|
80
|
-
}
|
|
81
|
-
|
|
82
67
|
bool Polyline::vert_bounds_intersect(Bounds& vertical_bounds)
|
|
83
|
-
{ return !(this->max_y_ < vertical_bounds.min || vertical_bounds.max < this->
|
|
68
|
+
{ return !(this->max_y_ < vertical_bounds.min || vertical_bounds.max < this->min_y_);
|
|
84
69
|
}
|
|
85
70
|
|
|
86
71
|
bool Polyline::within(std::vector<Point*>& points) {
|
|
@@ -99,3 +84,14 @@ bool Polyline::within(std::vector<Point*>& points) {
|
|
|
99
84
|
}
|
|
100
85
|
return inside;
|
|
101
86
|
}
|
|
87
|
+
|
|
88
|
+
std::string Polyline::named() {
|
|
89
|
+
if (this->named_.empty()) {
|
|
90
|
+
std::stringstream ss;
|
|
91
|
+
ss << "t" << this->tile->name() << "s" << this->name;
|
|
92
|
+
if (this->boundary()) ss << "B";
|
|
93
|
+
return ss.str();
|
|
94
|
+
} else {
|
|
95
|
+
return this->named_;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
class Tile;
|
|
24
24
|
class Shape;
|
|
25
25
|
class Point;
|
|
26
|
+
class InnerPolyline;
|
|
26
27
|
|
|
27
28
|
class Polyline : public Partitionable {
|
|
28
29
|
public:
|
|
@@ -42,17 +43,21 @@ class Polyline : public Partitionable {
|
|
|
42
43
|
std::vector<Point*> raw() const { return raw_; }
|
|
43
44
|
const std::vector<Part*>& parts() const { return parts_; }
|
|
44
45
|
const int max_y() const { return max_y_; }
|
|
46
|
+
const int min_y() const { return min_y_; }
|
|
45
47
|
void clear();
|
|
46
48
|
bool is_empty();
|
|
47
|
-
bool vert_intersect(Polyline& other);
|
|
48
49
|
bool any_ancients = false;
|
|
49
|
-
std::string info();
|
|
50
50
|
bool vert_bounds_intersect(Bounds& vertical_bounds);
|
|
51
51
|
bool within(std::vector<Point*>& points);
|
|
52
|
+
InnerPolyline* inside_inner_polyline = nullptr;
|
|
53
|
+
std::string named();
|
|
54
|
+
void set_named(std::string force_named) { this->named_ = force_named; }
|
|
52
55
|
|
|
53
56
|
private:
|
|
54
57
|
std::vector<Point*> raw_;
|
|
55
|
-
int min_x, max_x,
|
|
58
|
+
int min_x, max_x, min_y_, max_y_;
|
|
56
59
|
void find_boundary();
|
|
57
60
|
uint32_t flags_ = 0;
|
|
61
|
+
std::string named_;
|
|
62
|
+
int name;
|
|
58
63
|
};
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
#include <list>
|
|
11
11
|
#include <vector>
|
|
12
|
+
#include <string>
|
|
12
13
|
#include "Shape.h"
|
|
13
14
|
#include "Polyline.h"
|
|
14
15
|
|
|
@@ -34,3 +35,7 @@ void Shape::set_parent_shape(Shape* shape) {
|
|
|
34
35
|
{ shape->children_shapes.push_back(this);
|
|
35
36
|
}
|
|
36
37
|
}
|
|
38
|
+
|
|
39
|
+
std::string Shape::name() {
|
|
40
|
+
return(this->outer_polyline->named());
|
|
41
|
+
}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
#include <list>
|
|
12
12
|
#include <iostream>
|
|
13
13
|
#include <vector>
|
|
14
|
+
#include <string>
|
|
14
15
|
#include "InnerPolyline.h"
|
|
15
16
|
|
|
16
17
|
class Point;
|
|
@@ -24,9 +25,10 @@ class Shape {
|
|
|
24
25
|
InnerPolyline* parent_inner_polyline = nullptr;
|
|
25
26
|
std::vector<Shape*> children_shapes;
|
|
26
27
|
void clear_inner();
|
|
27
|
-
bool
|
|
28
|
+
bool fixed = false;
|
|
28
29
|
Shape* parent_shape() { return parent_shape_; }
|
|
29
30
|
void set_parent_shape(Shape*);
|
|
31
|
+
std::string name();
|
|
30
32
|
private:
|
|
31
33
|
Shape* parent_shape_ = nullptr;
|
|
32
34
|
};
|
|
@@ -17,8 +17,8 @@ Shape* ShapePool::acquire_shape(Polyline* outer_polyline, const std::vector<Inne
|
|
|
17
17
|
return shape;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
InnerPolyline* ShapePool::acquire_inner_polyline(std::vector<Point*> coords, Shape* shape
|
|
21
|
-
inner_polylines_storage.emplace_back(coords, shape
|
|
20
|
+
InnerPolyline* ShapePool::acquire_inner_polyline(std::vector<Point*> coords, Shape* shape) {
|
|
21
|
+
inner_polylines_storage.emplace_back(coords, shape);
|
|
22
22
|
InnerPolyline* ip = &inner_polylines_storage.back();
|
|
23
23
|
return ip;
|
|
24
24
|
}
|
|
@@ -27,7 +27,7 @@ class ShapePool {
|
|
|
27
27
|
|
|
28
28
|
public:
|
|
29
29
|
Shape* acquire_shape(Polyline* outer_polyline, const std::vector<InnerPolyline*>& inner_polylines);
|
|
30
|
-
InnerPolyline* acquire_inner_polyline(std::vector<Point*> coords, Shape* s
|
|
30
|
+
InnerPolyline* acquire_inner_polyline(std::vector<Point*> coords, Shape* s);
|
|
31
31
|
InnerPolyline* acquire_inner_polyline(Sequence* seq);
|
|
32
32
|
Sequence* acquire_sequence();
|
|
33
33
|
Polyline* acquire_polyline(Tile* tile, const std::vector<Point*>& polygon, const std::optional<RectBounds>& bounds);
|
|
@@ -61,7 +61,7 @@ void Tile::assign_raw_polygons(const std::list<Polygon>& raw_polylines, const st
|
|
|
61
61
|
{ Polyline* polyline = this->shapes_pool->acquire_polyline(this, raw_polyline.outer, raw_polyline.bounds);
|
|
62
62
|
std::vector<InnerPolyline*> inner_polylines_list;
|
|
63
63
|
for (auto& raw_points : raw_polyline.inner) {
|
|
64
|
-
inner_polylines_list.push_back(this->shapes_pool->acquire_inner_polyline(raw_points, nullptr
|
|
64
|
+
inner_polylines_list.push_back(this->shapes_pool->acquire_inner_polyline(raw_points, nullptr));
|
|
65
65
|
}
|
|
66
66
|
Shape* shape = this->shapes_pool->acquire_shape(polyline, inner_polylines_list);
|
|
67
67
|
polyline->shape = shape;
|
|
@@ -75,6 +75,7 @@ void Tile::assign_raw_polygons(const std::list<Polygon>& raw_polylines, const st
|
|
|
75
75
|
if (it != shapes_map.end()) {
|
|
76
76
|
Shape* parent = it->second;
|
|
77
77
|
shape->set_parent_shape(parent);
|
|
78
|
+
shape->fixed = true;
|
|
78
79
|
shape->parent_inner_polyline = parent->inner_polylines[treemap_entry.second];
|
|
79
80
|
}
|
|
80
81
|
}
|
|
@@ -84,7 +85,7 @@ void Tile::assign_raw_polygons(const std::list<Polygon>& raw_polylines, const st
|
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
void Tile::assign_shapes(std::
|
|
88
|
+
void Tile::assign_shapes(std::vector<Shape*>& shapes) {
|
|
88
89
|
for (Shape *shape : shapes) {
|
|
89
90
|
shape->outer_polyline->tile = this;
|
|
90
91
|
}
|
|
@@ -135,7 +136,8 @@ std::vector<std::pair<int, int>> Tile::compute_treemap()
|
|
|
135
136
|
for (auto* shape : this->shapes_) {
|
|
136
137
|
if (shape->outer_polyline->is_empty()) continue;
|
|
137
138
|
if (shape->parent_shape() != nullptr) {
|
|
138
|
-
|
|
139
|
+
auto p_it = shapes_map.find(shape->parent_shape());
|
|
140
|
+
int p_idx = (p_it != shapes_map.end()) ? p_it->second : -1;
|
|
139
141
|
const auto& inners = shape->parent_shape()->inner_polylines;
|
|
140
142
|
auto it = std::find(inners.begin(), inners.end(), shape->parent_inner_polyline);
|
|
141
143
|
int inner_idx = static_cast<int>(std::distance(inners.begin(), it));
|
|
@@ -30,7 +30,7 @@ class Tile {
|
|
|
30
30
|
int start_x_;
|
|
31
31
|
int end_x_;
|
|
32
32
|
std::string name_;
|
|
33
|
-
std::
|
|
33
|
+
std::vector<Shape*> shapes_;
|
|
34
34
|
|
|
35
35
|
public:
|
|
36
36
|
Tile(Finder *finder, int start_x, int end_x, std::string name, const Benchmarks& b);
|
|
@@ -42,15 +42,15 @@ class Tile {
|
|
|
42
42
|
int start_x() const { return start_x_; }
|
|
43
43
|
int end_x() const { return end_x_; }
|
|
44
44
|
std::string name() const { return name_; }
|
|
45
|
-
const std::
|
|
46
|
-
std::
|
|
45
|
+
const std::vector<Shape*>& shapes() const { return shapes_; }
|
|
46
|
+
std::vector<Shape*>& shapes() { return shapes_; }
|
|
47
47
|
bool whole();
|
|
48
48
|
bool left();
|
|
49
49
|
bool right();
|
|
50
50
|
void initial_process(ClippedPolygonFinder *finder);
|
|
51
51
|
void info();
|
|
52
52
|
bool tg_border(const Point& coord);
|
|
53
|
-
void assign_shapes(std::
|
|
53
|
+
void assign_shapes(std::vector<Shape*>& shapes);
|
|
54
54
|
void assign_raw_polygons(const std::list<Polygon>& raw_polylines, const std::vector<std::pair<int, int>>& treemap);
|
|
55
55
|
std::list<Polygon> to_raw_polygons();
|
|
56
56
|
std::vector<std::pair<int, int>> compute_treemap();
|
|
@@ -291,9 +291,8 @@ extern "C"
|
|
|
291
291
|
void Init_cpp_polygon_finder() {
|
|
292
292
|
#ifdef HAVE_TCMALLOC
|
|
293
293
|
MallocExtension::instance()->SetNumericProperty(
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
);
|
|
294
|
+
"tcmalloc.max_total_thread_cache_bytes",
|
|
295
|
+
1024 * 1024 * 1024);
|
|
297
296
|
#endif
|
|
298
297
|
|
|
299
298
|
Data_Type<Bitmap> rb_cBitmap =
|
|
@@ -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:
|
|
9
|
+
@hub = Hub.new(height:)
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def add(tile)
|
|
@@ -28,6 +28,7 @@ module Contrek
|
|
|
28
28
|
|
|
29
29
|
new_shapes = []
|
|
30
30
|
all_new_inner_polylines = []
|
|
31
|
+
|
|
31
32
|
tot_outer += Benchmark.measure do
|
|
32
33
|
@tiles.each do |tile|
|
|
33
34
|
tile.shapes.each do |shape|
|
|
@@ -54,13 +55,12 @@ module Contrek
|
|
|
54
55
|
new_inners = shape.inner_polylines
|
|
55
56
|
new_inner_polylines = []
|
|
56
57
|
tot_inner += Benchmark.measure do
|
|
57
|
-
new_inner_polylines = cursor.join_inners!(new_outer)
|
|
58
|
+
new_inner_polylines = cursor.join_inners!(new_outer, treemap)
|
|
58
59
|
new_inners += new_inner_polylines
|
|
59
60
|
if treemap
|
|
60
|
-
new_inner_polylines.each
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
all_new_inner_polylines << orphan_inner if orphan_inner.recombined
|
|
61
|
+
new_inner_polylines.each do |inner_polyline|
|
|
62
|
+
inner_polyline.sequence.compute_vertical_bounds!
|
|
63
|
+
all_new_inner_polylines += new_inner_polylines
|
|
64
64
|
end
|
|
65
65
|
end
|
|
66
66
|
new_inners += cursor.orphan_inners
|
|
@@ -70,7 +70,6 @@ module Contrek
|
|
|
70
70
|
inserting_new_shape = Shape.init_by(polyline, new_inners)
|
|
71
71
|
new_shapes << inserting_new_shape
|
|
72
72
|
polyline.shape = inserting_new_shape
|
|
73
|
-
inserting_new_shape.set_parent_shape(shape.parent_shape)
|
|
74
73
|
|
|
75
74
|
new_inner_polylines.each { |inner_polyline| inner_polyline.sequence.shape = inserting_new_shape }
|
|
76
75
|
|
|
@@ -78,23 +77,20 @@ module Contrek
|
|
|
78
77
|
cursor.shapes_sequence.each do |merged_shape|
|
|
79
78
|
merged_shape.merged_to_shape = inserting_new_shape
|
|
80
79
|
end
|
|
81
|
-
|
|
80
|
+
if shape.outer_polyline.inside_inner_polyline
|
|
81
|
+
assign_ancestry(inserting_new_shape, shape.outer_polyline.inside_inner_polyline)
|
|
82
|
+
end
|
|
82
83
|
end
|
|
83
84
|
else
|
|
84
|
-
if treemap
|
|
85
|
-
|
|
85
|
+
if treemap
|
|
86
|
+
if shape.fixed
|
|
87
|
+
shape.set_parent_shape(shape.parent_shape.merged_to_shape) if shape.parent_shape.merged_to_shape
|
|
88
|
+
else
|
|
89
|
+
is_children(shape, all_new_inner_polylines)
|
|
90
|
+
end
|
|
86
91
|
end
|
|
87
|
-
new_shapes << shape
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
end
|
|
91
92
|
|
|
92
|
-
|
|
93
|
-
@tiles.each do |tile|
|
|
94
|
-
tile.shapes.each do |shape|
|
|
95
|
-
if (merged_to_shape = shape.parent_shape&.merged_to_shape)
|
|
96
|
-
shape.set_parent_shape(merged_to_shape)
|
|
97
|
-
end
|
|
93
|
+
new_shapes << shape
|
|
98
94
|
end
|
|
99
95
|
end
|
|
100
96
|
end
|
|
@@ -116,16 +112,21 @@ module Contrek
|
|
|
116
112
|
|
|
117
113
|
private
|
|
118
114
|
|
|
119
|
-
def assign_ancestry(shape,
|
|
115
|
+
def assign_ancestry(shape, inner_polyline)
|
|
116
|
+
shape.set_parent_shape(inner_polyline.sequence.shape)
|
|
117
|
+
shape.parent_inner_polyline = inner_polyline
|
|
118
|
+
shape.fixed = true
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def is_children(shape, inner_polylines)
|
|
120
122
|
inner_polylines.each do |inner_polyline|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
end
|
|
123
|
+
bounds = inner_polyline.vertical_bounds
|
|
124
|
+
min_y = bounds[:min]
|
|
125
|
+
max_y = bounds[:max]
|
|
126
|
+
next if shape.outer_polyline.get_bounds[:max_y] < min_y ||
|
|
127
|
+
shape.outer_polyline.get_bounds[:min_y] > max_y
|
|
128
|
+
if shape.outer_polyline.within?(inner_polyline.raw)
|
|
129
|
+
assign_ancestry(shape, inner_polyline)
|
|
129
130
|
end
|
|
130
131
|
end
|
|
131
132
|
end
|
|
@@ -4,7 +4,7 @@ module Contrek
|
|
|
4
4
|
attr_reader :orphan_inners, :shapes_sequence
|
|
5
5
|
|
|
6
6
|
def initialize(cluster:, shape:)
|
|
7
|
-
@shapes_sequence =
|
|
7
|
+
@shapes_sequence = [shape]
|
|
8
8
|
@cluster = cluster
|
|
9
9
|
@outer_polyline = shape.outer_polyline
|
|
10
10
|
@orphan_inners = []
|
|
@@ -38,18 +38,19 @@ module Contrek
|
|
|
38
38
|
outer_joined_polyline
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
def join_inners!(outer_seq)
|
|
41
|
+
def join_inners!(outer_seq, treemap)
|
|
42
42
|
return_inner_polylines = []
|
|
43
|
+
shape_index = 0
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
@processing_shapes.each do |shape|
|
|
45
|
+
while shape_index < @shapes_sequence.size
|
|
46
|
+
shape = @shapes_sequence[shape_index]
|
|
47
47
|
polyline = shape.outer_polyline
|
|
48
48
|
polyline.parts.each do |part|
|
|
49
49
|
if part.innerable?
|
|
50
50
|
all_parts = []
|
|
51
|
+
tracked_end_points = []
|
|
51
52
|
bounds = {min: polyline.max_y, max: 0}
|
|
52
|
-
traverse_inner(part, all_parts, bounds)
|
|
53
|
+
traverse_inner(part, all_parts, bounds, tracked_end_points)
|
|
53
54
|
range_of_bounds = (bounds[:min]..bounds[:max])
|
|
54
55
|
|
|
55
56
|
retme_sequence = Sequence.new
|
|
@@ -62,16 +63,36 @@ module Contrek
|
|
|
62
63
|
end
|
|
63
64
|
end
|
|
64
65
|
if retme_sequence.is_not_vertical
|
|
65
|
-
|
|
66
|
+
inner_polyline = InnerPolyline.new(sequence: retme_sequence)
|
|
67
|
+
return_inner_polylines << inner_polyline
|
|
68
|
+
mark_children(tracked_end_points, polyline, inner_polyline) if treemap
|
|
66
69
|
end
|
|
67
70
|
end
|
|
68
71
|
end
|
|
72
|
+
shape_index += 1
|
|
69
73
|
end
|
|
70
74
|
return_inner_polylines
|
|
71
75
|
end
|
|
72
76
|
|
|
73
77
|
private
|
|
74
78
|
|
|
79
|
+
# finds each part (and relative polyline) inscribed between two end_points and sets the
|
|
80
|
+
# founded inner_polyline which be later used to define in which parent hole is placed.
|
|
81
|
+
def mark_children(end_points, outer_polyline, inner_polyline)
|
|
82
|
+
end_points.each_slice(2) do |a, b|
|
|
83
|
+
range = [a.position[:y], b.position[:y]].sort
|
|
84
|
+
(range[0] + 1).upto(range[1] - 1) do |y|
|
|
85
|
+
if (end_point = @cluster.hub.payloads[y])
|
|
86
|
+
end_point.queues.each do |part|
|
|
87
|
+
if part.polyline != outer_polyline
|
|
88
|
+
part.polyline.inside_inner_polyline = inner_polyline
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
75
96
|
# rubocop:disable Lint/NonLocalExitFromIterator
|
|
76
97
|
def traverse_outer(act_part, all_parts, shapes_sequence, outer_joined_polyline)
|
|
77
98
|
last_part = all_parts.last
|
|
@@ -104,7 +125,7 @@ module Contrek
|
|
|
104
125
|
cont = false if map.size == 1 && map.first == Part::SEAM
|
|
105
126
|
end
|
|
106
127
|
if cont
|
|
107
|
-
shapes_sequence.
|
|
128
|
+
shapes_sequence << part.polyline.shape if !shapes_sequence.include?(part.polyline.shape)
|
|
108
129
|
part.next_position(new_position)
|
|
109
130
|
part.dead_end = true
|
|
110
131
|
traverse_outer(part, all_parts, shapes_sequence, outer_joined_polyline)
|
|
@@ -120,7 +141,7 @@ module Contrek
|
|
|
120
141
|
traverse_outer(act_part.circular_next, all_parts, shapes_sequence, outer_joined_polyline)
|
|
121
142
|
end
|
|
122
143
|
|
|
123
|
-
def traverse_inner(act_part, all_parts, bounds)
|
|
144
|
+
def traverse_inner(act_part, all_parts, bounds, tracked_end_points)
|
|
124
145
|
return if act_part == all_parts.first
|
|
125
146
|
|
|
126
147
|
if act_part.size > 0
|
|
@@ -140,6 +161,8 @@ module Contrek
|
|
|
140
161
|
dest_part_versus = dest_part.versus
|
|
141
162
|
next if dest_part_versus != 0 && dest_part_versus == act_part.versus
|
|
142
163
|
|
|
164
|
+
tracked_end_points << act_part.head.end_point
|
|
165
|
+
|
|
143
166
|
link_seq = dest_part.continuum_to?(act_part)
|
|
144
167
|
if link_seq.any?
|
|
145
168
|
ins_part = Part.new(Part::ADDED, act_part.polyline)
|
|
@@ -150,13 +173,13 @@ module Contrek
|
|
|
150
173
|
end
|
|
151
174
|
shape = dest_part.polyline.shape
|
|
152
175
|
if !dest_part.polyline.on?(Polyline::TRACKED_OUTER)
|
|
153
|
-
@
|
|
176
|
+
@shapes_sequence << shape
|
|
154
177
|
@orphan_inners += shape.inner_polylines
|
|
155
178
|
end
|
|
156
179
|
dest_part.polyline.turn_on(Polyline::TRACKED_OUTER)
|
|
157
180
|
if !dest_part.touched
|
|
158
181
|
dest_part.touch!
|
|
159
|
-
traverse_inner(dest_part.circular_next, all_parts, bounds)
|
|
182
|
+
traverse_inner(dest_part.circular_next, all_parts, bounds, tracked_end_points)
|
|
160
183
|
return
|
|
161
184
|
end
|
|
162
185
|
end
|
|
@@ -165,7 +188,7 @@ module Contrek
|
|
|
165
188
|
end
|
|
166
189
|
end
|
|
167
190
|
elsif act_part.next
|
|
168
|
-
traverse_inner(act_part.next, all_parts, bounds)
|
|
191
|
+
traverse_inner(act_part.next, all_parts, bounds, tracked_end_points)
|
|
169
192
|
end
|
|
170
193
|
end
|
|
171
194
|
# rubocop:enable Lint/NonLocalExitFromIterator
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
module Contrek
|
|
2
2
|
module Concurrent
|
|
3
3
|
class InnerPolyline
|
|
4
|
-
attr_reader :sequence
|
|
4
|
+
attr_reader :sequence
|
|
5
5
|
|
|
6
|
-
def initialize(shape: nil, raw_coordinates: [], sequence: nil
|
|
6
|
+
def initialize(shape: nil, raw_coordinates: [], sequence: nil)
|
|
7
7
|
@raw = raw_coordinates if raw_coordinates
|
|
8
8
|
@sequence = sequence if sequence
|
|
9
|
-
@recombined = recombined
|
|
10
9
|
@shape = shape
|
|
11
10
|
end
|
|
12
11
|
|
|
@@ -6,15 +6,17 @@ module Contrek
|
|
|
6
6
|
TRACKED_OUTER = 1 << 0
|
|
7
7
|
|
|
8
8
|
attr_reader :raw, :name, :min_y, :max_y
|
|
9
|
-
attr_accessor :shape, :tile, :any_ancients
|
|
9
|
+
attr_accessor :shape, :tile, :any_ancients, :inside_inner_polyline
|
|
10
10
|
|
|
11
|
-
def initialize(tile:, polygon:, shape: nil, bounds: nil)
|
|
11
|
+
def initialize(tile:, polygon:, shape: nil, bounds: nil, force_named: nil)
|
|
12
12
|
@tile = tile
|
|
13
13
|
@name = tile.shapes.count
|
|
14
|
+
@named = force_named
|
|
14
15
|
@raw = polygon
|
|
15
16
|
@shape = shape
|
|
16
17
|
@flags = 0
|
|
17
18
|
@any_ancients = false
|
|
19
|
+
@inside_inner_polyline = nil
|
|
18
20
|
|
|
19
21
|
if bounds.nil?
|
|
20
22
|
find_boundary
|
|
@@ -31,17 +33,13 @@ module Contrek
|
|
|
31
33
|
end
|
|
32
34
|
|
|
33
35
|
def named
|
|
34
|
-
"
|
|
36
|
+
@named || "t#{@tile.name}s#{@name}#{"B" if boundary?}"
|
|
35
37
|
end
|
|
36
38
|
|
|
37
39
|
def numpy_raw
|
|
38
40
|
raw.flat_map { |p| [p[:x], p[:y]] }
|
|
39
41
|
end
|
|
40
42
|
|
|
41
|
-
def info
|
|
42
|
-
"w#{@tile.name} S#{@name}"
|
|
43
|
-
end
|
|
44
|
-
|
|
45
43
|
def turn_on(flag)
|
|
46
44
|
@flags |= flag
|
|
47
45
|
end
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
module Contrek
|
|
2
2
|
module Concurrent
|
|
3
3
|
class Shape
|
|
4
|
-
attr_accessor :outer_polyline, :inner_polylines, :merged_to_shape,
|
|
5
|
-
:
|
|
4
|
+
attr_accessor :outer_polyline, :inner_polylines, :merged_to_shape,
|
|
5
|
+
:parent_inner_polyline, :fixed
|
|
6
6
|
attr_reader :parent_shape, :children_shapes
|
|
7
7
|
|
|
8
8
|
def initialize
|
|
@@ -10,7 +10,7 @@ module Contrek
|
|
|
10
10
|
@merged_to_shape = nil
|
|
11
11
|
@parent_inner_polyline = nil
|
|
12
12
|
@children_shapes = []
|
|
13
|
-
@
|
|
13
|
+
@fixed = false
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def self.init_by(set_outer_polyline, set_inner_polylines)
|
|
@@ -24,6 +24,10 @@ module Contrek
|
|
|
24
24
|
@inner_polylines.clear
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
def name
|
|
28
|
+
outer_polyline.named
|
|
29
|
+
end
|
|
30
|
+
|
|
27
31
|
def set_parent_shape(shape)
|
|
28
32
|
@parent_shape&.children_shapes&.delete(self)
|
|
29
33
|
@parent_shape = shape
|
|
@@ -61,15 +61,15 @@ module Contrek
|
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
def compute_treemap
|
|
64
|
-
|
|
64
|
+
shapes_map = {}
|
|
65
65
|
shape_index = 0
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
shapes.map do |shape|
|
|
68
68
|
next if shape.outer_polyline.empty?
|
|
69
|
-
|
|
69
|
+
shapes_map[shape] = shape_index
|
|
70
70
|
shape_index += 1
|
|
71
71
|
if shape.parent_shape
|
|
72
|
-
[
|
|
72
|
+
[shapes_map[shape.parent_shape], shape.parent_shape.inner_polylines.index(shape.parent_inner_polyline)]
|
|
73
73
|
else
|
|
74
74
|
[-1, -1]
|
|
75
75
|
end
|
|
@@ -104,6 +104,7 @@ module Contrek
|
|
|
104
104
|
if treemap_entry != [-1, -1]
|
|
105
105
|
parent = shapes_map[treemap_entry.first]
|
|
106
106
|
shape.set_parent_shape(parent)
|
|
107
|
+
shape.fixed = true
|
|
107
108
|
shape.parent_inner_polyline = parent.inner_polylines[treemap_entry.last]
|
|
108
109
|
end
|
|
109
110
|
end
|
data/lib/contrek/version.rb
CHANGED
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.
|
|
4
|
+
version: 1.2.2
|
|
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-
|
|
11
|
+
date: 2026-05-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rspec
|
|
@@ -136,6 +136,7 @@ files:
|
|
|
136
136
|
- Gemfile
|
|
137
137
|
- Gemfile.lock
|
|
138
138
|
- LICENSE.md
|
|
139
|
+
- PERFORMANCE.md
|
|
139
140
|
- README.md
|
|
140
141
|
- Rakefile
|
|
141
142
|
- contrek.gemspec
|
|
@@ -143,10 +144,6 @@ files:
|
|
|
143
144
|
- ext/cpp_polygon_finder/PolygonFinder/CMakeLists.txt
|
|
144
145
|
- ext/cpp_polygon_finder/PolygonFinder/LICENSE_AGPL.txt
|
|
145
146
|
- ext/cpp_polygon_finder/PolygonFinder/clean.sh
|
|
146
|
-
- ext/cpp_polygon_finder/PolygonFinder/examples/example.cpp
|
|
147
|
-
- ext/cpp_polygon_finder/PolygonFinder/images/graphs_1024x1024.png
|
|
148
|
-
- ext/cpp_polygon_finder/PolygonFinder/images/labyrinth.png
|
|
149
|
-
- ext/cpp_polygon_finder/PolygonFinder/images/sample_10240x10240.png
|
|
150
147
|
- ext/cpp_polygon_finder/PolygonFinder/src/ContrekApi.h
|
|
151
148
|
- ext/cpp_polygon_finder/PolygonFinder/src/Tests.cpp
|
|
152
149
|
- ext/cpp_polygon_finder/PolygonFinder/src/Tests.h
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* example.cpp
|
|
3
|
-
*
|
|
4
|
-
* Copyright (c) 2025-2026 Emanuele Cesaroni
|
|
5
|
-
*
|
|
6
|
-
* Licensed under the GNU Affero General Public License v3 (AGPLv3).
|
|
7
|
-
* See the LICENSE file in this directory for the full license text.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
#include <iostream>
|
|
11
|
-
#include <gperftools/malloc_extension.h>
|
|
12
|
-
#include "ContrekApi.h"
|
|
13
|
-
#include "Tests.h"
|
|
14
|
-
|
|
15
|
-
void run_test() {
|
|
16
|
-
CpuTimer cpu_timer;
|
|
17
|
-
Tests test_suite;
|
|
18
|
-
cpu_timer.start();
|
|
19
|
-
|
|
20
|
-
// test_suite.test_a();
|
|
21
|
-
// test_suite.test_b();
|
|
22
|
-
// test_suite.test_c();
|
|
23
|
-
// test_suite.test_d();
|
|
24
|
-
// test_suite.test_e();
|
|
25
|
-
// test_suite.test_f();
|
|
26
|
-
// test_suite.test_g();
|
|
27
|
-
// test_suite.test_h();
|
|
28
|
-
// test_suite.test_i();
|
|
29
|
-
std::cout << "compute time =" << cpu_timer.stop() << std::endl;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
int main() {
|
|
33
|
-
MallocExtension::instance()->SetNumericProperty(
|
|
34
|
-
"tcmalloc.max_total_thread_cache_bytes",
|
|
35
|
-
1024 * 1024 * 1024
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
Contrek::Config cfg;
|
|
39
|
-
cfg.threads = 4;
|
|
40
|
-
cfg.tiles = 4;
|
|
41
|
-
cfg.compress_unique = true;
|
|
42
|
-
// cfg.treemap = true;
|
|
43
|
-
// cfg.connectivity_mode = Contrek::Connectivity::OMNIDIRECTIONAL;
|
|
44
|
-
|
|
45
|
-
CpuTimer cpu_timer;
|
|
46
|
-
cpu_timer.start();
|
|
47
|
-
std::cout << "--- Start Native Benchmark ---" << std::endl;
|
|
48
|
-
// auto result = Contrek::trace("../images/graphs_1024x1024.png", cfg);
|
|
49
|
-
auto result = Contrek::trace("../images/sample_10240x10240.png", cfg);
|
|
50
|
-
result->print_info();
|
|
51
|
-
std::cout << "Found polygons: " << result->groups << std::endl;
|
|
52
|
-
std::cout << "Time: " << cpu_timer.stop() << " ms" << std::endl;
|
|
53
|
-
|
|
54
|
-
// run_test();
|
|
55
|
-
return 0;
|
|
56
|
-
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|