kdraw 0.1.0__tar.gz

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.
kdraw-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 GumokuCat
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
kdraw-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,635 @@
1
+ Metadata-Version: 2.4
2
+ Name: kdraw
3
+ Version: 0.1.0
4
+ Summary: Topological Centerline SVG Vectorizer for CNC plotters, laser cutters, and CAM software.
5
+ Author: Ervin James P. Regio
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/anonymouschichvy/kdraw
8
+ Project-URL: Bug Tracker, https://github.com/anonymouschichvy/kdraw/issues
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Topic :: Multimedia :: Graphics
12
+ Classifier: Topic :: Scientific/Engineering :: Image Processing
13
+ Requires-Python: >=3.8
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: numpy>=1.20.0
17
+ Requires-Dist: pillow>=9.0.0
18
+ Requires-Dist: opencv-python>=4.5.0
19
+ Requires-Dist: scikit-image>=0.19.0
20
+ Provides-Extra: smooth
21
+ Requires-Dist: vtracer>=0.6.0; extra == "smooth"
22
+ Dynamic: license-file
23
+
24
+ # <p align="center"> <img src="http://kvenjoy.com/images/draw/icon.png" alt="logo" style="max-width:50%; height:300px;" /> </p>
25
+ # <div align="center">KDRAW: Topological Centerline SVG Vectorizer</div>
26
+ <div align="center">
27
+ <strong>KDRAW is a high-precision topological centerline vectorizer that converts raster graphics into optimized, smooth single-stroke SVGs for CNC plotters, laser cutters, and CAM software.</strong>
28
+ </div>
29
+
30
+ <br />
31
+
32
+ <div align="center">
33
+ <img src="https://img.shields.io/badge/Render-Centerline-blueviolet?style=for-the-badge&logo=visual-studio-code" alt="Centerline Mode" />
34
+ <img src="https://img.shields.io/badge/Optimize-TSP%20Greedy-success?style=for-the-badge&logo=python" alt="TSP Optimization" />
35
+ <img src="https://img.shields.io/badge/Smooth-Chaikin%20Subdivision-orange?style=for-the-badge&logo=scipy" alt="Chaikin Smoothing" />
36
+ </div>
37
+
38
+ ---
39
+
40
+ ## 📸 Visual Documentation & Evidence
41
+
42
+
43
+ ### 💻 Running the Vectorizer
44
+ To convert a text image into optimized single-line vectors using custom configuration:
45
+ ```bash
46
+ python main.py input.jpg output_centerline.svg --centerline --no-adaptive --morph-close 5 --min-spur 1 --upscale 8 --morph-close 5
47
+ ```
48
+
49
+ Here is the visual evidence of the conversion from the high-resolution raster image ([input.jpg](https://github.com/anonymouschichvy/kdraw/blob/main/docs/input.jpg)) to the thinned centerline stroke paths ([output_centerline.svg](https://github.com/anonymouschichvy/kdraw/blob/main/docs/output_centerline.svg)).
50
+
51
+ ### 1. Full-Page Comparison (Input vs. SVG Output)
52
+ Below is the full-page overview comparison. The left shows the original raster text ([input.jpg](https://github.com/anonymouschichvy/kdraw/blob/main/docs/input.jpg)) and the right shows the generated thinned centerline paths ([output_centerline.svg](https://github.com/anonymouschichvy/kdraw/blob/main/docs/output_centerline.svg)).
53
+ # <p align="center"> <img src="https://raw.githubusercontent.com/anonymouschichvy/kdraw/main/docs/comparison_full.png" alt="Full Page Comparison" style="max-width:100%;"/> </p>
54
+
55
+ ### 1.1 Full-Page Comparison Drawing (Input vs. SVG Output)
56
+ Below is the full-page drawing overview comparison. The left shows the original raster drawing ([input_draw.png](https://github.com/anonymouschichvy/kdraw/blob/main/docs/input_draw.png)) and the right shows the generated thinned centerline paths ([output_centerline.svg](https://github.com/anonymouschichvy/kdraw/blob/main/docs/output_centerline.svg)).
57
+ # <p align="center"> <img src="https://raw.githubusercontent.com/anonymouschichvy/kdraw/main/docs/comparison_draw.png" alt="Full Drawing Comparison" style="max-width:90%;"/> </p>
58
+
59
+ ### 2. Zoomed-In Details & Loop Preservation
60
+ To prevent plotters from bleeding ink and closing loops, KDRAW's pre-smoothing keeps character loops (`a`, `e`, `o`, `u`) perfectly open. The left shows the input pixels and the right shows the single-line thinned paths.
61
+
62
+ #### Region 0: Title and Introduction Text
63
+ # <p align="center"> <img src="https://raw.githubusercontent.com/anonymouschichvy/kdraw/main/docs/comparison_region0.png" alt="Region 0 Zoom" style="max-width:100%;"/> </p>
64
+
65
+ #### Region 1: Body Details (Dots of `i` & Colons)
66
+ Observe how the dots of the letter `i` and colons are preserved as independent, clean path strokes rather than being merged or pruned:
67
+ # <p align="center"> <img src="https://raw.githubusercontent.com/anonymouschichvy/kdraw/main/docs/comparison_region1.png" alt="Region 1 Zoom" style="max-width:100%;"/> </p>
68
+
69
+ ### 2.1 Zoomed-In Details & Loop Preservation
70
+ Observe how the lines are preserved as independent, clean path strokes rather than being merged or pruned:
71
+ # <p align="center"> <img src="https://raw.githubusercontent.com/anonymouschichvy/kdraw/main/docs/zoom_comparison_draw.png" alt="Drawing Zoom" style="max-width:100%;"/> </p>
72
+
73
+ ## ⚡ Key Highlights & Core Capabilities
74
+
75
+ * **🧩 Graph-Based Skeleton Tracing**: Represents the skeleton as a topological graph of nodes (junctions/endpoints) and edges. Prevents junction distortion and splits.
76
+ * **🔎 4x Upscaled Anti-Aliasing**: Interpolates and smooths low-resolution input images before skeletonization to eliminate pixel-level wiggles.
77
+ * **🛡️ Isolated Path Safety (i-Dot Preservation)**: Distinguishes between side spurs (noise) and isolated paths, ensuring colons, periods, and the dots of `i` are never pruned.
78
+ * **🌀 Chaikin Curve Fitting**: Corner-cutting curve smoothing that rounds out characters organic-style without coordinate shrinkage.
79
+ * **🏎️ TSP Pen-Travel Optimization**: Solves the Travelling Salesperson Problem (TSP) on the path sequence to save up to **98% of pen-up travel distance**.
80
+
81
+ ---
82
+
83
+ ## 🛠️ The Visual Pipeline
84
+
85
+ ```mermaid
86
+ graph TD
87
+ A[Raster Image input.jpg] --> B[4x Cubic Upscaling]
88
+ B --> C[Gaussian Blur 9x9]
89
+ C --> D[Otsu Binarization]
90
+ D --> E[Morphological Closing 5x5]
91
+ E --> F[Skeletonization]
92
+ F --> G[Graph Extraction & Pruning]
93
+ G --> H[Chaikin Path Smoothing]
94
+ H --> I[TSP Sort & Max-Join]
95
+ I --> J[Stroke SVG output.svg]
96
+ ```
97
+
98
+ ---
99
+
100
+ ## 📖 Complete Code Logic & Detailed Algorithms
101
+
102
+ Below is the exhaustive pseudocode and logic breakdown of every helper and processing routine in the KDRAW engine (`main.py` and the `kdraw` package).
103
+
104
+ ### 1. `get_hex_color(val, has_alpha)`
105
+
106
+ **Input:** Packed 32-bit pixel value `val`, transparency flag `has_alpha`
107
+ **Output:** Hex color string (`#RRGGBB`) or CSS RGBA string (`rgba(...)`)
108
+
109
+ #### Color Channel Extraction
110
+
111
+ Given a packed ARGB pixel:
112
+
113
+ ```math
114
+ \text{val}
115
+ =
116
+ (A \ll 24)
117
+ +
118
+ (R \ll 16)
119
+ +
120
+ (G \ll 8)
121
+ +
122
+ B
123
+ ```
124
+
125
+ Extract each channel using bitwise operations:
126
+
127
+ ```math
128
+ A
129
+ =
130
+ (\text{val} \gg 24)
131
+ \;\&\;
132
+ 255
133
+ ```
134
+
135
+ ```math
136
+ R
137
+ =
138
+ (\text{val} \gg 16)
139
+ \;\&\;
140
+ 255
141
+ ```
142
+
143
+ ```math
144
+ G
145
+ =
146
+ (\text{val} \gg 8)
147
+ \;\&\;
148
+ 255
149
+ ```
150
+
151
+ ```math
152
+ B
153
+ =
154
+ \text{val}
155
+ \;\&\;
156
+ 255
157
+ ```
158
+
159
+ where:
160
+
161
+ ```math
162
+ 0 \le A,R,G,B \le 255
163
+ ```
164
+
165
+ #### Alpha Normalization
166
+
167
+ When transparency is enabled, convert the alpha channel to the CSS opacity range:
168
+
169
+ ```math
170
+ \alpha
171
+ =
172
+ \frac{A}{255}
173
+ ```
174
+
175
+ with:
176
+
177
+ ```math
178
+ 0 \le \alpha \le 1
179
+ ```
180
+
181
+ #### Output Selection
182
+
183
+ If transparency is present:
184
+
185
+ ```math
186
+ \text{has\_alpha}
187
+ \land
188
+ A < 255
189
+ ```
190
+
191
+ return:
192
+
193
+ ```math
194
+ \text{rgba}(R,G,B,\alpha)
195
+ ```
196
+
197
+ Otherwise return:
198
+
199
+ ```math
200
+ \#RRGGBB
201
+ ```
202
+
203
+ where:
204
+
205
+ ```math
206
+ RRGGBB
207
+ =
208
+ \text{hex}(R)
209
+ \;||\;
210
+ \text{hex}(G)
211
+ \;||\;
212
+ \text{hex}(B)
213
+ ```
214
+
215
+ and \(||\) denotes string concatenation.
216
+
217
+ ---
218
+
219
+ ### 2. `smooth_paths_laplacian(path, iterations, weight)`
220
+
221
+ **Input:** Curve coordinate array `path`, iteration count `iterations`, smoothing weight `w`
222
+ **Output:** Laplacian-smoothed coordinate array
223
+
224
+ #### Laplacian Smoothing Model
225
+
226
+ For each vertex \( \mathbf{p}_i \), compute the local neighborhood average:
227
+
228
+ ```math
229
+ \mathbf{m}_i
230
+ =
231
+ \frac{
232
+ \mathbf{p}_{i-1}
233
+ +
234
+ \mathbf{p}_{i+1}
235
+ }{2}
236
+ ```
237
+
238
+ The updated position is a weighted blend between the original point and its neighborhood mean:
239
+
240
+ ```math
241
+ \mathbf{p}_i'
242
+ =
243
+ (1-w)\mathbf{p}_i
244
+ +
245
+ w\mathbf{m}_i
246
+ ```
247
+
248
+ Substituting the neighborhood average:
249
+
250
+ ```math
251
+ \mathbf{p}_i'
252
+ =
253
+ (1-w)\mathbf{p}_i
254
+ +
255
+ w
256
+ \left(
257
+ \frac{
258
+ \mathbf{p}_{i-1}
259
+ +
260
+ \mathbf{p}_{i+1}
261
+ }{2}
262
+ \right)
263
+ ```
264
+
265
+ where:
266
+
267
+ ```math
268
+ 0 \le w \le 1
269
+ ```
270
+
271
+ #### Interpretation
272
+
273
+ Special cases:
274
+
275
+ ```math
276
+ w = 0
277
+ \quad\Rightarrow\quad
278
+ \mathbf{p}_i' = \mathbf{p}_i
279
+ ```
280
+
281
+ (No smoothing)
282
+
283
+ ```math
284
+ w = 1
285
+ \quad\Rightarrow\quad
286
+ \mathbf{p}_i'
287
+ =
288
+ \frac{
289
+ \mathbf{p}_{i-1}
290
+ +
291
+ \mathbf{p}_{i+1}
292
+ }{2}
293
+ ```
294
+
295
+ (Complete neighborhood averaging)
296
+
297
+ For intermediate values:
298
+
299
+ ```math
300
+ 0 < w < 1
301
+ ```
302
+
303
+ the vertex moves proportionally toward the average of its neighboring vertices, reducing local curvature and noise while preserving the overall shape.
304
+ * **Logic**:
305
+ 1. If path has less than 3 points, return original path.
306
+ ### 2. `smooth_paths_laplacian(path, iterations, w)`
307
+
308
+ **Input:** Coordinate array `path`, iteration count `iterations`, smoothing weight `w`
309
+ **Output:** Laplacian-smoothed coordinate array
310
+
311
+ #### Algorithm
312
+
313
+ 1. If the path contains fewer than **3 points**, return the original path.
314
+
315
+ 2. Determine whether the path is closed:
316
+
317
+ ```math
318
+ \text{is\_closed}
319
+ =
320
+ \left\|
321
+ \mathbf{p}_0 - \mathbf{p}_{n-1}
322
+ \right\|
323
+ < 1.0
324
+ ```
325
+
326
+ 3. Repeat for each smoothing iteration:
327
+
328
+ - Create a temporary copy of the coordinate array.
329
+ - Apply the update rules below.
330
+
331
+ ##### Closed Path
332
+
333
+ For each vertex \( \mathbf{p}_i \) (excluding the duplicated endpoint):
334
+
335
+ ```math
336
+ \mathbf{p}_i'
337
+ =
338
+ (1-w)\mathbf{p}_i
339
+ +
340
+ w
341
+ \left(
342
+ \frac{\mathbf{p}_{i-1} + \mathbf{p}_{i+1}}{2}
343
+ \right)
344
+ ```
345
+
346
+ with cyclic indexing:
347
+
348
+ ```math
349
+ \mathbf{p}_{i-1}
350
+ =
351
+ \mathbf{p}_{(i-1)\bmod n}
352
+ ```
353
+
354
+ ```math
355
+ \mathbf{p}_{i+1}
356
+ =
357
+ \mathbf{p}_{(i+1)\bmod n}
358
+ ```
359
+
360
+ Maintain closure after updating:
361
+
362
+ ```math
363
+ \mathbf{p}_{n-1}
364
+ =
365
+ \mathbf{p}_0
366
+ ```
367
+
368
+ ##### Open Path
369
+
370
+ Keep endpoints fixed and update interior vertices:
371
+
372
+ ```math
373
+ i = 1, 2, \ldots, n-2
374
+ ```
375
+
376
+ ```math
377
+ \mathbf{p}_i'
378
+ =
379
+ (1-w)\mathbf{p}_i
380
+ +
381
+ w
382
+ \left(
383
+ \frac{\mathbf{p}_{i-1} + \mathbf{p}_{i+1}}{2}
384
+ \right)
385
+ ```
386
+
387
+ ---
388
+
389
+ ### 3. `smooth_paths_chaikin(path, iterations)`
390
+
391
+ **Input:** Coordinate array `path`, iteration count `iterations`
392
+ **Output:** Chaikin corner-cut smoothed coordinate array
393
+
394
+ #### Algorithm
395
+
396
+ 1. If the path contains fewer than **3 points**, return the original path.
397
+
398
+ 2. Repeat for each iteration.
399
+
400
+ ##### Closed Path
401
+
402
+ For every segment:
403
+
404
+ ```math
405
+ [\mathbf{p}_i,\mathbf{p}_{i+1}]
406
+ ```
407
+
408
+ Generate:
409
+
410
+ ```math
411
+ \mathbf{q}
412
+ =
413
+ 0.75\,\mathbf{p}_i
414
+ +
415
+ 0.25\,\mathbf{p}_{i+1}
416
+ ```
417
+
418
+ ```math
419
+ \mathbf{r}
420
+ =
421
+ 0.25\,\mathbf{p}_i
422
+ +
423
+ 0.75\,\mathbf{p}_{i+1}
424
+ ```
425
+
426
+ Append the first generated point to the end of the sequence to preserve closure.
427
+
428
+ ##### Open Path
429
+
430
+ Preserve endpoints:
431
+
432
+ ```math
433
+ \mathbf{p}_0
434
+ \qquad\text{and}\qquad
435
+ \mathbf{p}_{n-1}
436
+ ```
437
+
438
+ For each interior segment:
439
+
440
+ ```math
441
+ [\mathbf{p}_i,\mathbf{p}_{i+1}]
442
+ ```
443
+
444
+ Generate:
445
+
446
+ ```math
447
+ \mathbf{q}
448
+ =
449
+ 0.75\,\mathbf{p}_i
450
+ +
451
+ 0.25\,\mathbf{p}_{i+1}
452
+ ```
453
+
454
+ ```math
455
+ \mathbf{r}
456
+ =
457
+ 0.25\,\mathbf{p}_i
458
+ +
459
+ 0.75\,\mathbf{p}_{i+1}
460
+ ```
461
+
462
+ Resulting point sequence:
463
+
464
+ ```math
465
+ [
466
+ \mathbf{p}_0,\,
467
+ \mathbf{q}_1,\,
468
+ \mathbf{r}_1,\,
469
+ \mathbf{q}_2,\,
470
+ \mathbf{r}_2,\,
471
+ \dots,\,
472
+ \mathbf{p}_{n-1}
473
+ ]
474
+ ```
475
+
476
+ #### Chaikin Corner-Cutting Rule
477
+
478
+ For a segment connecting points \( \mathbf{A} \) and \( \mathbf{B} \):
479
+
480
+ ```math
481
+ \mathbf{Q}
482
+ =
483
+ \frac{3}{4}\mathbf{A}
484
+ +
485
+ \frac{1}{4}\mathbf{B}
486
+ ```
487
+
488
+ ```math
489
+ \mathbf{R}
490
+ =
491
+ \frac{1}{4}\mathbf{A}
492
+ +
493
+ \frac{3}{4}\mathbf{B}
494
+ ```
495
+
496
+ Repeated application progressively removes sharp corners and converges toward a smooth curve.
497
+
498
+ ### 4. `optimize_paths(contours, max_join_dist)`
499
+ * **Input**: List of curves `contours`, pen-down merging threshold `max_join_dist`
500
+ * **Output**: Sorted and merged curves list, original travel distance, optimized travel distance
501
+ * **Logic**:
502
+ 1. Convert all contours to NumPy float arrays. Calculate baseline sequential pen travel.
503
+ 2. Implement a greedy Travelling Salesperson (TSP) heuristic:
504
+ - Pop the first contour as the active path.
505
+ - While remaining contours exist:
506
+ - Find the distances from the active path's endpoint to the start and endpoints of all remaining contours.
507
+ - Identify the closest coordinate point.
508
+ - If the closest point belongs to the end of a contour, reverse that contour.
509
+ - If the distance to the closest contour is \(\le max\_join\_dist\), extend the active path coordinates directly with the closest contour coordinates (merging).
510
+ - Otherwise, append the active path to the optimized list and set the closest contour as the new active path.
511
+ - Append the final active path.
512
+
513
+ ### 5. `build_and_prune_graph(skel_bool, min_spur_length, collapse_dist)`
514
+ * **Input**: Binary skeleton image `skel_bool`, spur limit `min_spur_length`, merge radius `collapse_dist`
515
+ * **Output**: List of cleaned, continuous centerline coordinate paths
516
+ * **Logic**:
517
+ 1. Retrieve skeleton coordinates: `pixels = set(zip(*np.where(skel_bool)))`.
518
+ 2. Compute 8-connected adjacency dictionary: `adj = {p: get_neighbors(p, pixels) for p in pixels}`.
519
+ 3. Classify pixels:
520
+ - `endpoints` (neighbors == 1)
521
+ - `junctions` (neighbors >= 3)
522
+ - `regular` (neighbors == 2)
523
+ 4. Cluster contiguous junction pixels using BFS. Each connected component of junction pixels forms a singular "super-junction" node.
524
+ 5. Assign node IDs to all endpoints and junction clusters. Build `pixel_to_node` map.
525
+ 6. Trace edges:
526
+ - For each node:
527
+ - If a neighbor is a regular pixel, trace along regular pixels (BFS) until hitting any node. Create a stroke edge.
528
+ - If a neighbor is directly in another node, create a direct node-to-node edge of length 2 (essential for preserving i-dots).
529
+ 7. Locate isolated cycles (loops with no nodes, degree-2 only like in the letter `o`). Convert to closed loop edges.
530
+ 8. Perform iterative topology reductions:
531
+ - **Spur Check**: If an edge connects an endpoint (degree 1) to a junction (degree >= 3), and its pixel length is \(< min\_spur\_length\), delete the edge.
532
+ - **Isolated Check**: If an edge connects two endpoints directly (degree 1 to 1), it is an isolated dot. Protect it from spur pruning.
533
+ - **Junction Collapse**: If an edge connects two junction nodes and is shorter than `collapse_dist`, merge the two junction nodes and update all matching edge node IDs.
534
+ 9. Clean up: For any node left with degree 2 (exactly two edges), merge the paths of the two edges into a single edge.
535
+
536
+ ### 6. `convert_centerline(...)`
537
+ * **Input**: File paths and all tuning thresholds (`upscale_factor`, `blur_size`, etc.)
538
+ * **Output**: Stroke-only SVG file containing centerline paths
539
+ * **Logic**:
540
+ 1. Load input image. If `upscale_factor > 1`, upscale using `cv2.resize` with bicubic interpolation.
541
+ 2. Apply Gaussian blur of size `blur_size` (only odd dimensions allowed).
542
+ 3. Binarize:
543
+ - If `use_adaptive`: Apply local adaptive Gaussian thresholding using `cv2.adaptiveThreshold` with `block_size` and subtraction constant `c_val`.
544
+ - Else: Apply Otsu's thresholding using `cv2.threshold`.
545
+ 4. Morphological filters: Apply closing and opening operations using an elliptical structuring element on the binary mask.
546
+ 5. Thin binary mask to a single-pixel centerline using morphological `skeletonize` (Zhang-Suen/Lee).
547
+ 6. Call `build_and_prune_graph` to trace skeleton pixels into a clean set of coordinate paths.
548
+ 7. Downscale coordinate values by `upscale_factor` to match original image dimensions.
549
+ 8. For each path:
550
+ - If the path has only 2 points, bypass simplification.
551
+ - Otherwise, simplify coordinates using RDP (`cv2.approxPolyDP`) with tolerance `epsilon`.
552
+ 9. Apply smoothing: If `smooth_iters > 0`, call `smooth_paths_chaikin` or `smooth_paths_laplacian` for `smooth_iters` iterations.
553
+ 10. Decimate coordinates post-smoothing using RDP with a tight tolerance `smooth_decimate`.
554
+ 11. Call `optimize_paths` with `max_join` to minimize travel sequence.
555
+ 12. Format and write paths into SVG XML nodes containing `<path d="..." fill="none" stroke="black" ... />`.
556
+
557
+ ---
558
+
559
+ ## 🚀 Quick Start
560
+
561
+ ### 📦 Installation
562
+ Ensure you have the required libraries installed:
563
+ ```bash
564
+ pip install opencv-python scikit-image numpy pillow
565
+ ```
566
+
567
+ ### 💻 Running the Vectorizer
568
+ To convert a text image into optimized single-line vectors using the recommended defaults:
569
+ ```bash
570
+ python main.py input.jpg output_centerline.svg --centerline --no-adaptive
571
+ ```
572
+
573
+ ---
574
+
575
+ ## 📊 Parameters & Customization
576
+
577
+ | CLI Argument | Type | Default | Description |
578
+ | :--- | :---: | :---: | :--- |
579
+ | `--centerline` / `-cl` | flag | `False` | Enables single-stroke skeletonization (eliminates bubble outlines). |
580
+ | `--upscale` | `int` | `4` | Upscaling factor to smooth boundaries before tracing. |
581
+ | `--blur` | `int` | `9` | Pre-threshold Gaussian blur size to remove staircase wiggles. |
582
+ | `--no-adaptive` | flag | `False` | Disables adaptive thresholding (uses Otsu global thresholding, preserving loops). |
583
+ | `--morph-close` | `int` | `5` | Fills in tiny gaps on thin stroke contours. |
584
+ | `--min-spur` | `int` | `16` | Minimum pixel length of a branch to not be pruned as a spur. |
585
+ | `--collapse-junc` | `int` | `8` | Merges adjacent junctions to straighten line joints. |
586
+ | `--max-join` | `float` | `2.5` | Binds path ends within this distance to avoid lifting the pen. |
587
+ | `--smooth-iters` | `int` | `3` | Number of Chaikin smoothing iterations. |
588
+ | `--smooth-decimate` | `float` | `0.1` | Post-smoothing RDP decimation to minimize point count. |
589
+
590
+ ---
591
+
592
+ ## 📈 Quality & Performance Metrics
593
+
594
+ Running KDRAW with the optimal centerline defaults provides a massive boost in vector quality and plotter throughput:
595
+
596
+ > [!IMPORTANT]
597
+ > **TSP Optimization saves up to 98% of pen-up travel**, reducing wear and tear on plotter belts and servos.
598
+
599
+ | Metric | Raw Skeleton Trace | KDRAW Graph Pipeline | Improvement |
600
+ | :--- | :---: | :---: | :---: |
601
+ | **Path Count (Pen Lifts)** | 2,468 | **2,071** | **16.1% fewer lifts** |
602
+ | **Pen-Up Travel Distance** | 1,684,002 px | **35,425 px** | **97.9% distance saved** |
603
+ | **Average Angle Change** | 49.9° | **17.4°** | **Curves are 2.8x smoother** |
604
+ | **Punctuation & Dots** | Lost / Jagged | **Perfectly Preserved** | Flawless |
605
+
606
+ ---
607
+
608
+ ## 💖 Donate & Support
609
+
610
+ If you find this project useful and would like to support the deployment of FishTrack buoys for coastal fishing communities, donations are greatly appreciated!
611
+
612
+ <p align="left">
613
+ <a href="bitcoin:13zWnp2ty3NPzAXX9QxwEeoPSKhN5tPzic">
614
+ <img src="https://img.shields.io/badge/Donate-Bitcoin-F7931A?style=for-the-badge&logo=bitcoin&logoColor=white" alt="Donate Bitcoin" />
615
+ </a>
616
+ </p>
617
+
618
+ **Bitcoin Address:** `13zWnp2ty3NPzAXX9QxwEeoPSKhN5tPzic`
619
+
620
+ ---
621
+
622
+ ## 📜 License
623
+ MIT License. Open-source vector engine.
624
+
625
+ ---
626
+
627
+ ## Star History
628
+
629
+ <a href="https://www.star-history.com/?repos=anonymouschichvy%2FKDRAW&type=date&legend=top-left">
630
+ <picture>
631
+ <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=anonymouschichvy/KDRAW&type=date&theme=dark&legend=top-left" />
632
+ <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=anonymouschichvy/KDRAW&type=date&legend=top-left" />
633
+ <img alt="Star History Chart" src="https://api.star-history.com/chart?repos=anonymouschichvy/KDRAW&type=date&legend=top-left" />
634
+ </picture>
635
+ </a>