Skip to content

feat: unified image_to_svg function as complete pipeline#335

Merged
Ryan-Millard merged 44 commits into
Ryan-Millard:mainfrom
Krasner:feat/img_to_svg
May 11, 2026
Merged

feat: unified image_to_svg function as complete pipeline#335
Ryan-Millard merged 44 commits into
Ryan-Millard:mainfrom
Krasner:feat/img_to_svg

Conversation

@Krasner
Copy link
Copy Markdown
Collaborator

@Krasner Krasner commented Apr 29, 2026

Combines bilateral_filter, kmeans and label_to_svg into one unified image_to_svg function.
Updated js and py bindings

What was changed & why

Fixes: #318
Fixes: #360
Fixes: #361

Changes

Testing & Verification

Additional Resources

@Krasner Krasner requested a review from Ryan-Millard as a code owner April 29, 2026 15:49
@github-actions github-actions Bot added c/cpp Changes to C or C++ files js/ts example-app Updates to code related to demonstration (example) applications core labels Apr 29, 2026
@Krasner Krasner added the python changes to python bindings or python code label Apr 29, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 29, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a unified image_to_svg pipeline: new ImageToSvgConfig types and core image_to_svg implementation; exposes C config lifecycle and C API; updates WASM/JS and Python bindings and example apps to call the new config-driven image_to_svg entrypoint.

Changes

Image-to-SVG consolidated pipeline

Layer / File(s) Summary
Data Shape
core/include/img2num.h, bindings/c/include/cimg2num.h
Adds BilateralFilterConfig, KMeansConfig, and ImageToSvgConfig types; updates C header to accept const ImageToSvgConfig *config for img2num_image_to_svg and adds img2num_config_create/img2num_config_free.
Core Implementation
core/src/internal/image_to_svg.cpp
Implements img2num::image_to_svg(const uint8_t*, int, int, const ImageToSvgConfig&): allocates working buffers, applies bilateral filter and k-means using config fields, and returns SVG string filtered by min_cluster_area.
C ABI Wiring
bindings/c/src/cimg2num.cpp
Adds C-exported img2num_config_create() / img2num_config_free() and img2num_image_to_svg(...) that dereference/convert the C config and return a malloc'd char* SVG.
WASM Wrapper
bindings/js/src/wasm_wrapper.cpp, bindings/js/CMakeLists.txt
Adds EMSCRIPTEN entrypoint image_to_svg(...) that builds an ImageToSvgConfig, calls img2num_image_to_svg, frees the config; updates CMake to link Img2Num.
JS High-Level Wrapper
packages/js/safeWasmWrappers.js
Adds imgToSVG(...) async helper that calls WASM image_to_svg, forwards bilateral/kmeans/threshold params, and returns { svg, visualization }.
Python Bindings
bindings/py/src/img2num_pybind.cpp
Exports BilateralFilterConfig, KMeansConfig, ImageToSvgConfig (constructors, fields, repr) and image_to_svg(data, width, height, config) pybind binding.
Python API
packages/py/img2num/api.py
Re-exports ImageToSvgConfig and adds image_to_svg(image, config, *, width, height) wrapper with _inject_dims decorator that forwards to _image_to_svg.
Examples / Consumers
example-apps/console-c/main.c, example-apps/console-cpp/main.cpp, example-apps/console-py/main.py, example-apps/react-js/src/components/WasmImageProcessor.jsx
Switches examples to the unified image_to_svg API (creates/configures ImageToSvgConfig, calls single function), updates outputs and error handling accordingly.
Minor Maintenance
core/include/internal/gpu.h, core/src/internal/kmeans.cpp, README.md
GPU destructor clears gpu_initialized; removed iostream/logging from kmeans; README badge adds py.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Config as ImageToSvgConfig
    participant Core as C++ Core<br/>image_to_svg
    participant BF as BilateralFilter
    participant KM as KMeans
    participant SVG as labels_to_svg

    Client->>Config: create/configure (sigma, k, color_space, min_area)
    Client->>Core: image_to_svg(data, width, height, config)
    activate Core
    Core->>Core: allocate buffers / copy input
    Core->>BF: apply bilateral_filter(config.bilateral_filter, config.color_space)
    BF-->>Core: filtered image
    Core->>KM: run kmeans(config.kmeans, config.color_space)
    KM-->>Core: labels
    Core->>SVG: labels_to_svg(labels, config.min_cluster_area)
    SVG-->>Core: svg string
    Core-->>Client: svg string
    deactivate Core
