Bug: /v1/digitize ignores hoop_size_mm — design extends beyond requested hoop, no scaling or clipping #20

Closed
opened 2026-09-01 17:10:12 +00:00 by Mark · 0 comments
Owner

Summary

POST /v1/digitize accepts a hoop_size_mm parameter in DigitizeSettings but the pipeline does not scale or constrain the design to the hoop. The output stitch coordinates extend beyond the requested hoop dimensions, regardless of how small the hoop is set.

This silently produces embroidery files that exceed the physical hoop — a real production hazard.

Steps to Reproduce

# Set hoop to the minimum allowed (50x50mm) and upload an image larger than the hoop
curl -X POST http://127.0.0.1:8080/v1/digitize \
  -F 'image=@bb-hex.svg;type=image/svg+xml' \
  -F 'settings={"hoop_size_mm":{"w":50,"h":50}}'

The response stitchObjects[0].stitches[] shows x coordinates extending to ~200 mm even though the requested hoop is 50 × 50 mm.

Expected

The engine should either:

  • Scale the source image to fit within the requested hoop (preserving aspect ratio, padding if needed), or
  • Clip stitching that would fall outside the hoop, or
  • Reject the request with a clear 422 saying "design 200×100 mm exceeds requested hoop 50×50 mm."

Currently: silently produces an oversized file. Same response from 100×100, 130×180, 200×300 — all produce identical 6572-stitch output, identical PES bounding box (-1, -1, 2001, 997) in 0.1mm units (the BB SVG native size).

Actual

$ curl ... -F 'settings={"hoop_size_mm":{"w":50,"h":50}}'
stats: {totalStitches: 6572, colorCount: 2, processingMs: 64}
design bbox: x [-0.1, 200.1] y [-0.1, 99.7] mm  ← reaches 200mm wide
hoop setting: {w:50, h:50}                       ← only 50mm wide

PES bounds returned by pyembroidery are also identical across all three hoop sizes:

Hoop PES bounds (0.1mm)
100x100 (-1, -1, 2001, 997)
130x180 (-1, -1, 2001, 997)
200x300 (-1, -1, 2001, 997)

Root Cause

In easystitch-service/pipeline_runner.py, the DigitizeSettings is passed through to the engine via engine_settings:

result = await loop.run_in_executor(
    self._executor, self._digitize_sync, settings, image_bytes
)

The engine (src/main_pipeline.py / EmbroideryPipeline) is given hoop_size_mm but its SVG→stitch converter doesn't consume that parameter for scale/clip decisions. Likely the converter falls back to the SVG's native viewBox dimensions.

Suggested Fix

Pick one:

  1. Implement a fit-to-hoop step in _digitize_sync before the engine call: scale image_bytes by min(hoop.w / svg.width, hoop.h / svg.height) (or for raster, draw onto a canvas of hoop size with white margin), pass the scaled image to the engine.
  2. Validate at the boundary: in app.py line 193 (DigitizeSettings.model_validate_json), or in pipeline_runner, compute the expected design bbox and return 422 with a clear error if it exceeds hoop.
  3. Engine-side fix: thread hoop_size_mm into EmbroideryPipeline.run() and have the converter scale to fit. (Most thorough.)

Severity

High — silent failure mode that would ship unusable designs to a customer's machine.

Shakedown Context

2026-09-01 BB-monogram shakedown. Discovered when user asked to drive the hoop-size selector through the GUI. The native <select> element correctly updated React state to all four values (100x100, 130x180, 160x260, 200x300). The export PES files were byte-identical across sizes, which is what surfaced the issue.

Environment

  • easystitch-service on localhost:8080 (uvicorn pid 1457239)
  • HEAD of Mark/easystitch-service repo at test time
## Summary `POST /v1/digitize` accepts a `hoop_size_mm` parameter in `DigitizeSettings` but the pipeline **does not scale or constrain the design to the hoop**. The output stitch coordinates extend beyond the requested hoop dimensions, regardless of how small the hoop is set. This silently produces embroidery files that exceed the physical hoop — a real production hazard. ## Steps to Reproduce ```bash # Set hoop to the minimum allowed (50x50mm) and upload an image larger than the hoop curl -X POST http://127.0.0.1:8080/v1/digitize \ -F 'image=@bb-hex.svg;type=image/svg+xml' \ -F 'settings={"hoop_size_mm":{"w":50,"h":50}}' ``` The response `stitchObjects[0].stitches[]` shows x coordinates extending to `~200 mm` even though the requested hoop is **50 × 50 mm**. ## Expected The engine should either: - **Scale** the source image to fit within the requested hoop (preserving aspect ratio, padding if needed), or - **Clip** stitching that would fall outside the hoop, or - **Reject** the request with a clear 422 saying "design 200×100 mm exceeds requested hoop 50×50 mm." Currently: silently produces an oversized file. Same response from `100×100`, `130×180`, `200×300` — all produce identical 6572-stitch output, identical PES bounding box `(-1, -1, 2001, 997)` in 0.1mm units (the BB SVG native size). ## Actual ``` $ curl ... -F 'settings={"hoop_size_mm":{"w":50,"h":50}}' stats: {totalStitches: 6572, colorCount: 2, processingMs: 64} design bbox: x [-0.1, 200.1] y [-0.1, 99.7] mm ← reaches 200mm wide hoop setting: {w:50, h:50} ← only 50mm wide ``` PES bounds returned by pyembroidery are also identical across all three hoop sizes: | Hoop | PES bounds (0.1mm) | |--------|----------------------------| | 100x100 | (-1, -1, 2001, 997) | | 130x180 | (-1, -1, 2001, 997) | | 200x300 | (-1, -1, 2001, 997) | ## Root Cause In `easystitch-service/pipeline_runner.py`, the `DigitizeSettings` is passed through to the engine via `engine_settings`: ```python result = await loop.run_in_executor( self._executor, self._digitize_sync, settings, image_bytes ) ``` The engine (`src/main_pipeline.py` / `EmbroideryPipeline`) is given `hoop_size_mm` but its SVG→stitch converter doesn't consume that parameter for scale/clip decisions. Likely the converter falls back to the SVG's native viewBox dimensions. ## Suggested Fix Pick one: 1. **Implement a fit-to-hoop step** in `_digitize_sync` *before* the engine call: scale image_bytes by `min(hoop.w / svg.width, hoop.h / svg.height)` (or for raster, draw onto a canvas of hoop size with white margin), pass the scaled image to the engine. 2. **Validate at the boundary**: in app.py line 193 (`DigitizeSettings.model_validate_json`), or in pipeline_runner, compute the expected design bbox and return `422` with a clear error if it exceeds hoop. 3. **Engine-side fix**: thread `hoop_size_mm` into `EmbroideryPipeline.run()` and have the converter scale to fit. (Most thorough.) ## Severity **High** — silent failure mode that would ship unusable designs to a customer's machine. ## Shakedown Context 2026-09-01 BB-monogram shakedown. Discovered when user asked to drive the hoop-size selector through the GUI. The native `<select>` element correctly updated React state to all four values (100x100, 130x180, 160x260, 200x300). The export PES files were byte-identical across sizes, which is what surfaced the issue. ## Environment - easystitch-service on `localhost:8080` (uvicorn pid 1457239) - HEAD of `Mark/easystitch-service` repo at test time
Mark 2026-09-05 18:04:50 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Mark/easystitch-service#20
No description provided.