Skip to content

feat(contour tracing): Suzuki-Abe-style contour tracing - #219

Merged
Ryan-Millard merged 38 commits into
mainfrom
feat/contour-tracing
Jan 18, 2026
Merged

feat(contour tracing): Suzuki-Abe-style contour tracing#219
Ryan-Millard merged 38 commits into
mainfrom
feat/contour-tracing

Conversation

@Ryan-Millard

@Ryan-Millard Ryan-Millard commented Jan 10, 2026

Copy link
Copy Markdown
Owner

✨ Feature Pull Request

Proposal for new features or enhancements

📌 Description

  • What: Suzuki-Abe-style contour tracing (non-binary images)
  • Why: Precursor to vectorization algorithm - provides coordinates for paths.

🔗 Issue

Fixes #189

📦 Type of Change

  • New feature
  • Enhancement

🧪 How Has This Been Tested?

  • Visual tests (no testing suite for C++ yet)

✔️ Checklist

  • I’ve read the contribution guidelines.
  • My code follows the project style (lint/format passes).
  • I’ve added or updated tests.
  • I’ve updated documentation if needed.
  • No breaking changes (or version bump explained).

📸 Screenshots / Demo

See #220

Summary by CodeRabbit

  • New Features

    • Contour detection with SVG contour output for processed images.
    • Exposes per-pixel labels alongside clustered pixels for downstream processing.
  • Improvements

    • Replaced the prior in-place merging flow with contour-based and graph-driven region processing for more consistent results.
    • Worker memory handling and clustering calls updated for reliable buffer read/write.
  • Documentation

    • Added detailed WASM, worker, and build configuration guides.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 10, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Replaces in-place small-region merging with contour tracing after K-Means. kmeans now writes both pixel output and per-pixel Int32 labels; a new graph-based kmeans_clustering_graph produces region merging and contours. JS worker/hook updated to pass/receive Int32 labels; docs and tests adjusted; mergeSmallRegionsInPlace removed.

Changes