Loading
sequenceDiagram
    participant Browser
    participant Wrapper as safeWasmWrappers
    participant WASM as WASM Module
    participant CBinding as C Binding
    participant CppCore as C++ Core

    Browser->>Wrapper: imgToSVG(pixels, params)
    Wrapper->>WASM: image_to_svg(ptr, w, h, sigma_spatial, sigma_range, k, max_iter, min_area, color_space)
    activate WASM
    WASM->>WASM: create ImageToSvgConfig
    WASM->>CBinding: img2num_image_to_svg(data, config)
    CBinding->>CppCore: image_to_svg(data, w, h, *config)
    CppCore-->>CBinding: std::string(svg)
    CBinding-->>WASM: char* (malloc'd) svg
    WASM->>WASM: free config
    WASM-->>Wrapper: svg string
    Wrapper-->>Browser: { svg, visualization }
    deactivate WASM
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • Ryan-Millard

"i hop to code and tweak a plan,
configs held snug within my paw—
from pixels to vector, span by span,
one call, one flow, applaud the draw! 🐇✨"

🚥 Pre-merge checks | ✅ 7 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (7 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR implements all primary objectives from issue #318: provides a unified image_to_svg function in the core library, supports configuration-based manual setup via ImageToSvgConfig, and creates bindings for C, JavaScript, and Python.
Out of Scope Changes check ✅ Passed Minor scope additions include GPU destructor flag reset and removal of iostream logging from kmeans.cpp, but these are directly related to supporting the core image_to_svg functionality and do not represent unrelated changes.
No Ai Slop Pr Description ✅ Passed The PR description specifically describes the unified image_to_svg function, its constituent components, binding updates, and issue #318 reference with concrete details avoiding generic boilerplate.
No Strangely-Named Root Markdown Files ✅ Passed The repository contains only three acceptable markdown files at root level. The PR only modifies README.md and does not add unconventionally-named markdown files.
Coderabbit Config Needs Update ✅ Passed PR does not introduce new programming languages or file types requiring .coderabbit.yaml updates; Python was already supported.
Description check ✅ Passed The PR description clearly relates to the changeset, detailing a unified image_to_svg function combining bilateral filtering, k-means, and SVG generation, with updates to JavaScript and Python bindings.
Title check ✅ Passed The title 'feat: unified image_to_svg function as complete pipeline' follows Conventional Commits format with 'feat:' prefix and clearly describes the main change: introducing a unified image_to_svg function that serves as a complete pipeline combining bilateral filtering, k-means clustering, and SVG generation.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

@Krasner
Copy link
Copy Markdown
Collaborator Author

Krasner commented Apr 29, 2026

@Ryan-Millard overly paranoid lint check

Copy link
Copy Markdown
Owner

@Ryan-Millard Ryan-Millard left a comment

Choose a reason for hiding this comment

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

This is very nice and clean. Thank you.

There are just a few minor questions I have and one major architectural discussion I think we need to have before this can be merged. The architectural concern is about the core image_to_svg function.

Comment thread example-apps/react-js/src/components/WasmImageProcessor.jsx Outdated
Comment thread packages/js/safeWasmWrappers.js Outdated
Comment thread core/include/img2num.h Outdated
Comment thread core/src/internal/bilateral_filter.cpp Outdated
Comment thread core/src/internal/bilateral_filter_gpu.cpp Outdated
Comment thread core/src/internal/kmeans_gpu.cpp Outdated
Comment thread example-apps/console-cpp/main.cpp
Comment thread example-apps/console-py/main.py
coderabbitai[bot]
coderabbitai Bot previously approved these changes Apr 30, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@bindings/c/include/cimg2num.h`:
- Around line 15-23: The C header exposes an opaque ImageToSvgConfig but
provides no way for C callers to construct or initialize it, making
img2num_image_to_svg unusable from pure C; add C-compatible construction and
initialization APIs (e.g., img2num_image_to_svg_config_new /
img2num_image_to_svg_config_free and an init/reset function or a flat C struct
equivalent) and expose setter/getter functions for any fields used by
img2num_image_to_svg so C users can populate the config; update cimg2num.h (and
the corresponding symbols referenced around ImageToSvgConfig and
img2num_image_to_svg, including the other occurrences at lines 52-56) to declare
these factory/init/setter prototypes so the public C API mirrors the C++
surface.

In `@bindings/c/src/cimg2num.cpp`:
- Around line 76-87: The lambda in img2num_image_to_svg dereferences cfg
unconditionally causing a crash when config is NULL; update the lambda in
img2num_image_to_svg to first check if cfg is nullptr and if so either use a
default ImageToSvgConfig (create a local default_config) or immediately set
result to nullptr and return (so the C binding returns the sentinel nullptr on
failure), and only call img2num::image_to_svg(d, w, h, *cfg) after ensuring cfg
is non-null; also ensure memory allocation and memcpy happen only after svg is
produced and that failures leave result as nullptr.

In `@bindings/js/src/wasm_wrapper.cpp`:
- Around line 41-47: The WASM wrapper function image_to_svg must construct an
ImageToSvgConfig instance from the wrapper arguments and pass its pointer to
img2num_image_to_svg (which now expects const ImageToSvgConfig*); update
image_to_svg to populate a local ImageToSvgConfig with fields mapped from data,
width, height, sigma_spatial, sigma_range, k, max_iter, min_area, and
color_space, then call img2num_image_to_svg(&config) and return the result,
ensuring types match the new C API.

In `@bindings/py/src/img2num_pybind.cpp`:
- Around line 124-140: The ImageToSvgConfig binding exposes the nested types but
not the struct members, so Python cannot set bilateral_filter, kmeans,
min_cluster_area or color_space; update the pybind definition for
img2num::ImageToSvgConfig (the variable config) to expose those members by
adding .def_readwrite("bilateral_filter",
&img2num::ImageToSvgConfig::bilateral_filter), .def_readwrite("kmeans",
&img2num::ImageToSvgConfig::kmeans), .def_readwrite("min_cluster_area",
&img2num::ImageToSvgConfig::min_cluster_area) and .def_readwrite("color_space",
&img2num::ImageToSvgConfig::color_space) (remove the commented-out block and
keep the existing custom init that returns IMAGE_TO_SVG_DEFAULT_CONFIG).

In `@core/include/img2num.h`:
- Around line 18-38: Add Doxygen documentation for the new public types and
members: document the ImageToSvgConfig struct, its nested BilateralFilterConfig
and KMeansConfig types, and each public field (sigma_spatial, sigma_range,
min_cluster_area, color_space, k, max_iter) so they appear in generated API
docs; also add a brief doc comment for the IMAGE_TO_SVG_DEFAULT_CONFIG constant.
Ensure comments use Doxygen style (/** ... */) immediately above the
declarations of ImageToSvgConfig, BilateralFilterConfig, KMeansConfig, and
IMAGE_TO_SVG_DEFAULT_CONFIG and briefly describe purpose, units/range where
applicable, and defaults.

In `@core/src/internal/image_to_svg.cpp`:
- Around line 10-17: The function image_to_svg currently allocates
img_data/out_data/out_labels and calls std::memcpy using width * height * 4
computed as signed ints, which can overflow or be negative; validate inputs at
the top of image_to_svg by checking that width and height are positive and that
the product (cast to size_t) does not overflow when multiplied by 4 (e.g.,
ensure (size_t)width * (size_t)height <= SIZE_MAX / 4), return an error or empty
string on invalid dimensions, then compute sizes into size_t variables and use
those for vector sizing and for the memcpy length to avoid huge or negative
allocations and unsafe copies (refer to image_to_svg, img_data, out_data,
out_labels, and the memcpy call).

In `@example-apps/console-cpp/main.cpp`:
- Line 69: The call to img2num::image_to_svg is using img_data which has been
modified by the manual bilateral/kmeans path; change it to use the original
unmodified image buffer (e.g., save the raw input into a separate variable
before applying bilateral/kmeans and pass that raw buffer to
img2num::image_to_svg) so the example demonstrates the unified API on raw input;
ensure references to image_to_svg and the bilateral/kmeans modification code use
the preserved original buffer variable rather than the mutated img_data.

In `@packages/js/safeWasmWrappers.js`:
- Around line 234-240: The wrapper for the image_to_svg entrypoint incorrectly
returns a visualization from result.output.pixels even though the WASM function
only produces an SVG string; update the code in safeWasmWrappers.js around the
callWasm invocation for funcName "image_to_svg" (and any helper that consumes
its return) to stop accessing result.output.pixels and only return the SVG
(e.g., return { svg: result.returnValue } or equivalent), removing the
nonexistent visualization field so callers no longer expect an output pixel
buffer.
- Around line 233-236: The public API destructures and forwards a param named
sigma_pixels but the project uses sigma_range elsewhere; update the imgToSVG
function signature to destructure sigma_range (default 50) instead of
sigma_pixels and ensure the args passed to callWasm use the sigma_range property
(i.e., replace occurrences of sigma_pixels with sigma_range in the imgToSVG
parameter list and the args object so callers using { sigma_range: ... } are
honored).

In `@packages/py/img2num/api.py`:
- Around line 69-71: The wrapper image_to_svg currently requires a config even
though the core _image_to_svg accepts a default; make config optional by
defaulting it to ImageToSvgConfig() (e.g., accept config=None and if None set
config = ImageToSvgConfig()) before calling _image_to_svg(image, width, height,
config), so callers can use the simple form or pass a custom config; reference
image_to_svg, _image_to_svg, and ImageToSvgConfig when locating and updating the
code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3c3b9f2e-8ad0-48ee-bbb4-025f78b337e4

📥 Commits

Reviewing files that changed from the base of the PR and between 5d40e73 and 477e812.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (16)
  • bindings/c/include/cimg2num.h
  • bindings/c/src/cimg2num.cpp
  • bindings/js/src/wasm_wrapper.cpp
  • bindings/py/src/img2num_pybind.cpp
  • core/include/img2num.h
  • core/include/internal/gpu.h
  • core/src/internal/bilateral_filter.cpp
  • core/src/internal/bilateral_filter_gpu.cpp
  • core/src/internal/image_to_svg.cpp
  • core/src/internal/kmeans_gpu.cpp
  • core/src/internal/labels_to_svg.cpp
  • example-apps/console-cpp/main.cpp
  • example-apps/console-py/main.py
  • example-apps/react-js/src/components/WasmImageProcessor.jsx
  • packages/js/safeWasmWrappers.js
  • packages/py/img2num/api.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (8)
example-apps/**

⚙️ CodeRabbit configuration file

example-apps/**: - Example applications. These are for demonstration; keep them minimal and ensure
they correctly reflect the public API. Flag any use of internal/private APIs.

  • IMPORTANT: These example apps must be a good reflection of how to use Img2Num's
    library, which means that they need good comments and must have clean code. This
    is meant to be external and is designed for others to use to get started.

Files:

  • example-apps/console-py/main.py
  • example-apps/console-cpp/main.cpp
  • example-apps/react-js/src/components/WasmImageProcessor.jsx
**/*.py

⚙️ CodeRabbit configuration file

**/*.py: Python source files. Review for:

  • PEP 8 compliance (enforced by ruff; focus on logic, not style).
  • Type annotation coverage — prefer typed signatures for public functions.
  • Exception handling: avoid bare except: clauses; always catch specific exceptions.
  • Resource management: use with statements for file/socket/connection handling.
  • No mutable default arguments (e.g. def foo(x=[]):).
  • When suggesting how to run Python tooling, prefer the ./img2num Docker wrapper
    rather than direct python / ruff / pylint invocations.

Files:

  • example-apps/console-py/main.py
  • packages/py/img2num/api.py
**/*.{c,cc,cpp,cxx,h,hpp,hxx}

📄 CodeRabbit inference engine (.clang-format)

**/*.{c,cc,cpp,cxx,h,hpp,hxx}: Follow Google style guide for C/C++ code formatting
Use 4 spaces for indentation
Maintain a column limit of 100 characters per line
Do not allow short functions on a single line

Files:

  • core/src/internal/kmeans_gpu.cpp
  • core/src/internal/bilateral_filter.cpp
  • core/include/internal/gpu.h
  • bindings/js/src/wasm_wrapper.cpp
  • core/src/internal/labels_to_svg.cpp
  • core/src/internal/image_to_svg.cpp
  • example-apps/console-cpp/main.cpp
  • core/src/internal/bilateral_filter_gpu.cpp
  • bindings/c/include/cimg2num.h
  • bindings/c/src/cimg2num.cpp
  • core/include/img2num.h
  • bindings/py/src/img2num_pybind.cpp
**/*.{hpp,cpp,c,h}

📄 CodeRabbit inference engine (.editorconfig)

**/*.{hpp,cpp,c,h}: Use 4-space indentation for C/C++ files
Maintain 120 character maximum line length for C/C++ files

Files:

  • core/src/internal/kmeans_gpu.cpp
  • core/src/internal/bilateral_filter.cpp
  • core/include/internal/gpu.h
  • bindings/js/src/wasm_wrapper.cpp
  • core/src/internal/labels_to_svg.cpp
  • core/src/internal/image_to_svg.cpp
  • example-apps/console-cpp/main.cpp
  • core/src/internal/bilateral_filter_gpu.cpp
  • bindings/c/include/cimg2num.h
  • bindings/c/src/cimg2num.cpp
  • core/include/img2num.h
  • bindings/py/src/img2num_pybind.cpp
core/**/*.{cpp,c,h,hpp}

⚙️ CodeRabbit configuration file

core/**/*.{cpp,c,h,hpp}: This is the Img2Num core C/C++ library. Review for:

  • Memory safety: null pointer dereferences, use-after-free, buffer overflows.
  • Correct RAII usage and smart pointer idioms.
  • Adherence to the .clang-format style; formatting must be applied via
    ./img2num format-cpp (or ./img2num format-wasm for WASM modules),
    NOT by calling clang-format directly.
  • When suggesting build/test steps, always use the Docker-first wrapper scripts
    (./img2num, img2num.ps1, or img2num.bat) rather than direct tool invocations,
    since contributors may not have dependencies installed locally.
  • Must have Doxygen docstrings in the API (img2num.h) files. This is important!

Files:

  • core/src/internal/kmeans_gpu.cpp
  • core/src/internal/bilateral_filter.cpp
  • core/include/internal/gpu.h
  • core/src/internal/labels_to_svg.cpp
  • core/src/internal/image_to_svg.cpp
  • core/src/internal/bilateral_filter_gpu.cpp
  • core/include/img2num.h
**/*.{cpp,c,h,hpp}

⚙️ CodeRabbit configuration file

**/*.{cpp,c,h,hpp}: - For any C/C++ code outside core/ (e.g. bindings, example-apps), apply the same
memory-safety and style standards as the core. Formatting via ./img2num format-cpp.

  • Doxygen docstrings are required.

Files:

  • core/src/internal/kmeans_gpu.cpp
  • core/src/internal/bilateral_filter.cpp
  • core/include/internal/gpu.h
  • bindings/js/src/wasm_wrapper.cpp
  • core/src/internal/labels_to_svg.cpp
  • core/src/internal/image_to_svg.cpp
  • example-apps/console-cpp/main.cpp
  • core/src/internal/bilateral_filter_gpu.cpp
  • bindings/c/include/cimg2num.h
  • bindings/c/src/cimg2num.cpp
  • core/include/img2num.h
  • bindings/py/src/img2num_pybind.cpp
bindings/**

⚙️ CodeRabbit configuration file

bindings/**: Language bindings for the Img2Num library. Ensure the public API surface matches
the core C/C++ headers and that error propagation is handled correctly for each
binding language. Also ensure the relevant docstrings are present to enable
auto-generation of documentation.

Files:

  • bindings/js/src/wasm_wrapper.cpp
  • bindings/c/include/cimg2num.h
  • bindings/c/src/cimg2num.cpp
  • bindings/py/src/img2num_pybind.cpp
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.editorconfig)

**/*.{js,ts,jsx,tsx}: Use 2-space indentation for JavaScript and TypeScript files
Maintain 200 character maximum line length for JavaScript/TypeScript files

Files:

  • packages/js/safeWasmWrappers.js
  • example-apps/react-js/src/components/WasmImageProcessor.jsx
🧠 Learnings (18)
📓 Common learnings
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-25T16:20:34.359Z
Learning: In the Ryan-Millard/Img2Num repository, the following follow-up issues were created after merging PR `#307` (Python bindings): `#328` (Python docstrings/docs), `#329` (numpy img.shape inference for width/height), `#330` (.coderabbit.yaml Python tooling config), `#331` (CI workflow for Python bindings build test).
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-25T16:31:13.204Z
Learning: In the Ryan-Millard/Img2Num repository, issue `#331` (CI workflow for Python bindings build test) was addressed in PR `#307` via the `build-py` job in `.github/workflows/cmake-build.yml`. That job runs `uv venv`, `uv sync --no-build-isolation`, a smoke test (`uv run python3 -c "import img2num;"`), and uploads artifacts.
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-23T18:48:02.692Z
Learning: In the Ryan-Millard/Img2Num repository, PR `#311` adds `.coderabbit.yaml` to main. PR `#307` (adding Python .py files) must rebase onto main after `#311` merges so that `.coderabbit.yaml` exists and can be updated with: `ruff: enabled: true`, `pylint: enabled: true`, `flake8: enabled: false` under `tools:`, and a `path_instructions` entry for `**/*.py` covering PEP 8, type annotations, exception handling, resource management, mutable default arguments, and Docker-first command suggestions via the `./img2num` wrapper. The "CodeRabbit Config Needs Update" custom check will automatically flag `#307` if the Python config is missing.
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-23T18:48:02.712Z
Learning: In the Ryan-Millard/Img2Num repository, PR `#311` adds `.coderabbit.yaml` to main. PR `#307` (adding Python .py files) must rebase onto main after `#311` merges so that `.coderabbit.yaml` exists and can be updated with: `ruff: enabled: true`, `pylint: enabled: true`, `flake8: enabled: false` under `tools:`, and a `path_instructions` entry for `**/*.py` covering PEP 8, type annotations, exception handling, resource management, mutable default arguments, and Docker-first command suggestions via the `./img2num` wrapper. The "CodeRabbit Config Needs Update" custom check will automatically flag `#307` if the Python config is missing.
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-01T20:45:20.732Z
Learning: In the Ryan-Millard/Img2Num repository, PRs that introduce documentation or code examples referencing functions or symbols not present in the codebase (e.g., a `vectorize` function from `img2num`) should be flagged as potential AI-generated slop. Always cross-reference newly introduced API names/symbols against actual codebase exports.
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-23T18:35:01.229Z
Learning: In the Ryan-Millard/Img2Num repository, Python (.py files) is being added to the codebase via PR `#307`. The `.coderabbit.yaml` should include `ruff: enabled: true` and `pylint: enabled: true` under `tools:`, and a `path_instructions` entry for `**/*.py` covering PEP 8, type annotations, exception handling, resource management, mutable default arguments, and Docker-first command suggestions via the `./img2num` wrapper.
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-23T18:35:01.229Z
Learning: In the Ryan-Millard/Img2Num repository, Python (.py files) is being added to the codebase via PR `#307`. The `.coderabbit.yaml` should include `ruff: enabled: true` and `pylint: enabled: true` under `tools:`, and a `path_instructions` entry for `**/*.py` covering PEP 8, type annotations, exception handling, resource management, mutable default arguments, and Docker-first command suggestions.
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-01T20:45:20.732Z
Learning: In the Ryan-Millard/Img2Num repository, a PR body containing generic boilerplate such as "This contribution was made as part of an open-source documentation improvement initiative. All changes are meant to be helpful additions to the project." is a strong signal of an AI-generated slop PR and should be explicitly flagged during review.
📚 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:

  • core/src/internal/kmeans_gpu.cpp
  • core/src/internal/bilateral_filter.cpp
  • bindings/js/src/wasm_wrapper.cpp
  • core/src/internal/labels_to_svg.cpp
  • core/src/internal/image_to_svg.cpp
  • example-apps/console-cpp/main.cpp
  • core/src/internal/bilateral_filter_gpu.cpp
  • bindings/c/src/cimg2num.cpp
  • bindings/py/src/img2num_pybind.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:

  • core/src/internal/kmeans_gpu.cpp
  • core/src/internal/bilateral_filter.cpp
  • bindings/js/src/wasm_wrapper.cpp
  • core/src/internal/labels_to_svg.cpp
  • core/src/internal/image_to_svg.cpp
  • example-apps/console-cpp/main.cpp
  • core/src/internal/bilateral_filter_gpu.cpp
  • bindings/c/src/cimg2num.cpp
  • bindings/py/src/img2num_pybind.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:

  • core/src/internal/kmeans_gpu.cpp
  • core/src/internal/bilateral_filter.cpp
  • bindings/js/src/wasm_wrapper.cpp
  • core/src/internal/labels_to_svg.cpp
  • core/src/internal/image_to_svg.cpp
  • example-apps/console-cpp/main.cpp
  • core/src/internal/bilateral_filter_gpu.cpp
  • bindings/c/src/cimg2num.cpp
  • bindings/py/src/img2num_pybind.cpp
📚 Learning: 2026-01-19T00:02:34.957Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 234
File: src/wasm/modules/image/src/kmeans.cpp:79-81
Timestamp: 2026-01-19T00:02:34.957Z
Learning: In C++ multithreading contexts, prefer unsigned types for thread-count-like parameters (e.g., n_threads) to prevent negative values. Validate that the value is at least 1 before any division to avoid divide-by-zero at runtime. At the start of functions handling such values, create a safe count like: const unsigned int thread_count{std::max(1u, n_threads)}; This ensures non-negative, non-zero usage for divisions and related arithmetic. Apply this pattern to similar parameters across C++ modules, not just the specific file.

Applied to files:

  • core/src/internal/kmeans_gpu.cpp
  • core/src/internal/bilateral_filter.cpp
  • bindings/js/src/wasm_wrapper.cpp
  • core/src/internal/labels_to_svg.cpp
  • core/src/internal/image_to_svg.cpp
  • example-apps/console-cpp/main.cpp
  • core/src/internal/bilateral_filter_gpu.cpp
  • bindings/c/src/cimg2num.cpp
  • bindings/py/src/img2num_pybind.cpp
📚 Learning: 2026-02-25T21:24:19.036Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 272
File: core/src/internal/bilateral_filter_gpu.cpp:171-185
Timestamp: 2026-02-25T21:24:19.036Z
Learning: Adopt brace initialization (e.g., Type var{}) over copy initialization (e.g., Type var = {}) for zero-initialization in C++ across the repository. This should apply to variables, arrays, and structs in most C++ files (e.g., core/src/internal/bilateral_filter_gpu.cpp). It improves safety by avoiding narrowing conversions and makes initialization intent explicit.

Applied to files:

  • core/src/internal/kmeans_gpu.cpp
  • core/src/internal/bilateral_filter.cpp
  • bindings/js/src/wasm_wrapper.cpp
  • core/src/internal/labels_to_svg.cpp
  • core/src/internal/image_to_svg.cpp
  • example-apps/console-cpp/main.cpp
  • core/src/internal/bilateral_filter_gpu.cpp
  • bindings/c/src/cimg2num.cpp
  • bindings/py/src/img2num_pybind.cpp
📚 Learning: 2026-04-09T20:09:36.128Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-09T20:09:36.128Z
Learning: Img2Num is a cross-platform library supporting multiple runtimes. When the GPU is inaccessible for any reason (e.g., adapter/device acquisition fails, the runtime blocks GPU access, or no GPU is available), the code must not assume usability based solely on GPU API availability. Instead, it must detect the actual failure, automatically fall back to CPU execution, provide a clear message explaining the fallback, and include timeout guards to prevent hangs. This applies across all supported runtimes, not just browsers.

Applied to files:

  • core/src/internal/bilateral_filter.cpp
📚 Learning: 2026-04-27T15:40:33.329Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 333
File: bindings/c/src/cimg2num.cpp:0-0
Timestamp: 2026-04-27T15:40:33.329Z
Learning: In the Ryan-Millard/Img2Num project, `bindings/c/src/cimg2num.cpp` contains `extern "C"` C bindings. Do NOT suggest throwing C++ exceptions (e.g., `std::bad_alloc`) from within these C binding functions, even from lambdas passed internally to `clear_last_error_and_catch`. The correct C-API idiom is to return `nullptr` (or another sentinel) on failure and let the caller check the return value.

Applied to files:

  • core/src/internal/bilateral_filter.cpp
  • bindings/c/include/cimg2num.h
  • bindings/py/src/img2num_pybind.cpp
📚 Learning: 2026-02-25T21:24:34.055Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 272
File: core/include/internal/bilateral_filter_gpu.h:108-109
Timestamp: 2026-02-25T21:24:34.055Z
Learning: In WGSL shaders, .rgb swizzle on a vec4 returns the first three components regardless of color space. Do not assume data is RGB color space when the texture contains non-RGB data (e.g., CIELAB L, A, B). If LAB data is stored, treat and compute distances in LAB space, not RGB. Clearly document shader code paths that rely on specific color spaces and prefer explicit conversions or comments when using swizzled components with non-RGB textures. Apply this guidance to WGSL shader files across the codebase (not just this header) when handling color data or color-like channels.

Applied to files:

  • core/include/internal/gpu.h
  • bindings/c/include/cimg2num.h
  • core/include/img2num.h
📚 Learning: 2026-04-09T19:05:40.514Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 302
File: core/include/internal/gpu.h:203-216
Timestamp: 2026-04-09T19:05:40.514Z
Learning: In Img2Num’s internal GPU initialization code, if querying adapter limits fails (e.g., `adapter.GetLimits(...)` returns `false`), treat this as a GPU initialization failure and route execution to the CPU fallback path. Do not continue with default limits in this case; the failure should cause the same fallback behavior as other GPU init errors.

Applied to files:

  • core/include/internal/gpu.h
📚 Learning: 2026-04-02T22:17:22.903Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 290
File: bindings/js/CMakeLists.txt:52-53
Timestamp: 2026-04-02T22:17:22.903Z
Learning: In the Img2Num project, `bindings/js/CMakeLists.txt` uses `ASYNCIFY=1` (required for WebGPU via emdawnwebgpu) which is incompatible with `-fwasm-exceptions`. To enable C++ exception handling alongside ASYNCIFY, use `-fexceptions` (Emscripten's JS-based EH) applied consistently to both compile and link options for all build types, and add `-sNO_DISABLE_EXCEPTION_CATCHING` to COMMON_FLAGS so that catch blocks in `clear_last_error_and_catch()` are not stripped by Emscripten's runtime.

Applied to files:

  • bindings/js/src/wasm_wrapper.cpp
📚 Learning: 2026-03-14T17:55:27.180Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 278
File: example-apps/react-js/src/components/GlassModal.module.css:35-43
Timestamp: 2026-03-14T17:55:27.180Z
Learning: In the Img2Num React example app (example-apps/react-js), the project convention is: CSS Module files (*.module.css) use camelCase for identifiers (class names, keyframe names, etc.), while vanilla CSS files use kebab-case. For example, keyframe names `fadeIn` and `scaleIn` are correct in a `.module.css` file.

Applied to files:

  • example-apps/react-js/src/components/WasmImageProcessor.jsx
📚 Learning: 2026-04-01T20:45:20.732Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-01T20:45:20.732Z
Learning: In the Ryan-Millard/Img2Num repository, PRs that introduce documentation or code examples referencing functions or symbols not present in the codebase (e.g., a `vectorize` function from `img2num`) should be flagged as potential AI-generated slop. Always cross-reference newly introduced API names/symbols against actual codebase exports.

Applied to files:

  • bindings/c/include/cimg2num.h
  • bindings/py/src/img2num_pybind.cpp
📚 Learning: 2026-04-27T15:40:33.329Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 333
File: bindings/c/src/cimg2num.cpp:0-0
Timestamp: 2026-04-27T15:40:33.329Z
Learning: In the Ryan-Millard/Img2Num C bindings (e.g., `bindings/c/src/cimg2num.cpp`) that expose `extern "C"` functions, do not allow C++ exceptions (including `std::bad_alloc` and exceptions thrown from lambdas used internally) to propagate out of these C binding functions. Use the C-API failure idiom instead: catch exceptions internally as needed, clear/record error state via the existing error mechanism, and return `nullptr` (or the documented sentinel return value) on failure so the C caller can detect errors by checking the return value.

Applied to files:

  • bindings/c/src/cimg2num.cpp
📚 Learning: 2026-04-29T20:06:23.790Z
Learnt from: CR
Repo: Ryan-Millard/Img2Num PR: 0
File: docs/docs/contributing/setup-and-dependencies/index.md:0-0
Timestamp: 2026-04-29T20:06:23.790Z
Learning: Compile C++ library to WebAssembly (WASM) using Emscripten before using JavaScript library or example apps

Applied to files:

  • core/include/img2num.h
📚 Learning: 2026-04-25T16:20:34.359Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-25T16:20:34.359Z
Learning: In the Ryan-Millard/Img2Num repository, the following follow-up issues were created after merging PR `#307` (Python bindings): `#328` (Python docstrings/docs), `#329` (numpy img.shape inference for width/height), `#330` (.coderabbit.yaml Python tooling config), `#331` (CI workflow for Python bindings build test).

Applied to files:

  • bindings/py/src/img2num_pybind.cpp
📚 Learning: 2026-04-25T16:43:10.485Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-25T16:43:10.485Z
Learning: In Ryan-Millard/Img2Num, the Python documentation tool chosen is pdoc, mirroring the Doxygen pattern for C/C++. The plan: install python3-pip and pdoc in a new Dockerfile.dev layer (between layer 4 Doxygen and layer 5 pnpm), create docs/scripts/pdoc.js (mirrors doxygen.js), output to docs/static/docs/python/api/, update prebuild in docs/package.json to "pnpm doxygen && pnpm pdoc", scaffold docs/docs/python/index.md and docs/docs/python/api-reference.md (uses FullscreenIframe component pointing to /Img2Num/info/docs/python/api/).

Applied to files:

  • bindings/py/src/img2num_pybind.cpp
📚 Learning: 2026-04-25T16:31:13.204Z
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-25T16:31:13.204Z
Learning: In the Ryan-Millard/Img2Num repository, issue `#331` (CI workflow for Python bindings build test) was addressed in PR `#307` via the `build-py` job in `.github/workflows/cmake-build.yml`. That job runs `uv venv`, `uv sync --no-build-isolation`, a smoke test (`uv run python3 -c "import img2num;"`), and uploads artifacts.

Applied to files:

  • bindings/py/src/img2num_pybind.cpp
🪛 Cppcheck (2.20.0)
core/src/internal/labels_to_svg.cpp

[style] 189-189: The function 'labels_to_svg' is never used.

(unusedFunction)

core/src/internal/image_to_svg.cpp

[style] 10-10: The function 'image_to_svg' is never used.

(unusedFunction)

🔇 Additional comments (6)
core/src/internal/bilateral_filter_gpu.cpp (1)

289-289: Reconsider the fixed 100ms Emscripten delay.

This introduces unconditional per-call latency in a hot path; the same concern was already raised earlier in review history.

core/src/internal/kmeans_gpu.cpp (1)

668-668: No additional comment; same concern already captured at the root pattern.

core/src/internal/bilateral_filter.cpp (1)

228-231: Please remove or gate these runtime stdout logs in the core API path.

This duplicates a previously raised cleanup request and should still be addressed before release.

core/include/internal/gpu.h (1)

260-260: Good lifecycle-state reset in destructor.

Resetting gpu_initialized avoids stale initialization state after teardown.

core/src/internal/labels_to_svg.cpp (1)

189-190: Formatting-only change looks good.

No functional impact from this signature line-wrap.

example-apps/react-js/src/components/WasmImageProcessor.jsx (1)

84-106: ⚡ Quick win

Remove the large commented legacy pipeline block.

Now that imgToSVG is the active path, this dead block adds noise and makes the example harder to follow.

[ suggest_recommended_refactor ]
As per coding guidelines, example-apps/** should remain minimal, clean, and a clear reflection of public API usage.

Comment thread bindings/c/include/cimg2num.h
Comment thread bindings/c/src/cimg2num.cpp Outdated
Comment thread bindings/js/src/wasm_wrapper.c
Comment thread bindings/py/src/img2num_pybind.cpp Outdated
Comment thread core/include/img2num.h
Comment thread core/src/internal/image_to_svg.cpp Outdated
Comment on lines +10 to +17
std::string image_to_svg(const uint8_t *data, const int width, const int height, const ImageToSvgConfig& config) {
// self deallocate
std::vector<uint8_t> img_data(width * height * 4);
std::vector<uint8_t> out_data(width * height * 4);
std::vector<int32_t> out_labels(width * height);

std::memcpy(img_data.data(), data,
static_cast<size_t>(width) * static_cast<size_t>(height) * 4);
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Apr 30, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Validate dimensions before sizing buffers and copying.

width * height * 4 is computed in signed int here and then used for both vector sizes and memcpy. Negative or overflowing dimensions can turn into huge allocations and bogus copy lengths before the function ever reaches a recoverable error path.

Suggested change
+#include <limits>
+
 std::string image_to_svg(const uint8_t *data, const int width, const int height, const ImageToSvgConfig& config) {
-    // self deallocate
-    std::vector<uint8_t> img_data(width * height * 4);
-    std::vector<uint8_t> out_data(width * height * 4);
-    std::vector<int32_t> out_labels(width * height);
+    if (data == nullptr || width <= 0 || height <= 0) {
+        throw std::invalid_argument("image_to_svg requires non-null data and positive dimensions");
+    }
+
+    const size_t pixel_count{static_cast<size_t>(width) * static_cast<size_t>(height)};
+    if (pixel_count > std::numeric_limits<size_t>::max() / 4) {
+        throw std::overflow_error("image_to_svg image dimensions overflow");
+    }
+
+    std::vector<uint8_t> img_data(pixel_count * 4);
+    std::vector<uint8_t> out_data(pixel_count * 4);
+    std::vector<int32_t> out_labels(pixel_count);
 
-    std::memcpy(img_data.data(), data,
-                static_cast<size_t>(width) * static_cast<size_t>(height) * 4);
+    std::memcpy(img_data.data(), data, pixel_count * 4);
As per coding guidelines, the core library review must cover memory safety, including null dereferences and buffer overflows.
🧰 Tools
🪛 Cppcheck (2.20.0)

[style] 10-10: The function 'image_to_svg' is never used.

(unusedFunction)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@core/src/internal/image_to_svg.cpp` around lines 10 - 17, The function
image_to_svg currently allocates img_data/out_data/out_labels and calls
std::memcpy using width * height * 4 computed as signed ints, which can overflow
or be negative; validate inputs at the top of image_to_svg by checking that
width and height are positive and that the product (cast to size_t) does not
overflow when multiplied by 4 (e.g., ensure (size_t)width * (size_t)height <=
SIZE_MAX / 4), return an error or empty string on invalid dimensions, then
compute sizes into size_t variables and use those for vector sizing and for the
memcpy length to avoid huge or negative allocations and unsafe copies (refer to
image_to_svg, img_data, out_data, out_labels, and the memcpy call).

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@coderabbitai

If this is outdated, please resolve it or give an updated comment about this.

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.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
cat core/src/internal/image_to_svg.cpp

Length of output: 1117


@Ryan-Millard, the comment is still valid — the PR has made a partial improvement but the two most important safety checks are still missing.

What was fixed: The static_cast<size_t> casts were added to the multiplications, which prevents signed-integer overflow at the arithmetic site.

What is still missing:

  1. data == nullptr checkstd::memcpy with a null source is undefined behaviour.
  2. Positive-dimension guard (width <= 0 || height <= 0) — this is the critical gap. A negative int silently wraps to near SIZE_MAX when cast to size_t, so static_cast<size_t>(-1) is 18446744073709551615. The vector constructors will almost certainly throw std::bad_alloc, but the behaviour is still undefined and the failure mode is unpredictable for callers.
  3. size_t overflow guard — multiplying two size_t values can itself overflow before the * 4 step, though this only matters for pathologically large dimensions.

The suggested fix from the original comment still applies:

`#include` <limits>
`#include` <stdexcept>

std::string image_to_svg(const uint8_t* data, const int width, const int height,
                         const ImageToSvgConfig& config) {
    if (data == nullptr || width <= 0 || height <= 0) {
        throw std::invalid_argument("image_to_svg requires non-null data and positive dimensions");
    }

    const size_t pixel_count{static_cast<size_t>(width) * static_cast<size_t>(height)};
    if (pixel_count > std::numeric_limits<size_t>::max() / 4) {
        throw std::overflow_error("image_to_svg: image dimensions overflow");
    }

    std::vector<uint8_t> img_data(pixel_count * 4);
    std::vector<uint8_t> out_data(pixel_count * 4);
    std::vector<int32_t> out_labels(pixel_count);

    std::memcpy(img_data.data(), data, pixel_count * 4);
    // ... rest unchanged

🧠 Learnings used
Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 333
File: bindings/c/src/cimg2num.cpp:0-0
Timestamp: 2026-04-27T15:40:33.329Z
Learning: In the Ryan-Millard/Img2Num project, `bindings/c/src/cimg2num.cpp` contains `extern "C"` C bindings. Do NOT suggest throwing C++ exceptions (e.g., `std::bad_alloc`) from within these C binding functions, even from lambdas passed internally to `clear_last_error_and_catch`. The correct C-API idiom is to return `nullptr` (or another sentinel) on failure and let the caller check the return value.

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-25T16:20:34.359Z
Learning: In the Ryan-Millard/Img2Num repository, the following follow-up issues were created after merging PR `#307` (Python bindings): `#328` (Python docstrings/docs), `#329` (numpy img.shape inference for width/height), `#330` (.coderabbit.yaml Python tooling config), `#331` (CI workflow for Python bindings build test).

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-25T16:31:13.204Z
Learning: In the Ryan-Millard/Img2Num repository, issue `#331` (CI workflow for Python bindings build test) was addressed in PR `#307` via the `build-py` job in `.github/workflows/cmake-build.yml`. That job runs `uv venv`, `uv sync --no-build-isolation`, a smoke test (`uv run python3 -c "import img2num;"`), and uploads artifacts.

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-23T18:48:02.692Z
Learning: In the Ryan-Millard/Img2Num repository, PR `#311` adds `.coderabbit.yaml` to main. PR `#307` (adding Python .py files) must rebase onto main after `#311` merges so that `.coderabbit.yaml` exists and can be updated with: `ruff: enabled: true`, `pylint: enabled: true`, `flake8: enabled: false` under `tools:`, and a `path_instructions` entry for `**/*.py` covering PEP 8, type annotations, exception handling, resource management, mutable default arguments, and Docker-first command suggestions via the `./img2num` wrapper. The "CodeRabbit Config Needs Update" custom check will automatically flag `#307` if the Python config is missing.

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-23T18:48:02.712Z
Learning: In the Ryan-Millard/Img2Num repository, PR `#311` adds `.coderabbit.yaml` to main. PR `#307` (adding Python .py files) must rebase onto main after `#311` merges so that `.coderabbit.yaml` exists and can be updated with: `ruff: enabled: true`, `pylint: enabled: true`, `flake8: enabled: false` under `tools:`, and a `path_instructions` entry for `**/*.py` covering PEP 8, type annotations, exception handling, resource management, mutable default arguments, and Docker-first command suggestions via the `./img2num` wrapper. The "CodeRabbit Config Needs Update" custom check will automatically flag `#307` if the Python config is missing.

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-01T20:45:20.732Z
Learning: In the Ryan-Millard/Img2Num repository, PRs that introduce documentation or code examples referencing functions or symbols not present in the codebase (e.g., a `vectorize` function from `img2num`) should be flagged as potential AI-generated slop. Always cross-reference newly introduced API names/symbols against actual codebase exports.

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-05-01T00:15:18.172Z
Learning: In Ryan-Millard/Img2Num, the correct release propagation strategy uses TWO GitHub Actions jobs added to release.yml:
1. `propagate-core`: fires when `core_created == 'true'` → creates separate PRs (not direct commits) for bindings/c, packages/js, and packages/py using `git commit --allow-empty` with conventional commit messages and `gh pr create`. Requires RELEASE_PROPAGATION_TOKEN.
2. `propagate-c`: fires when `c_created == 'true' AND core_created != 'true'` → creates a PR for packages/js only (the guard prevents double-bumping JS when core already propagated to it in the same run).
Dependency graph: core → {bindings/c, packages/js, packages/py}; bindings/c → {packages/js}. packages/py depends directly on core (not via bindings/c). PRs must be created (not direct commits) so Ryan-Millard can review each propagation before merging.

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 0
File: :0-0
Timestamp: 2026-04-23T18:35:01.229Z
Learning: In the Ryan-Millard/Img2Num repository, Python (.py files) is being added to the codebase via PR `#307`. The `.coderabbit.yaml` should include `ruff: enabled: true` and `pylint: enabled: true` under `tools:`, and a `path_instructions` entry for `**/*.py` covering PEP 8, type annotations, exception handling, resource management, mutable default arguments, and Docker-first command suggestions via the `./img2num` wrapper.

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.

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.

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.

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 234
File: src/wasm/modules/image/src/kmeans.cpp:79-81
Timestamp: 2026-01-19T00:02:34.957Z
Learning: In C++ multithreading contexts, prefer unsigned types for thread-count-like parameters (e.g., n_threads) to prevent negative values. Validate that the value is at least 1 before any division to avoid divide-by-zero at runtime. At the start of functions handling such values, create a safe count like: const unsigned int thread_count{std::max(1u, n_threads)}; This ensures non-negative, non-zero usage for divisions and related arithmetic. Apply this pattern to similar parameters across C++ modules, not just the specific file.

Learnt from: Ryan-Millard
Repo: Ryan-Millard/Img2Num PR: 272
File: core/src/internal/bilateral_filter_gpu.cpp:171-185
Timestamp: 2026-02-25T21:24:19.036Z
Learning: Adopt brace initialization (e.g., Type var{}) over copy initialization (e.g., Type var = {}) for zero-initialization in C++ across the repository. This should apply to variables, arrays, and structs in most C++ files (e.g., core/src/internal/bilateral_filter_gpu.cpp). It improves safety by avoiding narrowing conversions and makes initialization intent explicit.


img2num::ImageToSvgConfig config = img2num::IMAGE_TO_SVG_DEFAULT_CONFIG;
config.kmeans.k = 32;
std::string res_svg2{img2num::image_to_svg(img_data, width, height, config)};
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use the original image buffer for image_to_svg in this example.

img_data is already modified by the manual bilateral/kmeans path, so this no longer demonstrates the unified API on raw input as intended.

Proposed fix
-    std::string res_svg2{img2num::image_to_svg(img_data, width, height, config)};
+    std::string res_svg2{img2num::image_to_svg(image_data_original, width, height, config)};

As per coding guidelines, example-apps/** should correctly reflect public API usage with clean, minimal examples.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@example-apps/console-cpp/main.cpp` at line 69, The call to
img2num::image_to_svg is using img_data which has been modified by the manual
bilateral/kmeans path; change it to use the original unmodified image buffer
(e.g., save the raw input into a separate variable before applying
bilateral/kmeans and pass that raw buffer to img2num::image_to_svg) so the
example demonstrates the unified API on raw input; ensure references to
image_to_svg and the bilateral/kmeans modification code use the preserved
original buffer variable rather than the mutated img_data.

Comment thread packages/js/safeWasmWrappers.js Outdated
Comment thread packages/js/safeWasmWrappers.js Outdated
@Krasner Krasner requested a review from Ryan-Millard April 30, 2026 04:33
@Ryan-Millard
Copy link
Copy Markdown
Owner

Ryan-Millard commented Apr 30, 2026

@Krasner, please see the review I previously left - some of those things are still relevant. Additionally, please clean up the old code that was commented out and update the C example app and ensure that the C bindings work (they currently cause the error below).

root@8c5abede0223:/usr/src/app# cmake --build build
No changes in /usr/src/app/build/embedded_shaders.h, skipping write
[  0%] Built target Img2Num_shaders
[ 68%] Built target Img2Num
[ 81%] Built target CImg2Num
[ 90%] Built target console_cpp_app
[ 95%] Building C object example-apps/console-c/CMakeFiles/console_c_app.dir/main.c.o
/usr/src/app/example-apps/console-c/main.c: In function 'main':
/usr/src/app/example-apps/console-c/main.c:55:5: error: variable 'config' has initializer but incomplete type
   55 |     ImageToSvgConfig config = {0};
      |     ^~~~~~~~~~~~~~~~
/usr/src/app/example-apps/console-c/main.c:55:32: warning: excess elements in struct initializer
   55 |     ImageToSvgConfig config = {0};
      |                                ^
/usr/src/app/example-apps/console-c/main.c:55:32: note: (near initialization for 'config')
/usr/src/app/example-apps/console-c/main.c:55:22: error: storage size of 'config' isn't known
   55 |     ImageToSvgConfig config = {0};
      |                      ^~~~~~
gmake[2]: *** [example-apps/console-c/CMakeFiles/console_c_app.dir/build.make:79: example-apps/console-c/CMakeFiles/console_c_app.dir/main.c.o] Error 1
gmake[1]: *** [CMakeFiles/Makefile2:307: example-apps/console-c/CMakeFiles/console_c_app.dir/all] Error 2
gmake: *** [Makefile:136: all] Error 2

@Krasner
Copy link
Copy Markdown
Collaborator Author

Krasner commented Apr 30, 2026

update the C example app and ensure that the C bindings work

good catch. let me see what's going on there

@Ryan-Millard
Copy link
Copy Markdown
Owner

Ryan-Millard commented May 5, 2026

Hi @Krasner. Please rather prefer separate PRs for each task you do.

For example, the transition from C++ to C-only for bindings/js should not be part of unifying the library into a single function because they serve different purposes - one is a new feature and the other is an internal refactor.

This will become very important when we have our first release (which I plan to have once this PR is merged) because a commit like this will break the Conventional Commit and SemVer standards that the people who use our library will expect us to follow. Also, if you introduced a dangerous bug in the bindings/js refactor, we'd have to revert this entire PR because it'll be squashed into one commit - that'll remove the image_to_svg function as well.

Keeping it smaller makes it easier to review and spot bugs, too.

Moving foward, please try to keep your pull requests simple - especially when it comes to things that will affect users.

@Ryan-Millard
Copy link
Copy Markdown
Owner

@Ryan-Millard feel free to update the doxygen stuff too...

Alright. I'll work on it tomorrow. I didn't want to do it because I wasn't sure how you felt about me committing to your branches - it sometimes felt like I was taking over and I didn't want that.

I'm more than happy to let someone else do documentation 😅

Nobody likes documentation - that's why we basically have none. The stuff we have is pretty much all outdated.

@Krasner
Copy link
Copy Markdown
Collaborator Author

Krasner commented May 8, 2026

@Ryan-Millard what's the status here?

Ryan-Millard
Ryan-Millard previously approved these changes May 11, 2026
@Ryan-Millard
Copy link
Copy Markdown
Owner

@Ryan-Millard what's the status here?

Sorry for the late reply. I was away over the weekend.

Everything looks good. I'm just going to clean up a few things, then I'll merge it.

@Ryan-Millard
Copy link
Copy Markdown
Owner

@Krasner, my latest commit clearly separates our logic:

bindings/py: simple bindings to make the C++ compatible with Python
packages/py: more advanced code to make the API "Pythonic". This includes things like defaults and any special logic that doesn't need to be done at the Pybind stage.

This separation of concerns protects us from overly scary-looking bindings (C++ and Pybind11 are very verbose and unreadable for the untrained eye, whereas Python is borderline pseudocode).

@Krasner
Copy link
Copy Markdown
Collaborator Author

Krasner commented May 11, 2026

@Krasner, my latest commit clearly separates our logic:

bindings/py: simple bindings to make the C++ compatible with Python packages/py: more advanced code to make the API "Pythonic". This includes things like defaults and any special logic that doesn't need to be done at the Pybind stage.

This separation of concerns protects us from overly scary-looking bindings (C++ and Pybind11 are very verbose and unreadable for the untrained eye, whereas Python is borderline pseudocode).

Oh yeah that's a much better solution :)

@Ryan-Millard Ryan-Millard changed the title feat(core): unified image_to_svg function as complete pipeline feat: unified image_to_svg function as complete pipeline May 11, 2026
@Ryan-Millard
Copy link
Copy Markdown
Owner

@coderabbitai please make a few issues here:

  1. Documentation for the changes added in this pull request (be granular and make sub-issues)
  2. Cleanup for the Python API around the ImageToSvgConfig struct - it needs a more Pythonic API because it may appear a bit weird to normal Python users. It possibly needs a special Python class or a complete rework.

DO NOT BE LAZY @coderabbitai!!!!

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 11, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@Ryan-Millard Sure! I'll create all the issues now — a parent documentation tracking issue with 6 granular sub-issues, plus the Python API cleanup issue.


🏁 Script executed:

#!/bin/bash
set -e

REPO="Ryan-Millard/Img2Num"
PR_URL="https://github.com/Ryan-Millard/Img2Num/pull/335"
REQUESTER="Ryan-Millard"

# ── Sub-issue 1: Doxygen for C++ core API ──────────────────────────────────
SUB1=$(gh issue create --repo "$REPO" \
  --title "docs: add Doxygen comments to C++ core API introduced in feat/img_to_svg" \
  --body "## Summary
Add Doxygen documentation for all new public C++ symbols introduced in PR `#335` ($PR_URL).

## Scope
File: \`core/include/img2num.h\`

### Structs to document
- \`img2num::BilateralFilterConfig\` — fields: \`sigma_spatial\`, \`sigma_range\`
- \`img2num::KMeansConfig\` — fields: \`k\`, \`max_iter\`
- \`img2num::ImageToSvgConfig\` — fields: \`bilateral_filter\`, \`kmeans\`, \`min_cluster_area\`, \`color_space\`

### Functions to document
- \`img2num::image_to_svg(const uint8_t* data, int width, int height, const ImageToSvgConfig& config)\`
  - Describe each parameter and the returned \`std::string\` SVG payload.
  - Note the full pipeline it encapsulates (bilateral filter → k-means → label-to-SVG).
  - Document any preconditions (e.g. \`data\` must be RGBA, \`width\`/\`height\` > 0).

File: \`core/src/internal/image_to_svg.cpp\`
- Add inline comments explaining the buffer allocation strategy and pipeline steps.

## Context
Requested by @$REQUESTER as a follow-up to $PR_URL.
" \
  --assignee "$REQUESTER" 2>&1)
echo "Sub-issue 1: $SUB1"

# ── Sub-issue 2: Doxygen for C bindings ────────────────────────────────────
SUB2=$(gh issue create --repo "$REPO" \
  --title "docs: add Doxygen comments to C binding API introduced in feat/img_to_svg" \
  --body "## Summary
Add Doxygen documentation for all new public C symbols introduced in PR `#335` ($PR_URL).

## Scope
File: \`bindings/c/include/cimg2num.h\`

### Types to document
- \`img2num_BilateralFilterConfig\` — fields: \`sigma_spatial\`, \`sigma_range\` with units/defaults
- \`img2num_KMeansConfig\` — fields: \`k\`, \`max_iter\` with valid ranges
- \`img2num_ImageToSvgConfig\` — aggregate config; explain each nested field
- \`img2num_ImageToSvgConfig_default()\` — document that it mirrors the C++ defaults

### Functions to document
- \`img2num_image_to_svg(const uint8_t* data, int width, int height, const img2num_ImageToSvgConfig* config)\`
  - Parameters, return value (heap-allocated \`char*\`, caller must \`free()\`), and NULL error semantics.

## Note
The C API uses a value-type config (not heap-allocated via create/free), so it is important to clearly document the memory ownership of the returned \`char*\` SVG string.

## Context
Requested by @$REQUESTER as a follow-up to $PR_URL.
" \
  --assignee "$REQUESTER" 2>&1)
echo "Sub-issue 2: $SUB2"

# ── Sub-issue 3: JSDoc for JavaScript / WASM API ───────────────────────────
SUB3=$(gh issue create --repo "$REPO" \
  --title "docs: add JSDoc comments to JavaScript/WASM API introduced in feat/img_to_svg" \
  --body "## Summary
Add JSDoc documentation for the new JavaScript/WASM surface introduced in PR `#335` ($PR_URL).

## Scope

### File: \`packages/js/safeWasmWrappers.js\`
- \`imgToSVG({ pixels, width, height, sigma_spatial, sigma_range, num_colors, max_iter, min_area, color_space })\`
  - Document all parameters with types and default values.
  - Document the return type: \`Promise<{ svg: string, visualization: Uint8ClampedArray }>\`.
  - Clarify that \`pixels\` must be a flat RGBA \`Uint8ClampedArray\`.

### File: \`bindings/js/src/wasm_wrapper.cpp\`
- Add a short block comment to the \`image_to_svg\` Emscripten export explaining its role as the WASM entry point and its parameter list.

## Context
Requested by @$REQUESTER as a follow-up to $PR_URL.
" \
  --assignee "$REQUESTER" 2>&1)
echo "Sub-issue 3: $SUB3"

# ── Sub-issue 4: Docstrings for Python API ─────────────────────────────────
SUB4=$(gh issue create --repo "$REPO" \
  --title "docs: add Python docstrings for image_to_svg and ImageToSvgConfig introduced in feat/img_to_svg" \
  --body "## Summary
Add Python docstrings for all new public Python symbols introduced in PR `#335` ($PR_URL).

## Scope

### File: \`packages/py/img2num/api.py\`
- \`image_to_svg(image, config, *, width, height)\`
  - Numpy array shape/dtype requirements for \`image\`.
  - Expected type of \`config\` (\`ImageToSvgConfig\`).
  - Return type (\`str\` containing SVG XML).
  - Note on the \`@_inject_dims\` decorator and auto-derived \`width\`/\`height\`.
- \`ImageToSvgConfig\` (re-exported) — short summary docstring pointing to the class definition.

### File: \`bindings/py/src/img2num_pybind.cpp\`
- Add \`.doc()\` strings to the pybind11 class and field definitions for:
  - \`ImageToSvgConfig\`
  - \`BilateralFilterConfig\` (nested)
  - \`KMeansConfig\` (nested)
  - \`image_to_svg\` binding

These \`.doc()\` strings will surface in \`help(img2num.ImageToSvgConfig)\` and IDE tooltips.

## Context
Requested by @$REQUESTER as a follow-up to $PR_URL.
" \
  --assignee "$REQUESTER" 2>&1)
echo "Sub-issue 4: $SUB4"

# ── Sub-issue 5: README usage examples ─────────────────────────────────────
SUB5=$(gh issue create --repo "$REPO" \
  --title "docs: update README with unified image_to_svg usage examples for all language bindings" \
  --body "## Summary
Update \`README.md\` to document the new unified \`image_to_svg\` pipeline function introduced in PR `#335` ($PR_URL) across all supported languages.

## Scope
Add a **Usage** or **Quick Start** section (or update the existing one) with minimal, runnable code snippets for:

- **C++**
  \`\`\`cpp
  img2num::ImageToSvgConfig cfg;
  cfg.kmeans.k = 32;
  std::string svg = img2num::image_to_svg(data, width, height, cfg);
  \`\`\`

- **C**
  \`\`\`c
  img2num_ImageToSvgConfig cfg = img2num_ImageToSvgConfig_default();
  cfg.kmeans.k = 32;
  char *svg = img2num_image_to_svg(data, width, height, &cfg);
  free(svg);
  \`\`\`

- **Python**
  \`\`\`python
  import img2num
  cfg = img2num.ImageToSvgConfig()
  cfg.kmeans.k = 32
  svg = img2num.image_to_svg(image, cfg)
  \`\`\`

- **JavaScript**
  \`\`\`js
  import { imgToSVG } from 'img2num';
  const { svg } = await imgToSVG({ pixels, width, height, num_colors: 32 });
  \`\`\`

## Additional notes
- Cross-link to language-specific docs or bindings directories.
- Ensure the Python icon added to the README badge in this PR is reflected in surrounding prose (if applicable).

## Context
Requested by @$REQUESTER as a follow-up to $PR_URL.
" \
  --assignee "$REQUESTER" 2>&1)
echo "Sub-issue 5: $SUB5"

# ── Sub-issue 6: Example app inline comments ───────────────────────────────
SUB6=$(gh issue create --repo "$REPO" \
  --title "docs: add inline comments to example apps demonstrating the unified image_to_svg API" \
  --body "## Summary
Add clear inline comments to the example apps updated in PR `#335` ($PR_URL) so they serve as reference implementations.

## Scope

### \`example-apps/console-cpp/main.cpp\`
- Comment the \`ImageToSvgConfig\` setup block and the \`img2num::image_to_svg\` call.
- Explain why \`k=32\` is chosen and what \`res_svg2\` represents.

### \`example-apps/console-c/main.c\`
- Comment the \`img2num_ImageToSvgConfig_default()\` call and the memory ownership of the returned \`char*\`.
- Explain the difference between the step-by-step path (\`res_svg\`) and the unified path (\`res_svg2\`).

### \`example-apps/console-py/main.py\`
- Uncomment or remove stale commented-out config overrides, or add a comment explaining they are illustrative.
- Add a short docstring/comment block at the top explaining what the script demonstrates.

## Context
Requested by @$REQUESTER as a follow-up to $PR_URL.
" \
  --assignee "$REQUESTER" 2>&1)
echo "Sub-issue 6: $SUB6"

echo "---All sub-issues created---"

Length of output: 413


All 6 sub-issues created. Now creating the parent documentation tracking issue and the Python API cleanup issue.


🏁 Script executed:

#!/bin/bash
set -e

REPO="Ryan-Millard/Img2Num"
PR_URL="https://github.com/Ryan-Millard/Img2Num/pull/335"
REQUESTER="Ryan-Millard"

# ── Parent documentation tracking issue ────────────────────────────────────
PARENT=$(gh issue create --repo "$REPO" \
  --title "docs: document all changes introduced in feat: unified image_to_svg function (PR `#335`)" \
  --body "## Summary
Tracking issue for all documentation work needed as a follow-up to PR `#335` ($PR_URL), which introduced the unified \`image_to_svg\` pipeline function, \`ImageToSvgConfig\`, and related bindings across C++, C, JavaScript, and Python.

## Sub-issues

- [ ] `#366` — Doxygen comments for C++ core API (\`core/include/img2num.h\`, \`core/src/internal/image_to_svg.cpp\`)
- [ ] `#367` — Doxygen comments for C binding API (\`bindings/c/include/cimg2num.h\`)
- [ ] `#368` — JSDoc for JavaScript/WASM API (\`packages/js/safeWasmWrappers.js\`, \`bindings/js/src/wasm_wrapper.cpp\`)
- [ ] `#369` — Python docstrings for \`image_to_svg\` and \`ImageToSvgConfig\` (\`packages/py/img2num/api.py\`, \`bindings/py/src/img2num_pybind.cpp\`)
- [ ] `#370` — Update README with unified \`image_to_svg\` usage examples for all language bindings
- [ ] `#371` — Inline comments in example apps (\`console-cpp\`, \`console-c\`, \`console-py\`)

## Context
Requested by @$REQUESTER as a follow-up to $PR_URL.
" \
  --assignee "$REQUESTER" 2>&1)
echo "Parent issue: $PARENT"

# ── Python API cleanup issue ────────────────────────────────────────────────
PYISSUE=$(gh issue create --repo "$REPO" \
  --title "feat(py): make ImageToSvgConfig more Pythonic in the packages/py API" \
  --body "## Summary
The \`ImageToSvgConfig\` class currently exposed in the Python package (\`packages/py/img2num/api.py\`) is a thin re-export of the pybind11 C++ binding. While it is functional, it may feel un-idiomatic to Python users because it lacks constructor keyword arguments, type annotations, and standard Python patterns.

This issue tracks a cleanup/rework of the Python-level config API so that it feels native to Python.

## Problems to address

### 1. No parameterised constructor
Currently users must mutate fields after construction:
\`\`\`python
cfg = img2num.ImageToSvgConfig()
cfg.bilateral_filter.sigma_spatial = 2.0
cfg.kmeans.k = 32
\`\`\`
A Pythonic API would accept keyword arguments at construction time:
\`\`\`python
cfg = img2num.ImageToSvgConfig(sigma_spatial=2.0, k=32)
\`\`\`

### 2. Consider a \`dataclass\`-style wrapper in \`packages/py\`
Following the existing pattern in this repo (pybind11 handles the C++ bridge; \`packages/py\` provides the Pythonic façade), a pure-Python wrapper class could be introduced in \`packages/py/img2num/\` that:
- Uses \`@dataclasses.dataclass\` or a hand-written \`__init__\` with full keyword support.
- Provides type annotations (\`sigma_spatial: float = 3.0\`, etc.).
- Converts to the underlying pybind11 \`_img2num.ImageToSvgConfig\` before passing to the C++ layer.
- Has a useful \`__repr__\` and possibly \`__eq__\`.

### 3. Flat vs. nested config
Consider whether the Python API should flatten the nested struct:
\`\`\`python
# Flat (more Pythonic)
cfg = img2num.ImageToSvgConfig(sigma_spatial=2.0, sigma_range=50.0, k=32, max_iter=100, min_cluster_area=100, color_space=0)

# Nested (mirrors C++)
cfg = img2num.ImageToSvgConfig(bilateral_filter=BilateralFilterConfig(sigma_spatial=2.0), kmeans=KMeansConfig(k=32))
\`\`\`
Either approach (or both) is worth discussing before implementing.

### 4. Type annotations and IDE discoverability
Add a \`.pyi\` stub file or inline annotations so IDEs (VS Code, PyCharm) can autocomplete config fields.

## Acceptance criteria
- [ ] \`ImageToSvgConfig\` accepts keyword arguments at construction time.
- [ ] Fields have type annotations accessible via \`__annotations__\`.
- [ ] \`__repr__\` returns a string that can be copy-pasted to recreate the object.
- [ ] Existing functional behaviour (\`image_to_svg\` producing correct SVG output) is preserved.
- [ ] A simple test or usage example in \`example-apps/console-py/main.py\` is updated to demonstrate the new API.

## Related
- PR `#335`: $PR_URL (introduced the current implementation)
- Issue `#318` (original consolidation request)

## Context
Requested by @$REQUESTER as a follow-up to $PR_URL.
" \
  --assignee "$REQUESTER" 2>&1)
echo "Python cleanup issue: $PYISSUE"

Length of output: 138


@Ryan-Millard All done! Here's a summary of everything created:


📄 Documentation — Parent tracking issue

📄 Documentation — Sub-issues

# Scope
#366 Doxygen for C++ core APIcore/include/img2num.h, image_to_svg.cpp
#367 Doxygen for C binding APIbindings/c/include/cimg2num.h, memory ownership of returned char*
#368 JSDoc for JavaScript/WASM APIsafeWasmWrappers.js, WASM entry point comment
#369 Python docstringsapi.py function signatures + pybind11 .doc() strings for IDE tooltips
#370 README usage examples — C++, C, Python, and JavaScript snippets for the unified API
#371 Example app inline commentsconsole-cpp, console-c, console-py

🐍 Python API cleanup

All issues are assigned to you.

@Ryan-Millard Ryan-Millard merged commit bdba68c into Ryan-Millard:main May 11, 2026
33 checks passed
@Ryan-Millard
Copy link
Copy Markdown
Owner

@Krasner please also see #342. Also, please let me know if you are interested in that part of maintaining in this project?

@Ryan-Millard
Copy link
Copy Markdown
Owner

@Krasner please also see #342. Also, please let me know if you are interested in that part of maintaining in this project?

@Krasner, are you interested in such parts of the maintenance of Img2Num?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c/cpp Changes to C or C++ files core docs enhancement New feature or request example-app Updates to code related to demonstration (example) applications js/ts python changes to python bindings or python code

Projects

None yet

2 participants