Decompose: Add HTTP service layer (FastAPI) exposing the EmbroideryPipeline #31

Closed
opened 2026-06-22 16:23:18 +00:00 by Mark · 1 comment
Owner

Add HTTP service layer (FastAPI) exposing the EmbroideryPipeline

Context

EasyStitch's EmbroideryPipeline (src/main_pipeline.py) is a complete 5-stage CAM
engine. Today it's only invokable via the CLI (src/main.py) or by direct
import — there's no HTTP layer.

A consumer (ThreadCraft, Mark/Kimi_Agent_Brother_10_Digitizing_App) is about to
start using easyStitch as a service. The integration ticket is
Kimi_Agent_Brother_10_Digitizing_App#11.

Goal

Add a thin FastAPI service that wraps the pipeline and exposes a typed JSON API.

Pipeline stage reference

Stage What it does EasyStitch module
1 Raster → SVG (if input is raster) src/raster/
2 SVG semantic annotation src/svg_annotation/
3 DOM resolution / intent mapping src/intent_resolver/
4 Stitch generation (satin/fill/running/underlay) src/embroidery_converter/
5 Post-processing (pull compensation, path opt) src/post_processing/
6 Format emission (PES/DST/EXP/JEF) src/output_generators/

The HTTP service wraps stages 1–6 as endpoints, with /v1/digitize covering
1–5 and /v1/export covering 6 (taking pre-computed stitch objects from
the client).

Decomposition

Too large for a single fix; the 5 phases below are each shippable
independently. The first is the skeleton + health, the others layer
endpoints on top.

P1: Service skeleton + healthcheck

Set up the FastAPI app structure and a working /healthz endpoint. This
phase is fully isolated — no pipeline integration yet.

Files:

  • New: src/service/__init__.py
  • New: src/service/app.py (FastAPI app factory, CORS config, router
    registration stubs, /healthz handler)
  • New: src/service/config.py (pydantic settings: PORT, MAX_UPLOAD_MB,
    LOG_LEVEL, WORKER_POOL_SIZE, CORS_ALLOWED_ORIGINS)
  • New: src/service/models.py (pydantic request/response models, empty
    for now)