Cohort / File(s) Summary
React Component
src/components/WasmImageProcessor.jsx
Switches to destructuring { pixels, labels } from kmeans, calls findContours with labels, and uses returned contours for SVG generation; updates effect deps and flow comments.
Hook & Tests
src/hooks/useWasmWorker.js, src/hooks/useWasmWorker.test.js
BREAKING API: kmeans now accepts/returns out buffers and returns { pixels, labels }; mergeSmallRegionsInPlace removed and replaced by findContours; tests updated accordingly.
Web Worker
src/workers/wasmWorker.js
Allocations tracked with pointer metadata; supports HEAP32 for Int32Array, type-aware copy/readback, and correct free semantics for typed buffers.
WASM Headers
src/wasm/modules/image/include/*
kmeans.h, kmeans_graph.h, contours.h, graph.h, node.h, (removed) mergeSmallRegionsInPlace.h, Pixel.h
New/updated public declarations: kmeans (out_data + out_labels), kmeans_clustering_graph, find_contours/contours types, Graph/Node APIs; removed mergeSmallRegionsInPlace.h; minor Pixel static_assert text tweak.
WASM Implementations
src/wasm/modules/image/src/*
kmeans.cpp, kmeans_graph.cpp, contours.cpp, graph.cpp, node.cpp, (removed) mergeSmallRegionsInPlace.cpp
kmeans rewritten to write out_data and out_labels; added graph-based region labeling/merging and contour tracing (kmeans_clustering_graph, find_contours); full contour tracing implementation added; old in-place merging removed.
WASM Build / CMake
src/wasm/modules/image/CMakeLists.txt
Adds HEAP32 to EXPORTED_RUNTIME_METHODS so JS can access Int32Array views.
Docs / Site
docs/docs/reference/react/hooks/useWasmWorker/index.md, docs/docs/reference/react/workers/wasmWorker.md, docs/docs/reference/react/vite-config.md, docs/docs/reference/wasm/modules/*, several _category_.json
Adds extensive docs for hook, worker, Vite WASM config, CMake orchestrator, Image module CMake; updates category metadata (position fields removed/adjusted); new pages for workers and WASM modules.

Sequence Diagram(s)

sequenceDiagram
    participant React as React Component
    participant Hook as useWasmWorker Hook
    participant Worker as Web Worker
    participant WASM as WASM Module

    React->>Hook: kmeans({ pixels, width, height, num_colors })
    Hook->>Worker: postMessage { call: "kmeans", buffers: [...], args: [...] }
    Worker->>WASM: ccall('kmeans', ptr_in, ptr_out_pixels, ptr_out_labels, width, height, k, max_iter)
    WASM-->>Worker: out_pixels + out_labels
    Worker-->>Hook: postMessage { pixels: Uint8ClampedArray, labels: Int32Array }
    Hook-->>React: resolve({ pixels, labels })

    React->>Hook: findContours({ pixels, labels, width, height, min_area })
    Hook->>Worker: postMessage { call: "kmeans_clustering_graph", args: [...] }
    Worker->>WASM: ccall('kmeans_clustering_graph', ptr_pixels, ptr_labels, width, height, min_area, draw_borders)
    WASM-->>Worker: modified pixels (and contour metadata internal)
    Worker-->>Hook: postMessage { pixels: Uint8ClampedArray }
    Hook-->>React: resolve(contours)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Possibly related issues

  • #189: feat(trace contours): add contour tracing after K-Means clustering — implements contour tracing using K‑Means labels and returns ordered contours; directly aligned with the new contours code.
  • #210: rename/refactor kmeans signatures — kmeans API/signature changes in this PR address the same kmeans refactor objectives noted in that issue.

Possibly related PRs

  • #165 — Documentation for mergeSmallRegionsInPlace: related because this PR removes that API/implementation and supersedes or conflicts with that doc PR.

Suggested labels

BREAKING CHANGE, docs

Poem

🐰 I hopped through pixels, tiny and bright,

K‑means made clusters, I traced each bite.
Nodes held hands, contours sang the tune,
WASM hummed softly beneath the moon.
SVG paths danced — a carrot-shaped delight.

🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes significant out-of-scope changes beyond contour tracing: Graph/Node classes for region management, kmeans API refactoring with int32_t changes, and removal of mergeSmallRegionsInPlace. These architectural changes exceed the contour-tracing scope. Extract Graph/Node refactoring and kmeans API changes into a separate PR as suggested in review comments; keep this PR focused purely on Suzuki-Abe contour tracing.
Docstring Coverage ⚠️ Warning Docstring coverage is 19.15% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(contour tracing): Suzuki-Abe-style contour tracing' clearly summarizes the main feature added in the changeset: contour tracing functionality using the Suzuki-Abe algorithm.
Linked Issues check ✅ Passed The PR meets the key requirements from issue #189: implements contour tracing after K-Means clustering in C++, outputs ordered contours per cluster as pixel coordinates, handles holes and disconnected regions, integrates with the image-to-svg pipeline, and shows no performance regressions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Ryan-Millard Ryan-Millard changed the title Feat/contour tracing WIP: feat(contour tracing): Suzuki-Abe-style contour tracing but for non-binary images Jan 10, 2026
@Ryan-Millard Ryan-Millard added enhancement New feature or request wasm important This item is important and needs to be worked on ASAP labels Jan 10, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In @src/hooks/useWasmWorker.js:
- Around line 60-63: The function expression findContours is missing a trailing
semicolon which causes Prettier/formatting failures; add a semicolon after the
closing brace of the findContours async function expression (the one returning
call('visualize_contours'...). Ensure the pattern matches other function
expressions in the file by placing the semicolon immediately after the function
expression's closing brace.

In @src/wasm/modules/image/include/find_contours.h:
- Around line 21-32: The getter inner_pixel() can return uninitialized data
because inner_pixel_ isn't initialized; initialize inner_pixel_ at declaration
(e.g., default-construct ImageLib::RGBAPixel<uint8_t> inner_pixel_ = {}), or
alternatively add a has_inner_pixel() method that checks is_inner_pixel_set_ and
make inner_pixel() either return a default-initialized pixel when unset or
assert/throw; update references to inner_pixel_, inner_pixel(),
set_inner_pixel(), and is_inner_pixel_set_ accordingly so callers cannot observe
uninitialized pixels.

In @src/wasm/modules/image/src/find_contours.cpp:
- Around line 164-172: The contour's starting pixel (start_x, start_y) is never
pushed into the contour points, so add the start coordinate to the contour
before entering the neighbor-walking loop: after creating Contour contour and
setting contour.set_inner_pixel(image(start_x, start_y)), append the starting
coordinate to contour.points (or call the contour's point-add method) so the
vector of points includes {start_x, start_y} as the first element; keep the
existing current = {start_x, start_y} and prev_dir = 7 logic intact so
subsequent neighbor discovery continues from that initial point.
- Around line 86-155: The static function find_next_contour_pixel is dead code
(never called) and contains four debug log() calls; either delete the entire
function to remove maintenance burden, or if you want to keep it for future use,
strip out the log(...) statements inside find_next_contour_pixel (the calls
emitting "1. skipping neighbor...", "2. skipping neighbor...", "3. border
pixel...", "4. incorrect at...") and leave the rest unchanged; locate the
function by its name and remove or clean the logging accordingly.
🧹 Nitpick comments (5)
src/wasm/modules/image/src/kmeans.cpp (1)

144-150: Consider applying consistent brace-init style to kmeans_clustering_spatial.

The kmeans_clustering function was updated to use brace-initialization, but kmeans_clustering_spatial still uses the older style. For consistency within the file, consider applying the same modernization here.

src/components/WasmImageProcessor.jsx (1)

118-129: Consider extracting threshold logic into a helper function.

The nested ternary for minimumAllowedMinArea is functional but could be more readable as a small helper function or lookup.

Optional refactor
const getMinimumAllowedMinArea = (area) => {
  if (area > 100_000_000) return 25;
  if (area > 10_000_000) return 20;
  if (area > 1_000_000) return 15;
  return 10;
};

// Usage:
const minimumAllowedMinArea = getMinimumAllowedMinArea(area);
src/wasm/modules/image/src/find_contours.cpp (3)

6-8: Duplicate #include <iostream>.

Line 8 duplicates the include on line 6.

Proposed fix
 #include "PixelConverters.h"
 #include <iostream>
 #include <array>
-#include <iostream>
 #include <string>

173-194: Loop termination condition is misleading.

The while (current != std::make_pair(start_x, start_y)) condition will never evaluate to true because:

  1. The starting pixel is already labeled before entering the loop (line 219 in find_contours)
  2. Line 182 skips already-labeled pixels (labels[ny][nx] != -1)

The loop always terminates via found_dir == -1 (line 193). Consider simplifying to while (true) or documenting that the termination relies on exhausting unlabeled neighbors.


73-84: Remove unused update_label function.

This function is not called anywhere in the codebase and appears to be a remnant from a previous implementation approach.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2ef66e5 and b81809a.

📒 Files selected for processing (8)
  • src/components/WasmImageProcessor.jsx
  • src/hooks/useWasmWorker.js
  • src/wasm/modules/image/include/Pixel.h
  • src/wasm/modules/image/include/find_contours.h
  • src/wasm/modules/image/include/visualize_contours.h
  • src/wasm/modules/image/src/find_contours.cpp
  • src/wasm/modules/image/src/kmeans.cpp
  • src/wasm/modules/image/src/visualize_contours.cpp
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-12-31T17:46:54.476Z
Learnt from: fransafu
Repo: Ryan-Millard/Img2Num PR: 176
File: src/wasm/modules/image/src/bilateral_filter.cpp:98-101
Timestamp: 2025-12-31T17:46:54.476Z
Learning: In bilateral_filter.cpp (src/wasm/modules/image/src/bilateral_filter.cpp), rely on the fact that the center pixel contributes a weight of exactly 1.0 to both spatial and range components (exp(0) = 1) so the normalization sum cannot be zero. This implies you do not need explicit guards against division by zero for normalization in this implementation.

Applied to files:

  • src/wasm/modules/image/src/visualize_contours.cpp
  • src/wasm/modules/image/src/kmeans.cpp
  • src/wasm/modules/image/src/find_contours.cpp
📚 Learning: 2026-01-06T04:56:57.269Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 191
File: src/wasm/modules/image/src/kmeans.cpp:128-134
Timestamp: 2026-01-06T04:56:57.269Z
Learning: In C++ sources compiled for WASM with Emscripten/Clang, designated initializers (e.g., RGBXY{.r = ..., .g = ...}) are allowed as a C++17 extension. When reviewing code that relies on designated initializers, verify that the target toolchain enables CXX_STANDARD 17 or higher and that the build system (CMake/emsdk) uses Emscripten with a compatible clang. If not, avoid such initializers or provide portable alternatives.

Applied to files:

  • src/wasm/modules/image/src/visualize_contours.cpp
  • src/wasm/modules/image/src/kmeans.cpp
  • src/wasm/modules/image/src/find_contours.cpp
📚 Learning: 2026-01-06T21:06:24.476Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 191
File: src/wasm/modules/image/src/cielab.cpp:43-57
Timestamp: 2026-01-06T21:06:24.476Z
Learning: In the Img2Num project, prefer recommending and using the provided docker/script wrappers (e.g., ./img2num format-wasm, ./img2num clean-wasm) instead of invoking local tools directly (e.g., clang-format -i). This reduces dependency requirements for users and ensures consistent tooling across environments. Apply this guidance to C++ source files under the project when reviewing changes.

Applied to files:

  • src/wasm/modules/image/src/visualize_contours.cpp
  • src/wasm/modules/image/src/kmeans.cpp
  • src/wasm/modules/image/src/find_contours.cpp
📚 Learning: 2026-01-06T05:10:03.461Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 191
File: src/wasm/modules/image/src/cielab.cpp:31-39
Timestamp: 2026-01-06T05:10:03.461Z
Learning: When reviewing color space transformation code in C++ (specifically in src/wasm/modules/image/src/*.cpp), flag and require checks for precision inconsistencies between forward and inverse matrix coefficients. Ensure that round-trip conversions do not accumulate unacceptable rounding errors by comparing forward and inverse results within a defined numerical tolerance, documenting tolerances, and adding tests that verify symmetry (forward then inverse).

Applied to files:

  • src/wasm/modules/image/src/visualize_contours.cpp
  • src/wasm/modules/image/src/kmeans.cpp
  • src/wasm/modules/image/src/find_contours.cpp
🧬 Code graph analysis (5)
src/components/WasmImageProcessor.jsx (3)
src/hooks/useWasmWorker.js (3)
  • useWasmWorker (4-66)
  • findContours (60-63)
  • mergeSmallRegionsInPlace (56-59)
src/wasm/modules/image/src/mergeSmallRegionsInPlace.cpp (4)
  • minArea (44-46)
  • minArea (44-44)
  • mergeSmallRegionsInPlace (51-117)
  • mergeSmallRegionsInPlace (51-52)
src/wasm/modules/image/include/find_contours.h (1)
  • contours (10-36)
src/wasm/modules/image/src/visualize_contours.cpp (2)
src/wasm/modules/image/include/find_contours.h (2)
  • contours (10-36)
  • RGBAPixel (21-23)
src/wasm/modules/image/src/find_contours.cpp (2)
  • find_contours (199-224)
  • find_contours (199-199)
src/wasm/modules/image/include/find_contours.h (1)
src/wasm/modules/image/src/find_contours.cpp (2)
  • find_contours (199-224)
  • find_contours (199-199)
src/wasm/modules/image/include/visualize_contours.h (1)
src/wasm/modules/image/src/visualize_contours.cpp (2)
  • visualize_contours (36-57)
  • visualize_contours (36-36)
src/wasm/modules/image/src/find_contours.cpp (1)
src/wasm/modules/image/include/find_contours.h (1)
  • contours (10-36)
🪛 Clang (14.0.6)
src/wasm/modules/image/src/visualize_contours.cpp

[error] 1-1: 'visualize_contours.h' file not found

(clang-diagnostic-error)

src/wasm/modules/image/src/find_contours.cpp

[error] 3-3: 'find_contours.h' file not found

(clang-diagnostic-error)

🪛 GitHub Actions: CI
src/hooks/useWasmWorker.js

[warning] 1-1: Code style issues found in 1 file. Run 'prettier --write' to fix.

src/components/WasmImageProcessor.jsx

[warning] 1-1: Code style issues found in 1 file. Run 'prettier --write' to fix.

🔇 Additional comments (10)
src/wasm/modules/image/include/Pixel.h (1)

8-9: LGTM!

The static_assert message now correctly references Pixel<NumberT> instead of the outdated RGBPixel<NumberT>, improving diagnostic clarity.

src/wasm/modules/image/include/visualize_contours.h (1)

1-10: LGTM!

Clean header with proper include guard. The exported function signature is well-defined and includes are minimal and appropriate.

src/wasm/modules/image/src/kmeans.cpp (1)

28-58: LGTM! Style modernization looks good.

The brace-initialization style updates are consistent and follow modern C++ practices.

src/components/WasmImageProcessor.jsx (2)

194-209: LGTM!

Memoizing static JSX with useMemo is a reasonable optimization to prevent unnecessary re-creation on each render.


171-191: The concern about ImageData compatibility is invalid. The WASM worker already returns Uint8ClampedArray (see wasmWorker.js line 49), which is the correct type required by the ImageData constructor. No conversion is needed in the canvas drawing code.

Likely an incorrect or invalid review comment.

src/wasm/modules/image/src/visualize_contours.cpp (1)

36-57: LGTM!

The visualize_contours implementation correctly loads the image, finds contours, assigns unique colors, and writes the modified data back to the buffer. The static analysis warning about missing header is a false positive—the build system handles include paths.

src/wasm/modules/image/include/find_contours.h (1)

1-38: LGTM overall!

The Contour class structure is clean with appropriate encapsulation for the inner pixel. The find_contours declaration matches the implementation. The hierarchy index fields (parent_index, first_child_index, etc.) provide good support for future hole/nesting handling per the PR objectives.

src/wasm/modules/image/src/find_contours.cpp (3)

14-28: LGTM!

The Moore 8-neighbor offsets are correctly defined in clockwise order with clear documentation.


30-54: LGTM!

The border mask computation correctly identifies pixels adjacent to differently-colored neighbors. The early break optimization is good.


199-224: LGTM with minor suggestion.

The main contour-finding logic correctly identifies and traces contours from unvisited border pixels. For large images, consider reserving capacity for contours if you can estimate the typical number of contours to reduce reallocations.

Comment thread src/hooks/useWasmWorker.js Outdated
Comment thread src/wasm/modules/image/include/find_contours.h Outdated
Comment thread src/wasm/modules/image/src/find_contours.cpp Outdated
Comment thread src/wasm/modules/image/src/find_contours.cpp Outdated
Comment thread src/wasm/modules/image/src/visualize_contours.cpp Outdated
@Ryan-Millard
Ryan-Millard marked this pull request as draft January 12, 2026 17:32
Comment thread src/wasm/modules/image/src/contours.cpp Fixed
Comment thread src/wasm/modules/image/src/kmeans_graph.cpp Fixed
Comment thread src/wasm/modules/image/src/kmeans_graph.cpp Fixed
…and provide more data

- input is const
- out_ params are return values
	- return labels & modified image
@Ryan-Millard Ryan-Millard mentioned this pull request Jan 14, 2026
12 tasks
Comment thread src/workers/wasmWorker.js Dismissed
Comment thread src/workers/wasmWorker.js Dismissed
Comment thread src/workers/wasmWorker.js Dismissed
Comment thread src/workers/wasmWorker.js Fixed
Comment thread src/workers/wasmWorker.js Fixed
@Krasner

Krasner commented Jan 18, 2026

Copy link
Copy Markdown
Collaborator

how can i visualize contours before svg?

@Ryan-Millard

Copy link
Copy Markdown
Owner Author

how can i visualize contours before svg?

I removed the code to visualize it because it isn't something I want on the live site. Ask ChatGPT to update WasmImageProcessor.jsx to display the image from findContours instead of navigating to the editor page.

By the way, were you familiar with React or JavaScript at all before you started contributing to this repo? It doesn't really change much, I just want to know where your skills lie.

@Ryan-Millard

Copy link
Copy Markdown
Owner Author
image

You're right. I messed something up here. I'll look into it.

@Krasner

Krasner commented Jan 18, 2026

Copy link
Copy Markdown
Collaborator

I can figure it out... have limited experience with react. more with javascript...

@Ryan-Millard

Copy link
Copy Markdown
Owner Author

The problem is on the main branch I think.

@Ryan-Millard

Copy link
Copy Markdown
Owner Author
image

It's on the main branch. I have no clue what could be causing this because the last change I can see to the C++ was the bilateral filter:

[21:51] ~/projects/Img2Num $ git log --oneline -20
73ed5a3 (HEAD -> main, origin/main, origin/HEAD) docs(readme): Revise maintainer and contributor sections (#223)
2a65b95 chore(deps): Bump @types/react in the all-npm group (#221)
2ef66e5 chore(deps): Bump the all-npm group with 2 updates (#215)
3d933d0 refactor(dependabot.yml): better grouping, labels & timing (#204)
b063c42 refactor(workflow): issue take-untake edit issue body instead of commenting(#200)
a736cdf (krasner/main) chore(README): remove deprecated content (#203)
60c1ebc chore(deps-dev): Bump the all-npm group with 2 updates (#199)
c281adc chore(deps): Bump actions/setup-node from 4 to 6 (#198)
9b72687 chore(deps): Bump actions/checkout from 4 to 6 (#197)
c399755 feat(bilateral filter): implement bilateral filter for denoising before K-Means (#191)
ad19bb1 feat(react: editor preview): add preview image option - show colored image (#201)
65616c4 docs(react: GlassCard): add complete GlassCard documentation (#179)
5e63ac9 feat(react): Add GlassSwitch component (#170)
c0f4a98 feat(ci): add linting job to CI workflow & update contributing docs (#146)
f897186 fix(vite config): ignore docs in dev and preview folders (#186)
8b7ea93 feat(analytics): add GA4 tracking for app and docs (#182)
492ff3b chore(deps): Bump qs from 6.14.0 to 6.14.1 in /docs and normalize all package-lock peer metadata (#180)
2b0ebe0 feat(UX): enhance global styles & NavBar layout (#119)
9b71238 chore(deps-dev): Bump the all-npm group with 2 updates (#169)
0645362 feat(theme): initial theme switch functionality (#121)
[21:51] ~/projects/Img2Num $

@Krasner

Krasner commented Jan 18, 2026

Copy link
Copy Markdown
Collaborator

yeah strange.
I get the same colorization issues if I look at imgBilteralFiltered (in RGB or CIELAB color space) or the kmeansed output.
Could it be some change to the npm packages or imageTracers.js?

@Ryan-Millard

Copy link
Copy Markdown
Owner Author

@Krasner, I have absolutely no clue. I'm going to look into it.

The bilateral filter was definitely the last change made to the C++ on the main branch:

[21:58] ~/projects/Img2Num $ git log --oneline -20 -- src/wasm/modules/image/
c399755 feat(bilateral filter): implement bilateral filter for denoising before K-Means (#191)
43e6160 feat(cross platform refactor & docker): Add Docker setup, CMake and cross-platform build scripts (#139)
8c40079 create(Docusaurus Skeleton): Basic template with starter code (#75)
1b85d2d feat(Merge Small Regions): Detect & merge regions in processed images that are difficult to click
25d4289 fix(.editorconfig & prettier): Fixes bad commit in 875da1e8b6d1988dd67167eb9e8a5fca9bbdac4b
34fad6d update(Call chain): Better call chain in JS for image preprocessing before K-Means
6978a71 create(Gaussian Blur FFT): Preprocessor for K-Means
1798830 create(2D FFT): Fast Fourier in 2 dimensions
632068e create(Iterative FFT): Fast Fourier Transform - spatial -> frequency domain
d482097 create(exported.h): Macro simplifies exported functions. Implemented in other files (#36)
6e7281c chore(Refactor K-Means into dedicated module): Segregated image_utils.cpp logically (#30)
eef0a57 fixed(Makefile): fixed Makefile flags the kmeans_clustering() function doesn't cause OOM errors.
7b28b87 squash(OOP Image Abstractions): Add several classes to assist with buffer manipulation (#18)
c2f96cf fix(image_utils.cpp): Fix alpha bug in kmeans_clustering function (#17 & #19)
a387ce5 refactor(Dev builds): Setup change detection & hot reloads/rebuilds (#6)
[21:58] ~/projects/Img2Num $ git branch
  docs/readme/maintainers-contributors
  feat/bilater_filter_gpu
  feat/contour-tracing
  feat/contour-tracing-cleanup
  feat/export-processed-image/issue-85
* main
  modified
[22:00] ~/projects/Img2Num $

@Ryan-Millard

Copy link
Copy Markdown
Owner Author

This is not the type of bug I want to fix at all.😂

It is a problem from around the time of the bilateral filter:

[22:00] ~/projects/Img2Num $ git log --oneline -20 -- src/wasm/modules/image/
c399755 feat(bilateral filter): implement bilateral filter for denoising before K-Means (#191)
43e6160 feat(cross platform refactor & docker): Add Docker setup, CMake and cross-platform build scripts (#139)
8c40079 create(Docusaurus Skeleton): Basic template with starter code (#75)
1b85d2d feat(Merge Small Regions): Detect & merge regions in processed images that are difficult to click
25d4289 fix(.editorconfig & prettier): Fixes bad commit in 875da1e8b6d1988dd67167eb9e8a5fca9bbdac4b
34fad6d update(Call chain): Better call chain in JS for image preprocessing before K-Means
6978a71 create(Gaussian Blur FFT): Preprocessor for K-Means
1798830 create(2D FFT): Fast Fourier in 2 dimensions
632068e create(Iterative FFT): Fast Fourier Transform - spatial -> frequency domain
d482097 create(exported.h): Macro simplifies exported functions. Implemented in other files (#36)
6e7281c chore(Refactor K-Means into dedicated module): Segregated image_utils.cpp logically (#30)
eef0a57 fixed(Makefile): fixed Makefile flags the kmeans_clustering() function doesn't cause OOM errors.
7b28b87 squash(OOP Image Abstractions): Add several classes to assist with buffer manipulation (#18)
c2f96cf fix(image_utils.cpp): Fix alpha bug in kmeans_clustering function (#17 & #19)
a387ce5 refactor(Dev builds): Setup change detection & hot reloads/rebuilds (#6)
[22:00] ~/projects/Img2Num $ git checkout c399755
Note: switching to 'c399755'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

Or undo this operation with:

  git switch -

Turn off this advice by setting config variable advice.detachedHead to false

HEAD is now at c399755 feat(bilateral filter): implement bilateral filter for denoising before K-Means (#191)
[22:01] ~/projects/Img2Num $ git status
HEAD detached at c399755
nothing to commit, working tree clean
[22:01] ~/projects/Img2Num $ ./img2num dev

> imgtonum@0.0.0 dev
> vite

Found wasm module: image
🔨 Building WASM modules on startup...
✅ WASM modules built successfully
Build output:
> imgtonum@0.0.0 build-wasm
> node scripts/build-wasm.js


🔧 Building WASM modules (Release)...


> emcmake cmake -S /usr/src/app/src/wasm -B /usr/src/app/src/wasm/cmake-build -DCMAKE_BUILD_TYPE=Release

-- Build type: Release
-- Emscripten: /opt/emsdk/upstream/emscripten
-- Adding module: image
-- Module 'image' configured (export: createImageModule)
-- Configuring done
-- Generating done
-- Build files have been written to: /usr/src/app/src/wasm/cmake-build

> cmake --build /usr/src/app/src/wasm/cmake-build --parallel --config Release

Scanning dependencies of target image_wasm
[ 14%] Building CXX object modules/image/CMakeFiles/image_wasm.dir/src/kmeans.cpp.o
[ 28%] Building CXX object modules/image/CMakeFiles/image_wasm.dir/src/image_utils.cpp.o
[ 42%] Building CXX object modules/image/CMakeFiles/image_wasm.dir/src/mergeSmallRegionsInPlace.cpp.o
[ 57%] Linking CXX executable ../../../modules/image/build/index.js
[100%] Built target image_wasm

✅ WASM build complete!


Build warnings: configure: cmake -S /usr/src/app/src/wasm -B /usr/src/app/src/wasm/cmake-build -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=/opt/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_CROSSCOMPILING_EMULATOR=/opt/emsdk/node/22.16.0_64bit/bin/node

⏳ Generating credits.json...
✅ contributor-credits.json generated!

  VITE v7.3.0  ready in 15319 ms

  ➜  Local:   http://localhost:5173/Img2Num/
  ➜  Network: http://172.18.0.2:5173/Img2Num/
  ➜  press h + enter to show help
image

@Krasner

Krasner commented Jan 18, 2026

Copy link
Copy Markdown
Collaborator

my feat/contour-tracing is after the bilateralFilter and so far i don't see this issue.
debugging as fast as i can :)

@Ryan-Millard

Copy link
Copy Markdown
Owner Author

my feat/contour-tracing is after the bilateralFilter and so far i don't see this issue. debugging as fast as i can :)

That doesn't make sense either. How do you have no problem? It should be the same code.😭

@Ryan-Millard

Copy link
Copy Markdown
Owner Author

Can you run the command below from your own contours branch for me please:
(Replace origin with the name you gave to the remote for my repo)

git diff origin/main -- src/components/WasmImageProcessor.jsx src/wasm/modules/image/src/{image_utils.cpp,bilateral_filter.cpp,kmeans.cpp}

@Krasner

Krasner commented Jan 18, 2026

Copy link
Copy Markdown
Collaborator

one sec. pretty sure the error is with svg.

Taking my current feat/contour-tracing which had no SVG but just the intermediate image visualizers, I add back in SVG (navigate(/editor ... ))

this is the intermediate image (no contours)
image

with the uint8ClampedArrayToSVG as it is in image-utils.js I see the same loss of color.

However if I change the options to ImageTracer - specifically numberofcolors then the colors return. that option is by default 16 so this function is doing yet another round of color quantization on top of what exists already

let svgString = ImageTracer.imagedataToSVG(imageData, {
      ltres: 0,
      rightangleenhance: false,
      numberofcolors: 256,
    });

with 256 the colors are looking much better
image

@Krasner

Krasner commented Jan 18, 2026

Copy link
Copy Markdown
Collaborator
-- src/components/WasmImageProcessor.jsx src/wasm/modules/image/src/{image_utils.cpp,bilateral_filter.cpp,kmeans.cpp}

diff.log

see attached

@Krasner

Krasner commented Jan 18, 2026

Copy link
Copy Markdown
Collaborator

another solution is to run kmeans again after contours to force a fixed number of colors again. that might have changed as regions were merged

step(80);
      const contours = await findContours({
        pixels: kmeansed,
        labels,
        width,
        height,
      });

      step(90);
      const { pixels: kmeansed2, labels2 } = await kmeans({
        ...fileData,
        pixels: contours,
        num_colors: 16,
      });

      step(95);
      const svg = await uint8ClampedArrayToSVG({
        pixels: kmeansed2,
        width,
        height,
      });
     ```

@Ryan-Millard

Copy link
Copy Markdown
Owner Author

Okay... So the problem is num_colors. That's not a bug.😎

num_colors = 8:

      const { pixels: kmeansed, labels } = await kmeans({
        ...fileData,
        pixels: thresholded,
        num_colors: 8,
      });
image

num_colors = 16:

      const { pixels: kmeansed, labels } = await kmeans({
        ...fileData,
        pixels: thresholded,
        num_colors: 16,
      });
image

@Ryan-Millard

Copy link
Copy Markdown
Owner Author

We'll add a feature to fix this soon. I'm not sure who will, but I'll try to get it going.

@Krasner

Krasner commented Jan 18, 2026

Copy link
Copy Markdown
Collaborator

OK! In that case we should have a better kmeans color initialization approach. Some how initialize to important colors based on a histogram, for example.

@Ryan-Millard

Copy link
Copy Markdown
Owner Author

another solution is to run kmeans again after contours to force a fixed number of colors again. that might have changed as regions were merged

step(80);
      const contours = await findContours({
        pixels: kmeansed,
        labels,
        width,
        height,
      });

      step(90);
      const { pixels: kmeansed2, labels2 } = await kmeans({
        ...fileData,
        pixels: contours,
        num_colors: 16,
      });

      step(95);
      const svg = await uint8ClampedArrayToSVG({
        pixels: kmeansed2,
        width,
        height,
      });
     ```

There's no need - the problem is with a single parameter. I changed it to 8 in the bilateral filter PR so users wouldn't have to wait so long to get a result. We need to get some buttons added to the home screen to configure the parameters.

@Ryan-Millard

Copy link
Copy Markdown
Owner Author

Otherwise, do you think this PR can be merged?

@Krasner

Krasner commented Jan 18, 2026

Copy link
Copy Markdown
Collaborator

Yes I think it's good to go

@Ryan-Millard

Copy link
Copy Markdown
Owner Author

Thanks!

@Ryan-Millard
Ryan-Millard merged commit 22f50fd into main Jan 18, 2026
7 checks passed
@Ryan-Millard
Ryan-Millard deleted the feat/contour-tracing branch January 18, 2026 20:44
@coderabbitai coderabbitai Bot mentioned this pull request Feb 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request important This item is important and needs to be worked on ASAP

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(trace contours): add contour tracing after K-Means clustering

3 participants