Skip to content

Latest commit

History

986 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Thread: image flow, stitched with care

thread, London, 2026
an image pipeline, hand-typed and stitched with care
0.1.0 · BSD 3-Clause


This is not a dump.

It is a single sheet, laid on the table,typed slowly, with space to breathe.Every command here has been held and checked.If something breaks, it will tell you gently.

* * *

A note, before you run anything

Thread takes one image. It makes careful tiles. It can upscale those tiles, on the CPU by default, with Metal or CUDA if you ask for it, and it stitches them back into one image. A small, local API holds it all together.

It is quiet software. It does not shout. It prefers the default path.

Built for macOS 11+, Ubuntu 22.04+, Windows 10+ · Python 3.10+ · C++20 · CMake 3.10+


Contents

 i. The Work: what it does
ii. The Atelier: where things live
iii. What Works Now: an honest ledger
iv. To Run It, Gently: setup and build
v. Correspondence: the local API (v1)
vi. On Metal and CUDA: only if needed
vii. Care and Checks: tests, lint, CI
viii. Paper Stock: compatibility
ix. Colophon

* * *

i. The Work

Thread is a tiled image pipeline. Not a framework. Not a platform. Just a small, well-lit room where four things happen:

  1. Upload: one image comes in.
  2. Tile: the image is cut, cleanly, into squares (tile_size, default 512).
  3. Upscale: each tile is enlarged (scale 2×–8×, bicubic on CPU; Metal / CUDA when available).
  4. Stitch: tiles are joined again, row by row, with care for edges.

The API speaks HAL+JSON. Every response carries _links to where you might go next, and _embedded where there is more to see. Errors are small, legible objects: { code, title, details }.

No cloud. Runs on http://localhost:5001. Your images stay on your paper.

 [ image.jpg ] ──► [ tiles ] ──► [ upscaled tiles ] ──► [ stitched.jpg ]
│ │ │ │
POST POST POST POST
/v1/images /v1/images/:id/tiles /v1/tiles/:id/upscale /v1/stitch

ii. The Atelier

Every tool has its drawer. Nothing hidden.

PathUse: what lives there
api/server.pyFlask API: uploads, tiles, upscale, stitch, HAL responses
src/preprocess.cstb_image tiler: no OpenCV needed, always builds
src/preprocess.cppOpenCV path: when you want it
src/upscaler.cppUpscaler factory: chooses Metal to CUDA to error, with grace
src/metal/Metal on macOS: MetalShim.mm, MetalUpscaler.cpp, Upscale.metal
cloud_gpu/CUDA on Linux/Windows: upscale.cu, filters.cu, resize.cu + 9 more
include/Headers: upscaler.hpp, cuda_shim.h, stb_image.h
scripts/setup.sh, e2e.py, stitch.py, run.sh: the hands
tests/C and Python: test_preprocess.c, test_api_server.py, test_stitch.py
docs/Notes and site: Onboarding, Compatibility, CI, Troubleshooting
CMakeLists.txtBuild: Metal OFF by default, CUDA OFF by default, as it should be

The drawing on the wall: thread-flow.png: one image, four steps, stitched in the centre.


iii. What Works Now: an honest ledger

We list what is true today. No embellishment.

PartStateNote
Python APIActiveFlask, HAL, paginated
Upload imageActivepngjpgjpegbmptiff
Make tilesActivestb_image always; OpenCV if present
CPU upscale fallbackActivecv2.INTER_CUBIC, deterministic
Stitch outputActivehconcat + vconcat, normalises edges
C preprocessor (preprocess_c)Activebuild/bin/preprocess_c
CUDAOptionalOff by default. USE_CUDA=ON + Toolkit 12+
MetalOptionalOff by default. WITH_METAL=ON + Xcode 14+

The default path is CPU. It is enough to see the whole work. CUDA and Metal are not needed for the local flow, they are for when you know you need them.


iv. To Run It, Gently

We have kept this to four gestures. No rush.

1: Lay the table

bash scripts/setup.sh

What it does, quietly:

AreaDefault (no flags)
macOSHomebrew: CMake, Ninja, Xcode command line tools
Linuxapt: CMake, Ninja, Python, Git, build-essential
WindowsChocolatey: CMake, Ninja, Python, Git
Pythonvenv, requirements.txt, pytest, ruff, black, mypy, pre-commit

If you need more:

NeedCommand: typed exactly
Native OpenCV (libopencv-dev / brew install opencv)INSTALL_NATIVE_OPENCV=true bash scripts/setup.sh
Apple Metal toolchainWITH_METAL=ON bash scripts/setup.sh
CUDA toolkit on Linux (12.6)INSTALL_CUDA=true bash scripts/setup.sh

