Image patch extraction, reconstruction, pairing and seam-aware stitching for super-resolution and dataset pipelines.
Project description
PatchCraft
A small library for encoding an image into patches and decoding it back. Built to slot into other people's torch pipelines as one transform among many — like a GaussianBlur step in a Compose([...]).
Status (2026-05-17): v0.1.0 released; v0.2.0-track is on
main(not yet tagged). Public API:extract,Patchify,reconstruct,stitch,pair,resize,Cache, plus geometry helpers (num_patches,tilings,TilingSpec,scale_factor,paired_tilings,PairedTilingSpec), pixel metrics (patch_metrics,per_patch_mse,per_patch_psnr), andPatchPair/PatchMeta.
The lib vs. this repo
Think of the lib as a car and this repo as the car plus its test track.
- The car —
src/patchcraft/— is what gets installed bypip install patchcraft. It is a single library with one job: take one image (Tensor[C, H, W]), encode it into patches, decode patches back into the image, optionally pair LR/HR, resize, cache. One image at a time, every time. No datasets, no training, no orchestration, no batching across images. Multi-image is the caller'sforloop, ortorch.vmap, or aDataLoader. - The track —
tests/,lab/,tests/_datasets.py, and the dev extras (torchvision, etc.) — is the pit crew, telemetry, driver and stopwatch that prove the car works on real images (MNIST today; more later). It downloads datasets, drives the lib through varied geometries, measures correctness. It never ships in the wheel.
The car is also acoplável — designed to drop into someone else's pipeline:
from patchcraft import Patchify
from torchvision import transforms
transform = transforms.Compose([
transforms.ToTensor(),
transforms.GaussianBlur(kernel_size=3),
Patchify(patch_size=4, stride=2), # ← PatchCraft as one step
])
Patchify is a callable; chain it inside a Compose, let DataLoader parallelize over workers. PatchCraft gives you the primitive; the surrounding pipeline stays your code.
Visual cheat sheet
The five core operations, one diagram each. Letters mark which patch each cell came from / goes to.
extract — image → patch stack
patch_size=4, stride=4 (no overlap) on an 8×8 image:
image (1, 8, 8) patches (4, 1, 4, 4)
+-----------------+ +-----+ +-----+
| . . . . | . . . . | | A | | B |
| . A . . | . B . . | extract +-----+ +-----+
| . . . . | . . . . | --------> patch0 patch1
| . . . . | . . . . |
|---------+---------| +-----+ +-----+
| . . . . | . . . . | | C | | D |
| . C . . | . D . . | +-----+ +-----+
| . . . . | . . . . | patch2 patch3
| . . . . | . . . . | (row-major order)
+-----------------+
reconstruct — patch stack → image (bit-exact when stride == patch_size)
Each output pixel = sum of patch contributions / count map (= how many patches covered it). When stride == patch_size, count is all-ones and the divide is a no-op.
stride == patch --> count map all 1 --> trivial copy
stride < patch --> count map > 1 --> weighted average
patch=4, stride=2, image cols 0..7:
col: 0 1 2 3 4 5 6 7
patch0: x x x x
patch1: x x x x
patch2: x x x x
count: 1 1 2 2 2 2 1 1 <- divide sum by this
pair — LR <-> HR, same image region, different resolution
scale_factor=2: every k-th LR patch corresponds to the k-th HR patch; HR coords are LR coords times the integer scale.
LR (1, 4, 4) HR (1, 8, 8)
+---------+ +-------------+
| . . . . | | . . . . . . . . |
| .[A]. . | k = 1 --> | . .[A A]. . . . |
| . . . . | | . .[A A]. . . . |
| . . . . | | . . . . . . . . |
+---------+ | . . . . . . . . |
| . . . . . . . . |
| . . . . . . . . |
| . . . . . . . . |
+-------------+
LR patch at (row=1, col=1) <--> HR patch at (row=2, col=2)
stitch — same fold geometry as reconstruct, but each patch weighted by a window kernel
Use when patches were modified by a model and uniform averaging shows boundary seams. Window kernels for patch_size=4:
weight="uniform" weight="hann" weight="gaussian"
(== reconstruct) centers > edges centers >> edges (never 0)
+ + + + . . . . . o o .
+ + + + . X X . o X X o
+ + + + . X X . o X X o
+ + + + . . . . . o o .
no seam attenuation strong attenuation, smooth attenuation,
image corners -> 0 corners preserved
Everything stays one-image-at-a-time
for image in images:
patches = extract(image, ...) # PatchCraft primitive
result = model(patches) # caller's work
out = stitch(result, ...) # PatchCraft primitive
Multi-image parallelism is the caller's pipeline (torch.vmap, DataLoader workers, etc.) — see SCOPE.md §2.
Scope (what the car does)
- Extract patches from a single image with configurable size, stride and dilation (
extract,Patchify). - Reconstruct an image from its patches — exact and weighted-overlap (
reconstruct). - Stitch modified patches (model output, denoised, super-resolved) back into one image with a window kernel that attenuates boundary seams (
stitch, withweight="uniform"|"hann"|"gaussian"). - Plan the geometry ahead of time:
num_patches((H, W), ...)for the count,tilings((H, W), allow_overlap=...)for every full-coverage(patch_size, stride)combo (no image, no allocation — just arithmetic). For LR↔HR setups:scale_factor(...)andpaired_tilings(...). - Pair LR and HR patches with metadata sufficient to reconstruct either (
pair,PatchPair,PatchMeta). - Measure pixel-level error between two patch stacks:
patch_metrics,per_patch_mse,per_patch_psnr. - Resize with pluggable backends — PIL or torch (
resize). - Cache results on disk with content-addressed keys, OneDrive-race retry, optional zstd (
Cache).
Scope (what the car does NOT do)
- Not a dataset manager. PatchCraft does not load, download, batch, shuffle, or stream datasets. That's the track's job —
tests/_datasets.pyhasmnist_subset(...)for dev fixtures, andtorchvisionis in the[dev]extra (never a runtime dep of the car). - Not a multi-image API. Every primitive takes one image. Use
vmapor a Python loop if you need to apply it to many. - No SVMs, no kernels, no quantum circuits — those belong to other projects.
- No neural network training — PatchCraft is infrastructure, not a model.
Install
From PyPI
pip install patchcraft # core only
pip install patchcraft[cache] # adds zstandard for compressed Cache entries
From source (development)
git clone https://github.com/LeoPR/PatchCraft.git
cd patchcraft
pip install -e ".[dev,cache]"
For GPU support, install a matching torch wheel before PatchCraft
(e.g. pip install torch --index-url https://download.pytorch.org/whl/cu124).
Run tests
pytest
pytest -m "not gpu" # skip GPU-requiring tests
Layout
PatchCraft/
├── pyproject.toml package metadata, build backend (hatchling)
├── README.md this file
├── LICENSE MIT
├── .python-version 3.13
├── .gitignore ignores archive/, venvs, caches, outputs
├── src/patchcraft/ library core — one-image-at-a-time primitives
│ ├── __init__.py re-exports the full public API
│ ├── extract.py patches via F.unfold; Patchify wrapper (ADR 0002)
│ ├── reconstruct.py inverse via F.fold + count map
│ ├── geometry.py pre-flight: num_patches, tilings, TilingSpec
│ ├── pair.py LR↔HR pairing; PatchPair, PatchMeta
│ ├── resize.py resize with PIL or torch backends
│ └── cache.py content-addressed disk cache
├── tests/ pytest suite (contract tests for src/)
│ ├── test_extract.py extract + Patchify
│ ├── test_reconstruct.py
│ ├── test_geometry.py num_patches + tilings
│ ├── test_pair.py
│ ├── test_resize.py
│ ├── test_cache.py
│ ├── test_datasets_helper.py label_subset
│ ├── test_import.py
│ └── _datasets.py dev-only fixtures (MNIST, etc) — NOT public API
├── lab/ ephemeral experiments; see lab/README.md
│ ├── README.md bench rules (tracked)
│ └── .gitignore ignores everything else (tracked)
├── docs/
│ ├── USAGE.md live REPL walkthrough of every public API
│ ├── SCOPE.md responsibilities matrix + parallelization analysis
│ ├── AUXILIARY.md tests/_datasets, lab/, Z:\ conventions (NOT part of the wheel)
│ ├── THEORY.md distilled design + §9 condition contract; §0 binding scope
│ ├── ROADMAP.md milestone plan
│ └── ADR/
│ ├── 0001-patch-extraction-api.md pure function `extract`
│ └── 0002-patchify-transform.md callable wrapper for Compose pipelines
└── archive/ reference-only; gitignored (pruned 2026-05-17 — only HISTORY.md kept)
Validation lab
The library is "one image in, one tensor out" by design — but you only know it works once you run it end-to-end on real images. That happens in two places, neither of which is part of the shipped package:
tests/— formal pytest suite that defines the contract fromdocs/THEORY.md§9.lab/— ephemeral scripts and notebooks for fast hypothesis-checking. Seelab/README.mdfor the bench rules; outputs go toZ:\outputs\patchcraft\(off-tree).
Datasets used by tests/lab are downloaded lazily into Z:\caches\datasets\<name>\ on first use; they do not ship with the package and are never bundled into the wheel.
Where to read next
| If you want… | Open |
|---|---|
| A hands-on tour with real REPL outputs for every public API | docs/USAGE.md |
| The line between "PatchCraft's job" and "your pipeline's job", plus the parallelization story | docs/SCOPE.md |
| The auxiliary test fixtures and lab conventions (not shipped) | docs/AUXILIARY.md |
| Design decisions, math, the per-API contract | docs/THEORY.md |
| Architecture Decision Records | docs/ADR/ |
| Milestone plan | docs/ROADMAP.md |
| Per-release changes | CHANGELOG.md |
Author
Leonardo Marques de Souza
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file patchcraft-0.2.0.tar.gz.
File metadata
- Download URL: patchcraft-0.2.0.tar.gz
- Upload date:
- Size: 144.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ab739ce0979a7abc0f8aaadb2f3918ef8c9fead250273399425f47491ed085ea
|
|
| MD5 |
b3e4373cf158b247442e5ff193771d79
|
|
| BLAKE2b-256 |
007aba306f3eb1bdc84a17e7d8fc83f731ef1db57aee0c457d75e53d5e71f453
|
File details
Details for the file patchcraft-0.2.0-py3-none-any.whl.
File metadata
- Download URL: patchcraft-0.2.0-py3-none-any.whl
- Upload date:
- Size: 27.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3f022372b4863e6004ab996e4ff62805f5d3f5e598b1c49b06d861a364325999
|
|
| MD5 |
1dd5908d81915623133a6b2bf471eb8e
|
|
| BLAKE2b-256 |
5c8f63c71f15f3b43e9211230f5266de789db316fe8b47d69793b0a6ae4e2267
|