Laser Cut Vectorizer converts raster-like artwork into SVG contour paths that are easier to clean, organize, and prepare for laser cutting workflows. The tool focuses on predictable, scriptable preprocessing: thresholding, contour detection, geometric simplification, and physical-size-aware SVG output.
In practical terms, this repository exists to bridge the gap between "I have artwork" and "I have paths I can refine for fabrication." Instead of manually tracing every shape from scratch, you can generate a strong first-pass vector layer and spend your time on design intent, kerf compensation, and machine-specific setup.
- What This Project Is
- Why It Is Needed
- Feature Overview
- Tech Stack
- Architecture
- Install
- Quick Start
- CLI Reference
- Examples
- Visual Debug Mode
- Report Mode
- Output Model
- Quality and Validation
- Troubleshooting
- Limitations
- Project Layout
This is a Python command-line utility that accepts a source file, runs a vectorization pipeline, and writes an SVG document made of stroke paths. Inputs can be JPEG/PNG style artwork or PDF pages. The first PDF page is rasterized before vector extraction so the same contour pipeline can be applied consistently.
The output is intentionally simple and fabrication-friendly: no filled shapes, no style complexity, and explicit physical dimensions in millimeters. This keeps the generated file compatible with common design and cutter-prep tools, while leaving creative edits to downstream software where visual QA is easier.
Laser workflows often start with images that are not manufacturing-ready. A scan, silhouette photo, or design comp may look right but still lack clean vectors. Manual tracing can be slow and inconsistent, especially when iterating across many parts.
This project solves that bottleneck by giving you:
- A repeatable conversion baseline you can run from scripts and CI
- Parameterized contour cleanup (threshold, simplification, minimum area)
- Optional millimeter sizing so downstream layout starts closer to target scale
- A clear boundary between automated extraction and manual finishing
Note
The generated SVG is a strong starting point, not a guaranteed production artifact. Final inspection and cleanup remain part of the process for serious fabrication work.
| # | Capability | What It Does | Why It Matters |
|---|---|---|---|
| 1 | Image + PDF input | Accepts JPG, JPEG, PNG, BMP, TIFF, and PDF. | Supports common sources from scanning, export, and design workflows. |
| 2 | PDF first-page rasterization | Renders page 1 with configurable DPI before contouring. | Keeps the pipeline consistent across raster and document sources. |
| 3 | Thresholding and inversion | Binarizes grayscale artwork, with optional inversion. | Handles both dark-on-light and light-on-dark source styles. |
| 4 | Contour simplification | Uses geometric approximation to reduce point count. | Creates cleaner paths that are easier to edit and cut. |
| 5 | Minimum contour area filtering | Drops tiny regions likely to be noise. | Reduces cleanup time and accidental micro-cuts. |
| 6 | Millimeter sizing | Writes SVG width/height in mm from DPI or explicit overrides. | Improves scale continuity from conversion to cutter setup. |
| 7 | Presets | Applies common parameter bundles for known source conditions. | Speeds up early tuning and promotes reproducible conversions. |
| 8 | Contact-sheet debug visual | Composes grayscale, binary, contour, and SVG preview into one PNG. | Provides quick side-by-side diagnostics at a glance. |
| 9 | Self-contained HTML report | Embeds run images and parameter summary as inline data URLs. | Makes run review and sharing easier without extra files. |
GitHub markdown note: This table summarizes the conversion primitives and why each one exists in a fabrication pipeline.
The codebase intentionally uses a small stack focused on deterministic processing rather than heavy ML-based tracing. Each dependency has a specific role in the architecture.
| # | Layer | Package / Tool | Role in System |
|---|---|---|---|
| 1 | Language runtime | Python 3.11+ | Core runtime and packaging baseline. |
| 2 | Image math + arrays | NumPy | Represents grayscale image matrices for processing. |
| 3 | Computer vision | OpenCV (headless) | Thresholding, contour detection, area tests, and simplification. |
| 4 | Raster/image I/O | Pillow | Loads raster formats and converts to grayscale arrays. |
| 5 | PDF rendering | pypdfium2 | Renders PDF page 1 to a raster image at selected DPI. |
| 6 | SVG emission | xml.etree.ElementTree | Serializes traced contours to stroke-only SVG paths. |
| 7 | Testing | unittest | Verifies raster and PDF conversion paths. |
| 8 | Task runner | Makefile | Provides install, validate, compile, and test commands. |
GitHub markdown note: This table maps each dependency to one clearly scoped responsibility, which keeps the pipeline maintainable.
The project is organized as a linear conversion pipeline with explicit boundaries between loading, processing, and serialization. This keeps behavior easier to reason about when tuning contour parameters.
flowchart LR
A[CLI args] --> B[VectorizeOptions]
B --> C[load_grayscale_image]
C --> D[threshold + optional blur]
D --> E[findContours]
E --> F[area filter + simplify]
F --> G[resolve physical size mm]
G --> H[write_svg]
H --> I[SVG output file]
The module-level architecture separates concerns so each part can evolve independently.
flowchart TD
CLI[cli.py] --> MODELS[models.py]
CLI --> VEC[vectorizer.py]
VEC --> RASTER[raster.py]
VEC --> SVG[svg_writer.py]
TESTS[tests/test_vectorizer.py] --> CLI
TESTS --> VEC
Debug-visual flow for tuning threshold and contour parameters:
sequenceDiagram
participant User as CLI User
participant CLI as cli.py
participant V as vectorizer.py
participant FS as Filesystem
User->>CLI: run with --debug-dir
CLI->>V: build VectorizeOptions
V->>V: load grayscale and threshold to binary
V->>V: detect contours
V->>FS: write 01_grayscale.png
V->>FS: write 02_binary.png
V->>FS: write 03_contours.png
V->>FS: write 04_svg_preview.png
V->>FS: write 05_contact_sheet.png
V->>FS: write final SVG
Failure path handling and user-visible outcomes:
flowchart TD
A[Start conversion] --> B{Source format supported?}
B -- no --> E1[Raise ValueError: Unsupported source format]
B -- yes --> C{Threshold in 0..255?}
C -- no --> E2[Raise ValueError: Threshold must be between 0 and 255]
C -- yes --> D{Debug directory valid?}
D -- no --> E3[Raise ValueError: Debug directory path exists and is not a directory]
D -- yes --> F[Continue conversion and write output artifacts]
Create and activate a virtual environment, then install in editable mode:
python -m venv .venv
source .venv/bin/activate
pip install -e .Tip
Editable mode is useful during parameter tuning because code and documentation updates are immediately reflected without reinstalling the package each time.
Convert a JPEG to SVG:
python -m laser_cut_vectorizer input.jpg output.svgConvert a PDF page to SVG with explicit contour constraints:
python -m laser_cut_vectorizer drawing.pdf output.svg --dpi 300 --simplify 0.01 --min-area 50Invert thresholding for dark artwork on light background:
python -m laser_cut_vectorizer sketch.jpg output.svg --invertSet physical width and preserve aspect ratio automatically:
python -m laser_cut_vectorizer panel.png panel.svg --width-mm 180All options are available through the CLI parser in the source package.
| # | Argument | Type | Default | Purpose |
|---|---|---|---|---|
| 1 | source | Path | required | Input file (JPG/PNG/PDF and related raster formats). |
| 2 | output | Path | required | Destination SVG file path. |
| 3 | --preset | choice | None | Preset profile: clean-logo, noisy-scan, photo-edge. |
| 4 | --dpi | int | 300 | PDF render DPI and default physical size basis. |
| 5 | --threshold | int | preset/default | Binary threshold from 0 to 255. |
| 6 | --invert | flag | false | Inverts thresholding mode. |
| 7 | --blur-kernel | int | 0 | Optional odd Gaussian kernel size. |
| 8 | --min-area | float | preset/default | Minimum contour area to keep. |
| 9 | --simplify | float | preset/default | Simplification ratio for contour approximation. |
| 10 | --width-mm | float | None | Optional explicit output width. |
| 11 | --height-mm | float | None | Optional explicit output height. |
| 12 | --debug-dir | Path | None | Optional folder for intermediate PNG visuals. |
| 13 | --report | flag | false | Writes an HTML report with embedded debug images. |
GitHub markdown note: This table is a quick command-line map for scripting and repeatable conversion presets.
| # | Scenario | Suggested Changes | Why These Values Help |
|---|---|---|---|
| 1 | Noisy scan with speckles | Raise --min-area to 50-200 and try --blur-kernel 3 | Suppresses micro-contours caused by sensor/paper noise. |
| 2 | Overly jagged outlines | Increase --simplify from 0.01 to 0.02 or 0.03 | Reduces point density and smooths path complexity. |
| 3 | Missing thin strokes | Lower --threshold and reduce --min-area | Keeps lighter or thinner features from being dropped. |
| 4 | Foreground/background swapped | Use --invert | Switches contour polarity when source contrast direction is opposite. |
| 5 | Wrong physical size in downstream tool | Set --width-mm or --height-mm explicitly | Makes output dimensions deterministic regardless of import defaults. |
GitHub markdown note: This table provides practical tuning defaults for common first-pass failures.
| # | Preset | Threshold | Simplify | Min Area | Intended Source |
|---|---|---|---|---|---|
| 1 | clean-logo | 200 | 0.008 | 12 | Flat, high-contrast logos and line graphics. |
| 2 | noisy-scan | 165 | 0.020 | 90 | Scans with paper texture or sensor noise. |
| 3 | photo-edge | 140 | 0.030 | 140 | Photo-derived silhouettes or complex edges. |
GitHub markdown note: Presets are starting points. Explicit flags still override preset values when you need fine control.
Additional example commands are listed in examples/README.md. You can keep source assets in the examples folder and write outputs to the output folder for repeatable local experiments.
python -m laser_cut_vectorizer examples/source.jpg output/source.svg
python -m laser_cut_vectorizer examples/source.pdf output/source.svg --dpi 300Use this checklist before importing into cutter software:
- Confirm only intended contours were traced
- Remove small artifacts and duplicate loops
- Verify dimensions in millimeters
- Assign stroke/layer semantics required by your cutter profile
Visual tuning is usually the fastest way to dial in thresholding, inversion, and minimum-area values. The debug mode writes intermediary images so you can inspect exactly where contour quality is gained or lost.
Run conversion with debug outputs enabled:
python -m laser_cut_vectorizer input.jpg output.svg --invert --debug-dir output/debugThe debug directory contains deterministic filenames in pipeline order:
| # | File | Meaning | Why It Helps |
|---|---|---|---|
| 1 | 01_grayscale.png | Input converted to grayscale. | Confirms tonal contrast before thresholding. |
| 2 | 02_binary.png | Thresholded binary image. | Shows whether foreground/background separation is correct. |
| 3 | 03_contours.png | Detected contours overlaid on source. | Reveals noise, missed edges, and contour density. |
| 4 | 04_svg_preview.png | Rasterized preview of the simplified final vector paths. | Shows what the final SVG geometry will look like before export review. |
| 5 | 05_contact_sheet.png | 2x2 comparison grid of pipeline stages. | Enables quick side-by-side diagnostics in one image. |
GitHub markdown note: This table maps each generated debug image to the exact tuning decision it supports.
Important
Debug artifacts are diagnostic snapshots. They are not manufacturing output, but they can significantly reduce trial-and-error when preparing difficult artwork.
Report mode writes a lightweight HTML file beside the output SVG. The report embeds all debug visuals as data URLs and includes a parameter table with resolved values, which means you can archive or share one HTML file without external image dependencies.
python -m laser_cut_vectorizer input.jpg output.svg --preset noisy-scan --invert --reportWhen --report is used without an explicit --debug-dir, the tool automatically writes artifacts to <output_stem>_artifacts and writes the HTML report to <output_stem>_report.html in the same output folder.
Note
Report mode is intended for inspection and traceability. It does not replace CAD cleanup or machine-specific cut validation.
The generated SVG is a stroke-first geometry file intended for post-processing in vector tools. Width and height are written with millimeter units, while path coordinates are emitted from contour points in image space.
Important
The tool emits closed paths (Z command) with no fill and black stroke. This is deliberate because many cutter workflows begin by mapping strokes to operations.
Tip
If your machine software expects specific colors or layer names, apply those conventions after conversion in your vector editor template.
The repository includes scaffold checks, compilation checks, and unit tests.
make validate
make compile
make testValidation responsibilities at a glance:
| # | Command | What It Verifies | Why It Is Useful |
|---|---|---|---|
| 1 | make validate | Project scaffold and expected files. | Prevents missing-structure regressions. |
| 2 | make compile | Python bytecode compilation across src/tests/scripts. | Catches syntax issues quickly. |
| 3 | make test | Raster and PDF conversion behavior via unittest. | Protects core conversion path with executable checks. |
GitHub markdown note: This table explains what each quality gate catches so contributors can run the right command first.
PDF input fails or behaves unexpectedly
Ensure dependencies are installed in the active environment and that the PDF contains at least one page. The current implementation processes only the first page, so multi-page documents require separate runs.
SVG contains too many tiny paths
Raise --min-area and consider a small blur kernel (--blur-kernel 3) before thresholding. This combination usually removes speckle-driven contours.
Output size is different than expected in the cutter software
Some tools interpret unit metadata differently on import. Set --width-mm or --height-mm explicitly and verify document units in your editor before exporting machine-specific formats.
Warning
Extremely complex photographic images can generate many paths and require substantial manual cleanup. For best results, pre-process images for high contrast before vectorization.
- The converter traces visible contours, not artistic or semantic intent.
- Multi-page PDFs are processed one page at a time.
- Complex tonal photos can require heavy post-cleanup.
- Machine-specific toolpath rules (kerf, lead-ins, operation color mapping) are intentionally out of scope.
| # | Path | Responsibility |
|---|---|---|
| 1 | src/laser_cut_vectorizer/cli.py | Parses CLI args and runs conversion. |
| 2 | src/laser_cut_vectorizer/models.py | Defines options and result data models. |
| 3 | src/laser_cut_vectorizer/raster.py | Loads raster formats and renders PDF page 1. |
| 4 | src/laser_cut_vectorizer/vectorizer.py | Runs thresholding, contouring, filtering, and sizing. |
| 5 | src/laser_cut_vectorizer/svg_writer.py | Writes stroke-only SVG output. |
| 6 | tests/test_vectorizer.py | Covers JPG and PDF conversion flows. |
| 7 | docs/workflow.md | Practical workflow notes for laser prep. |
GitHub markdown note: This table is a contributor quick-map for where to implement changes in each layer.
When extending this README, prefer longer explanatory paragraphs over one-line bullets for process-heavy topics. For fabrication projects, readers benefit from understanding not only what a command does, but why a specific parameter matters in production contexts.
When adding new capabilities, document all three dimensions:
- What the feature is.
- What problem it solves in a real workflow.
- What failure modes or trade-offs users should expect.
That style keeps docs useful for both quick-start users and maintainers making architecture-level changes.