Then, to work inside:

source venv/bin/activate # Unix / macOS# or
.\venv\Scripts\activate # Windows

2: Build (CPU, the quiet way)

This is the recommended build. It keeps both shims off, so nothing foreign is asked for.

cmake -S . -B build -DUSE_CUDA=OFF -DWITH_OPENCV=OFF -DWITH_METAL=OFF -DENABLE_BENCHMARK=OFF
cmake --build build --parallel
ctest --test-dir build --output-on-failure

You should see test_preprocess pass. That is the C tiler, cut from stb_image, no dependencies.

3: Python checks

python -m pytest -v

Covers test_api_server.py, test_stitch.py, test_create_test_image.py and the rest under tests/.

4: See the whole flow, end to end

python scripts/e2e.py

It will: create a test image → run build/bin/preprocess_c (16 tiles) → upscale each tile to 128×128 (using build/bin/upscale if built, else cv2 fallback) → stitch to test_images/final_output.jpg (512×512) → verify and print “E2E test passed”.

5: Start the API

python api/server.py
# → http://localhost:5001# health: http://localhost:5001/v1/health (and /health)

Environment, if you like to set it:

API_VERSION=v1
UPLOAD_FOLDER=/tmp/thread/uploads
OUTPUT_FOLDER=/tmp/thread/output
PORT=5001
FLASK_DEBUG=0

v. Correspondence: the local API

Version: v1. Base: http://localhost:5001. All bodies are JSON unless you are sending a file.

Every reply is HAL, a small courtesy:

{
"_links": { "self": { "href": "/v1/images/..." } },
"_embedded": { "tiles": [ ... ] },
"id": "...",
"created_at": "2026-..."
}

Errors are always:

{ "errors": [{ "code": "not_found", "title": "Not Found", "details": "..." }] }

Upload an image

curl -X POST http://localhost:5001/v1/images \
-F "file=@image.jpg"# 201 → { id, filename, format, size, created_at,# _links: { self, tiles, upscale } }

Allowed: pngjpgjpegbmptiff. Stored as <uuid>_<filename> under UPLOAD_FOLDER.

List images: paginated, with care:

curl "http://localhost:5001/v1/images?offset=0&limit=25"# → { count, total, _embedded: { images: [...] },# _links: { self, next, prev } }

Get one image / download the file

curl http://localhost:5001/v1/images/<image_id>
curl http://localhost:5001/v1/images/<image_id>/file --output original.jpg

Create tiles

curl -X POST http://localhost:5001/v1/images/<image_id>/tiles \
-H "Content-Type: application/json" \
-d '{"tile_size": 512}'# 202 → { image_id, tile_count, tile_size,# _embedded: { tiles: [{ id, filename, href }] } }

Tiles live at OUTPUT_FOLDER/tiles_<image_id>/<image_id>_tile_<n>.jpg.

# list all tiles
curl "http://localhost:5001/v1/tiles?offset=0&limit=25"# get one tile
curl http://localhost:5001/v1/tiles/<tile_id>

Upscale: whole image

curl -X POST http://localhost:5001/v1/images/<image_id>/upscale \
-H "Content-Type: application/json" \
-d '{"scale": 2}'# scale: 1–8, default 2# 202 → { id, scale, output_file, _links: { download: "/v1/outputs/..." } }

Upscale: single tile

curl -X POST http://localhost:5001/v1/tiles/<tile_id>/upscale \
-H "Content-Type: application/json" \
-d '{"scale": 2}'# download the upscaled tile
curl http://localhost:5001/v1/tiles/<tile_id>/upscaled --output upscaled.png
# or
curl http://localhost:5001/v1/outputs/upscaled_<tile_id>.png --output upscaled.png

Underneath: cv2.resize(..., INTER_CUBIC) on CPU; Metal / CUDA if your build provides it.

Stitch tiles

curl -X POST http://localhost:5001/v1/stitch \
-H "Content-Type: application/json" \
-d '{ "tile_ids": ["tile_0", "tile_1", "tile_2", "tile_3"], "rows": 2, "cols": 2, "output": "stitched.png" }'# rows/cols optional, if omitted, tile_count must be a perfect square# prefers upscaled tiles if found, else originals# 202 → { id: <job_id>, status: "completed", result: "/v1/outputs/...",# tile_count, rows, cols,# _links: { self: "/v1/stitch/<job_id>", status: "/v1/jobs/<job_id>", download } }
# check the job
curl http://localhost:5001/v1/jobs/<job_id># download the stitched image
curl http://localhost:5001/v1/outputs/<filename> --output stitched.png