Acceptance:

  • uvicorn src.service.app:app --reload starts on port 8080.
  • GET /healthz returns {"status": "ok", "version": "<git-sha>"} with
    200.
  • CORS configured for ThreadCraft dev origin
    (http://localhost:3000) and a configurable prod origin.
  • A smoke test (tests/test_service_health.py) passes.
  • Dockerfile extends to CMD ["uvicorn", "src.service.app:app", ...].

Why first: every other phase adds a router under this skeleton, so
the skeleton must be in place before they can be tested.


P2: POST /v1/digitize

Wire the 5-stage pipeline to a single endpoint. This is the core of the
service.

Files:

  • Modify: src/service/app.py (add /v1/digitize route)
  • Modify: src/service/models.py (DigitizeRequest, DigitizeResponse
    pydantic models)
  • New: src/service/handlers/digitize.py (route handler, calls
    EmbroideryPipeline.process_bytes(), formats response)

Acceptance:

  • POST /v1/digitize with a 1024×1024 PNG and default settings returns
    a valid DigitizeResponse (regions, stitchObjects, stats) within 5
    seconds on a 4-core dev box.
  • The response includes a stats.stages breakdown (depends on issue
    #35 — the stages dict population fix — being merged first).
  • Errors at any stage (raster conversion fails, no regions detected,
    format emit fails) return a structured 503 with the failing stage
    named in the body. No 500s on expected failures.
  • 3 concurrent requests handled without deadlock.

Depends on: P1 (skeleton). Soft-depends on #32 (importable pipeline)
and #33 (process_bytes method) — if those aren't done first, this
phase has to call process(input_path) and write temp files, which
adds friction. Land them first for cleanliness.


P3: POST /v1/export

Take pre-computed stitch objects (already in the client or returned by
P2) and emit a binary embroidery file in the requested format.

Files:

  • Modify: src/service/app.py (add /v1/export route)
  • Modify: src/service/models.py (ExportRequest, ExportResponse or
    binary response)
  • New: src/service/handlers/export.py

Acceptance:

  • POST /v1/export with stitch objects and format=pes returns a
    valid PES file body. Header check: starts with #PES0001 or the
    correct v1 magic for the requested PES version.
  • format=dst, format=exp, format=jef each return a valid file
    with the correct magic bytes for that format.
  • Response includes X-Stitch-Count and X-Color-Count headers.
  • The output is the same bytes as calling the generator directly (no
    transformation in the service layer).

Depends on: P1 (skeleton), P2 (routing pattern). Independent of
P2's data path — accepts stitch objects from any source.


P4: POST /v1/threads/resolve + GET /v1/threads/catalog

ΔE-2000 thread palette lookup. Two endpoints:

  • POST /v1/threads/resolve — input: array of hex colors, output:
    nearest catalog entries.
  • GET /v1/threads/catalog — output: the embedded Brother PR-10
    thread catalog (one-shot fetch, client caches).

Files:

  • Modify: src/service/app.py (add the two routes)
  • Modify: src/service/models.py (ResolveRequest, ResolveResponse,
    CatalogResponse)
  • New: src/service/handlers/threads.py

Acceptance:

  • POST /v1/threads/resolve with ["#aabbcc", "#112233"] returns
    the nearest 2 catalog entries with deltaE values.
  • GET /v1/threads/catalog returns the full PR-10 catalog as JSON
    (~79 entries).
  • The catalog is loaded once at startup, not per-request (response
    time < 50ms for resolve).
  • The ΔE-2000 implementation is the same as
    src/embroidery_converter/thread_manager.py — no new algorithm.

Depends on: P1 (skeleton). Independent of P2/P3 (different code
path).

Note: requires the Brother PR-10 profile from #36 so the catalog
source is correct. The catalog can be sourced separately if #36 is
in flight.


P5: Dockerfile + deployment config

Production-ready container build and node_hub deployment manifest.

Files:

  • Modify: Dockerfile (add CMD for uvicorn, ensure deps are
    installed in runtime stage, pin base image tag)
  • New: node-hub.app.yml (or whatever format node_hub expects) for
    deploying easyStitch as a managed service
  • New: docker-compose.yml for local dev (optional, but useful for
    testing the full service)
  • New: docs/deployment.md covering Docker, node_hub, env vars

Acceptance:

  • docker build -t easystitch-service . && docker run -p 8080:8080 easystitch-service starts a working service.
  • node-hub.app.yml registers the service, sets CORS_ALLOWED_ORIGINS
    correctly, and node_hub health-checks it successfully.
  • docs/deployment.md covers: building, running, env vars, common
    errors.
  • Image size is reasonable (< 2 GB; the current python:3.11-slim
    base + scipy + scikit-image is ~1.5 GB; that's fine).

Depends on: P1 (skeleton) so the Dockerfile has something to run.
P2/P3/P4 not required — the image with only P1 is shippable, and the
endpoints come online as those phases land.

Cross-cutting concerns (handled across all phases, not in one phase)

  • CORS is configured in P1; later phases don't need to think about it.
  • Concurrency (queue depth, timeouts) is documented per #43; the
    service implementation honors those values across all endpoints.
  • Logging uses structured JSON in production (LOG_LEVEL=info by
    default; per-request UUID for correlation).
  • Error responses follow a consistent shape: {"error": "...", "stage": "...", "request_id": "..."} across all endpoints.
# Add HTTP service layer (FastAPI) exposing the EmbroideryPipeline ## Context EasyStitch's `EmbroideryPipeline` (`src/main_pipeline.py`) is a complete 5-stage CAM engine. Today it's only invokable via the CLI (`src/main.py`) or by direct import — there's no HTTP layer. A consumer (ThreadCraft, `Mark/Kimi_Agent_Brother_10_Digitizing_App`) is about to start using easyStitch as a service. The integration ticket is `Kimi_Agent_Brother_10_Digitizing_App#11`. ## Goal Add a thin FastAPI service that wraps the pipeline and exposes a typed JSON API. ## Pipeline stage reference | Stage | What it does | EasyStitch module | |---|---|---| | 1 | Raster → SVG (if input is raster) | `src/raster/` | | 2 | SVG semantic annotation | `src/svg_annotation/` | | 3 | DOM resolution / intent mapping | `src/intent_resolver/` | | 4 | Stitch generation (satin/fill/running/underlay) | `src/embroidery_converter/` | | 5 | Post-processing (pull compensation, path opt) | `src/post_processing/` | | 6 | Format emission (PES/DST/EXP/JEF) | `src/output_generators/` | The HTTP service wraps stages 1–6 as endpoints, with `/v1/digitize` covering 1–5 and `/v1/export` covering 6 (taking pre-computed stitch objects from the client). ## Decomposition Too large for a single fix; the 5 phases below are each shippable independently. The first is the skeleton + health, the others layer endpoints on top. ### P1: Service skeleton + healthcheck Set up the FastAPI app structure and a working `/healthz` endpoint. This phase is fully isolated — no pipeline integration yet. **Files:** - New: `src/service/__init__.py` - New: `src/service/app.py` (FastAPI app factory, CORS config, router registration stubs, `/healthz` handler) - New: `src/service/config.py` (pydantic settings: `PORT`, `MAX_UPLOAD_MB`, `LOG_LEVEL`, `WORKER_POOL_SIZE`, `CORS_ALLOWED_ORIGINS`) - New: `src/service/models.py` (pydantic request/response models, empty for now) **Acceptance:** - `uvicorn src.service.app:app --reload` starts on port 8080. - `GET /healthz` returns `{"status": "ok", "version": "<git-sha>"}` with 200. - CORS configured for ThreadCraft dev origin (`http://localhost:3000`) and a configurable prod origin. - A smoke test (`tests/test_service_health.py`) passes. - Dockerfile extends to `CMD ["uvicorn", "src.service.app:app", ...]`. **Why first:** every other phase adds a router under this skeleton, so the skeleton must be in place before they can be tested. --- ### P2: POST /v1/digitize Wire the 5-stage pipeline to a single endpoint. This is the core of the service. **Files:** - Modify: `src/service/app.py` (add `/v1/digitize` route) - Modify: `src/service/models.py` (DigitizeRequest, DigitizeResponse pydantic models) - New: `src/service/handlers/digitize.py` (route handler, calls `EmbroideryPipeline.process_bytes()`, formats response) **Acceptance:** - `POST /v1/digitize` with a 1024×1024 PNG and default settings returns a valid `DigitizeResponse` (regions, stitchObjects, stats) within 5 seconds on a 4-core dev box. - The response includes a `stats.stages` breakdown (depends on issue #35 — the `stages` dict population fix — being merged first). - Errors at any stage (raster conversion fails, no regions detected, format emit fails) return a structured `503` with the failing stage named in the body. No 500s on expected failures. - 3 concurrent requests handled without deadlock. **Depends on:** P1 (skeleton). Soft-depends on #32 (importable pipeline) and #33 (process_bytes method) — if those aren't done first, this phase has to call `process(input_path)` and write temp files, which adds friction. Land them first for cleanliness. --- ### P3: POST /v1/export Take pre-computed stitch objects (already in the client or returned by P2) and emit a binary embroidery file in the requested format. **Files:** - Modify: `src/service/app.py` (add `/v1/export` route) - Modify: `src/service/models.py` (ExportRequest, ExportResponse or binary response) - New: `src/service/handlers/export.py` **Acceptance:** - `POST /v1/export` with stitch objects and `format=pes` returns a valid PES file body. Header check: starts with `#PES0001` or the correct v1 magic for the requested PES version. - `format=dst`, `format=exp`, `format=jef` each return a valid file with the correct magic bytes for that format. - Response includes `X-Stitch-Count` and `X-Color-Count` headers. - The output is the same bytes as calling the generator directly (no transformation in the service layer). **Depends on:** P1 (skeleton), P2 (routing pattern). Independent of P2's data path — accepts stitch objects from any source. --- ### P4: POST /v1/threads/resolve + GET /v1/threads/catalog ΔE-2000 thread palette lookup. Two endpoints: - `POST /v1/threads/resolve` — input: array of hex colors, output: nearest catalog entries. - `GET /v1/threads/catalog` — output: the embedded Brother PR-10 thread catalog (one-shot fetch, client caches). **Files:** - Modify: `src/service/app.py` (add the two routes) - Modify: `src/service/models.py` (ResolveRequest, ResolveResponse, CatalogResponse) - New: `src/service/handlers/threads.py` **Acceptance:** - `POST /v1/threads/resolve` with `["#aabbcc", "#112233"]` returns the nearest 2 catalog entries with `deltaE` values. - `GET /v1/threads/catalog` returns the full PR-10 catalog as JSON (~79 entries). - The catalog is loaded once at startup, not per-request (response time < 50ms for resolve). - The ΔE-2000 implementation is the same as `src/embroidery_converter/thread_manager.py` — no new algorithm. **Depends on:** P1 (skeleton). Independent of P2/P3 (different code path). **Note:** requires the Brother PR-10 profile from #36 so the catalog source is correct. The catalog can be sourced separately if #36 is in flight. --- ### P5: Dockerfile + deployment config Production-ready container build and node_hub deployment manifest. **Files:** - Modify: `Dockerfile` (add `CMD` for uvicorn, ensure deps are installed in runtime stage, pin base image tag) - New: `node-hub.app.yml` (or whatever format node_hub expects) for deploying easyStitch as a managed service - New: `docker-compose.yml` for local dev (optional, but useful for testing the full service) - New: `docs/deployment.md` covering Docker, node_hub, env vars **Acceptance:** - `docker build -t easystitch-service . && docker run -p 8080:8080 easystitch-service` starts a working service. - `node-hub.app.yml` registers the service, sets `CORS_ALLOWED_ORIGINS` correctly, and `node_hub` health-checks it successfully. - `docs/deployment.md` covers: building, running, env vars, common errors. - Image size is reasonable (< 2 GB; the current `python:3.11-slim` base + scipy + scikit-image is ~1.5 GB; that's fine). **Depends on:** P1 (skeleton) so the Dockerfile has something to run. P2/P3/P4 not required — the image with only P1 is shippable, and the endpoints come online as those phases land. ## Cross-cutting concerns (handled across all phases, not in one phase) - **CORS** is configured in P1; later phases don't need to think about it. - **Concurrency** (queue depth, timeouts) is documented per #43; the service implementation honors those values across all endpoints. - **Logging** uses structured JSON in production (`LOG_LEVEL=info` by default; per-request UUID for correlation). - **Error responses** follow a consistent shape: `{"error": "...", "stage": "...", "request_id": "..."}` across all endpoints.
Mark changed title from Add HTTP service layer (FastAPI) exposing the EmbroideryPipeline to Decompose: Add HTTP service layer (FastAPI) exposing the EmbroideryPipeline 2026-06-22 16:30:52 +00:00
Author
Owner

This issue has been decomposed into the following sub-issues:

# Title Phase Scope
#46 Decompose: Add HTTP service layer (FastAPI) exposing the Emb Set up the FastAPI app structure and a working /healthz endpoint. This phase i
#47 Decompose: Add HTTP service layer (FastAPI) exposing the Emb Wire the 5-stage pipeline to a single endpoint. This is the core of the service.
#48 Decompose: Add HTTP service layer (FastAPI) exposing the Emb Take pre-computed stitch objects (already in the client or returned by P2) and e
#49 Decompose: Add HTTP service layer (FastAPI) exposing the Emb ΔE-2000 thread palette lookup. Two endpoints: - POST /v1/threads/resolve — inp
#50 Decompose: Add HTTP service layer (FastAPI) exposing the Emb Production-ready container build and node_hub deployment manifest. Files: -

Each sub-issue has been labeled with triage-approved and is ready for queue routing.

This issue has been decomposed into the following sub-issues: | # | Title | Phase Scope | |---|-------|-------------| | #46 | Decompose: Add HTTP service layer (FastAPI) exposing the Emb | Set up the FastAPI app structure and a working `/healthz` endpoint. This phase i | | #47 | Decompose: Add HTTP service layer (FastAPI) exposing the Emb | Wire the 5-stage pipeline to a single endpoint. This is the core of the service. | | #48 | Decompose: Add HTTP service layer (FastAPI) exposing the Emb | Take pre-computed stitch objects (already in the client or returned by P2) and e | | #49 | Decompose: Add HTTP service layer (FastAPI) exposing the Emb | ΔE-2000 thread palette lookup. Two endpoints: - `POST /v1/threads/resolve` — inp | | #50 | Decompose: Add HTTP service layer (FastAPI) exposing the Emb | Production-ready container build and node_hub deployment manifest. **Files:** - | Each sub-issue has been labeled with `triage-approved` and is ready for queue routing.
Mark closed this issue 2026-06-22 16:31:13 +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#31
No description provided.