Health and root

curl http://localhost:5001/v1/health
# → { status: "healthy", version: "v1" }
curl http://localhost:5001/
# → { message: "Thread API v1", endpoints: { health, images, tiles, stitch } }

IDs are SQUUID-shaped (uuid4, lower-case, [A-Za-z0-9_-] only), safe for paths and URLs. tile_ids and output names are validated with secure_filename.


vi. On Metal and CUDA: only if needed

A note on gloss: we keep these off until you ask. The work is complete without them.

Metal (macOS 12+, Xcode 14+, macOS deployment 11.0)

WITH_METAL=ON bash scripts/setup.sh
cmake -S . -B build -DWITH_METAL=ON -DUSE_CUDA=OFF
cmake --build build --parallel
# shaders: build/shaders/default.metallib → share/thread/shaders/# library: build/lib/libcudart.* (Metal shim, cudart_shim)

The shim provides a cudart-shaped surface over Metal: Upscale.metal compiled via xcrun metal to metallib, wrapped by MetalUpscaler.cpp / MetalShim.mm, chosen at runtime by Upscaler::create() (isMetalAvailable() checks MTLCreateSystemDefaultDevice()).

CUDA (Ubuntu 22.04+, Toolkit 12.0+)

INSTALL_CUDA=true bash scripts/setup.sh
cmake -S . -B build -DUSE_CUDA=ON -DWITH_METAL=OFF -DCUDA_ARCH="70;75;80;86"
cmake --build build --parallel
# executables: upscale, filters, rotation, resize, colorspace,# histogram, morphology, median, sharpen, threshold, canny, blend

CUDA executables live in build/bin/. The factory prefers Metal when present; otherwise CUDA if isCudaAvailable() (cudaGetDeviceCount > 0); otherwise it raises gently, and the CPU fallback in api/server.py continues.

Do not set both to ON on the same machine unless you know why. The default keeps both OFF.


vii. Care and Checks

We treat checks as part of the work, not an afterthought.

CheckStateHow to run it
C tests (ctest)Activectest --test-dir build --output-on-failure
Python tests (pytest)Activepython -m pytest / python -m pytest --cov
E2E flowActivepython scripts/e2e.py
Pre-commit (black, isort, ruff, yamllint)Activepre-commit run --all-files
Build (macOS / Linux / Windows)ActiveGitHub Actions + CircleCI
Release checkActivescripts/validate_release_config.sh
Docs deployActivemkdocsdocs/
Extra workflowsManual / slash command.github/workflows/

Formatting is black (127), isort (black), ruff (E,F,W,C90,I,N,UP…), clang-format for C++. Commits are conventional: feat(api): … / fix(ci): … / docs(readme): ….

# before you push, the London way: slowly, then once
pre-commit run --all-files
python -m pytest
ctest --test-dir build --output-on-failure

CI lives in .github/workflows/ and .circleci/: CodeQL, Trivy, benchmark gates, and platform matrices.


viii. Paper Stock: compatibility

A small card, kept in the drawer:

StockDetail
Python3.10+ (tested 3.10–3.14)
C++20 · Objective-C++ 17 (Metal)
CMake3.10+ (3.21+ recommended for CUDA/Metal)
OpenCV4.0+ (4.5+ recommended), optional: stb_image is the fallback
CUDA12.0+ optional (tested 12.6.1, 13.0–13.1)
Metal3.0+ on macOS 12+
OpenMPoptional, for CUDA targets
OSmacOS 11+ (arm64/x86_64), Ubuntu 22.04+, Windows 10+

Full matrix: docs/COMPATIBILITY.md · Onboarding: docs/ONBOARDING.md · Testing: docs/TESTING.md


ix. Colophon

Thread
: a tiled image pipeline
Set in London. Typed by hand.
Printed with care on quiet paper.
No tracking. No cloud. Just tiles and stitch.
Version 0.1.0: see VERSION
License BSD 3-Clause: see LICENSE
© 2026 bniladridas
Contributing: see CONTRIBUTING.md
Development: see DEVELOPMENT.md
Security: see SECURITY.md
Changelog: see CHANGELOG.md
Docs site: mkdocs.yml (theme: readthedocs)

If you use this work, a citation or a note is kindly received, but never required. If something is unclear, open an issue and we will answer plainly.

* * *

“Make it simple, but not simpler. Make it sensitive, not loud.”
- atelier note, pinned above the press

hand-typed in London · 100% sensitive · not a dump

About

Thread: tiled image pipeline (tile, upscale, stitch) with local Flask API. CPU first, optional Metal/CUDA.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages