Skip to main content

PatchCraft

Cut one image into patches, put it back together, and decide what happens at the seams.

Latest version on PyPI Supported Python versions License MIT Scope: one image at a time

PatchCraft takes a single (C, H, W) float tensor, cuts it into a stack of patches, and puts the image back together. It owns the unfold and fold arithmetic, the geometry validation and the seam blending, so that your pipeline can own everything else.

The scope is one image at a time, and that is worth knowing before you install anything, because it is the constraint that decides whether PatchCraft fits your problem at all. There is no batching across images, no Dataset, no DataLoader and no training loop, so multi-image work stays in your own for loop, in your torch.vmap, or in your DataLoader calling this once per item.

   one image                     the patch stack                one image again
   (1, 4, 4)                     (4, 1, 2, 2), row-major        (1, 4, 4)

   +-----+-----+                 +-----+   +-----+              +-----+-----+
   | A A | B B |                 | A A |   | B B |              | A A | B B |
   | A A | B B |    extract      | A A |   | B B |  reconstruct | A A | B B |
   +-----+-----+   ---------->   +--p0-+   +--p1-+  ----------> +-----+-----+
   | C C | D D |   patch_size=2  +-----+   +-----+   stride=2   | C C | D D |
   | C C | D D |   stride=2      | C C |   | D D |              | C C | D D |
   +-----+-----+                 | C C |   | D D |              +-----+-----+
                                 +--p2-+   +--p3-+

The image goes out as a stack of patches, you do your work on the stack, and it comes back as one image. That last arrow has two doors: reconstruct when the patches are untouched, and stitch when a model rewrote them and the seams need to fade.

This page is the short one. The manual is docs/GUIDE.md, which carries the measurements, the tables and the long examples.

Install

pip install patchcraft
pip install "patchcraft[cache]"     # adds zstandard, which compresses Cache payloads

There is nothing else to install for speed. On Windows x64, Linux x86_64 and aarch64, and both macOS architectures, the wheel carries a Rust accelerator for the overlapping fold, which is where reconstruct and stitch spend their time. Every other platform gets the universal wheel and runs the pure-torch paths, which return the same values.

patchcraft.accel_available() reports at runtime which one you got, and PATCHCRAFT_ACCEL=0 in the environment forces the pure path. On the overlapping fold the accelerator is worth between 5.8x and 16.5x on the machine it was measured on, which the performance page reports in full.

The distribution name and the import name are both patchcraft. The cache extra is optional, because Cache works without zstandard as well and simply stores its payload uncompressed.

Sixty seconds

import torch
from patchcraft import extract, reconstruct, stitch

image = torch.rand(3, 256, 256)                      # one float (C, H, W) tensor
patches = extract(image, patch_size=32, stride=16)   # (L, C, ph, pw) == (225, 3, 32, 32)

back = reconstruct(patches, image.shape, stride=16)  # the patches came back untouched
assert torch.equal(back, image)                      # the same tensor, bit for bit

edited = patches * 1.01                              # stands in for a per-patch model
blended = stitch(edited, image.shape, stride=16, weight="hann")
assert blended.shape == image.shape                  # seams smoothed, geometry preserved

PatchCraft accepts float tensors only. An 8-bit image has to become image.float() / 255 before it reaches extract, because extract passes the tensor straight to F.unfold, and torch has no integer kernel there: it raises NotImplementedError: "im2col_out_cpu" not implemented for 'Byte'.

reconstruct or stitch

reconstruct is the inverse of extract. It assumes the patches still hold the pixels extract gave you, it divides each pixel by the number of patches that covered it, and on the geometries described further down it hands the image back bit for bit.

stitch is for patches a model rewrote, because neighbours now disagree about the pixels they share, and that disagreement lands on the grid lines unless something spreads it.

Call Use it when What it does at the overlaps
reconstruct the patches are the ones extract produced, or you only read them divides each pixel by how many patches covered it, which inverts extract
stitch a model rewrote the patches, so neighbours now disagree weights each patch through "uniform", "hann" or "gaussian" before averaging

Uniform averaging is the default, and it is the option that reports what the model actually produced, since it changes no value beyond dividing by the count. The price is a straight line of disagreement along every patch boundary, and that is what the eye reads as tiling. A Hann window spreads the same disagreement across the whole overlap instead, so the seam stops being visible, and what it costs is a little fidelity to the values the model returned.

Why not unfold and fold directly

Nothing stops you, and PatchCraft is a thin contract over exactly those two calls. What the contract buys is the pixel order and the boundary checks, because the intuitive reshape after F.unfold returns a tensor of the right shape whose pixels are scrambled.

import torch
import torch.nn.functional as F
from patchcraft import extract

image = torch.arange(64, dtype=torch.float32).reshape(1, 8, 8)
patches = extract(image, patch_size=4, stride=4)             # (4, 1, 4, 4)
cols = F.unfold(image.unsqueeze(0), kernel_size=4, stride=4) # (1, C*ph*pw, L)

scrambled = cols[0].view(-1, 1, 4, 4)                        # the intuitive reshape
assert scrambled.shape == patches.shape                      # the right shape
assert not torch.equal(scrambled, patches)                   # and the wrong pixels

The saving is real on the other side too. Tiling an image, running a per-patch model and blending the result back with a Hann window took 17 non-blank lines by hand against 3 with extract and stitch, and the two outputs agreed to about 1.2e-05 on a value in [0, 1], summation-order rounding more than 300 times smaller than one 8-bit step.

The geometry has to cover the image

extract follows whatever grid you hand it, but reconstruct and stitch refuse a grid that does not cover the image exactly, rather than returning a plausible tensor built on missing pixels. On a 128x128 image with patch_size=32 and stride=20 the grid reaches only 112x112, which leaves 3840 of the 16384 pixels at zero, and the error message names that covered extent instead of hiding it.

The answer is to pick a legal geometry rather than to pad the image into one, because padding synthesizes pixels you never had. tilings(image_shape) enumerates the legal geometries from the shape alone and allocates nothing while it does so, so you can call it before you have committed to anything: a 28x28 image has 5 exact tilings, and 73 of them once allow_overlap=True lets the patches overlap.

Two narrower questions have their own entry points. num_patches takes a geometry you already have in mind and returns the grid it implies, and paired_tilings is the one to reach for when a low-resolution image and a high-resolution image have to stay aligned patch for patch.

What you are getting into

The surface is one tensor in and one tensor out, with no batch axis anywhere in the signature, so extract accepts (C, H, W) and rejects (N, C, H, W) by decision rather than by omission. It is a geometry library and nothing else, which means it ships no models, no losses and no Dataset, and the one confusion worth heading off is compression: the round trip keeps every pixel it started with, and Cache only writes bytes you already hold.

It helps when you tile one image for an inference pass too large to run in a single forward call, when you build aligned low-resolution and high-resolution patch pairs, and when you run a sliding window analysis and need the pieces to go back together exactly.

When the round trip is bit for bit

The round trip is exact when every value in the count map is a power of two. The reason is that reconstruction divides each pixel by the number of patches that covered it, and dividing a float by a power of two is the one division that never rounds.

That makes the geometry the deciding axis rather than the dtype, so float64 is not a safe harbour: outside the rule the per-pixel error is bounded by (k+1)·eps·|v|, with k the pixel's coverage count. A wider float buys a smaller miss and never exactness.

The everyday shorthand is that stride == patch_size and stride == patch_size / 2 always satisfy the rule. Both are sufficient conditions rather than necessary ones, so a geometry outside them can still be exact, and the guide carries the sweep that measures it.

Status

This is pre-1.0, so both the output values and the API shape can still move. While the leading digit is zero the middle one is the compatibility boundary, which makes a new 0.y.z safe to take and a new 0.y the place where a change is allowed to land, and the changelog records each one with the measurement behind it. The suite collects 1657 tests and passes on Python 3.12, 3.13 and 3.14, on Ubuntu and on Windows alike.

Where it applies today: on GPU the functions accept CUDA tensors and keep the device, and the Rust kernel is CPU-only, so it does not accelerate there; every figure on this page is a CPU figure, so check exactness on your device before relying on it. No external project consumes the published API yet, which is the gate this project set for calling the shape settled.

Documentation

  • Guide, the manual, with every figure on this page shown as runnable code
  • Performance, what the native accelerator is worth and how to re-measure it
  • Usage, a walkthrough of each of the 20 public symbols, executed as a doctest
  • Theory, the math and the per-function contract
  • Scope, the line between this library and your pipeline
  • Repository, issues and contributing

License and citation

MIT, in LICENSE. To cite this work, the authoritative metadata is in CITATION.cff, which is what GitHub's "Cite this repository" button reads; the same reference as BibTeX is in the guide. There is no DOI yet.

Release files for patchcraft 0.5.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for patchcraft 0.5.5
File Size Uploaded
patchcraft-0.5.5.tar.gz 328.2 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for patchcraft 0.5.5
File
patchcraft-0.5.5-py3-none-any.whl Python 3 none any Details
patchcraft-0.5.5-cp312-abi3-win_amd64.whl CPython 3.12 abi3 Windows x86-64 Details
patchcraft-0.5.5-cp312-abi3-manylinux_2_28_x86_64.whl CPython 3.12 abi3 Linux glibc 2.28+ x86-64 Details
patchcraft-0.5.5-cp312-abi3-manylinux_2_28_aarch64.whl CPython 3.12 abi3 Linux glibc 2.28+ ARM64 Details
patchcraft-0.5.5-cp312-abi3-macosx_11_0_arm64.whl CPython 3.12 abi3 macOS 11.0+ ARM64 Details
patchcraft-0.5.5-cp312-abi3-macosx_10_13_x86_64.whl CPython 3.12 abi3 macOS 10.13+ x86-64 Details

Total release size: 1.7 MB

Release files / patchcraft-0.5.5.tar.gz

Download URL patchcraft-0.5.5.tar.gz
Size 328.2 kB
Tags Source
SHA-256 checksum
How to use checksums
ac8ba4b5628686c35eea4e105d0399e76a39a691601055b88013804b0942d8cc
BLAKE2b-256 checksum
How to use checksums
5c141bcb707bf35c6908b3397b4db795866d9c6a676390df5f7be003c346f82f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.

Transparency log

Release files / patchcraft-0.5.5-py3-none-any.whl

Download URL patchcraft-0.5.5-py3-none-any.whl
Size 37.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4b66c0eaca519fa442e0ef3297ea92f3199ed1f119f63523f6ce3970d43758cc
BLAKE2b-256 checksum
How to use checksums
6233321e5337d2a2dff380dd1906a5ab155ec268dcf1422e39d3c7b1d7810e0f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.

Transparency log

Release files / patchcraft-0.5.5-cp312-abi3-win_amd64.whl

Download URL patchcraft-0.5.5-cp312-abi3-win_amd64.whl
Size 162.8 kB
Tags CPython 3.12 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
bfdb4094a0b08b79e39813cae3eec8f2103820660b96911c1584cd06e0cf539e
BLAKE2b-256 checksum
How to use checksums
2338c732e082b960fb31e6ae7a31c3c1c60ddc9304ea5456e2b6cf88c19d0496
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.

Transparency log

Release files / patchcraft-0.5.5-cp312-abi3-manylinux_2_28_x86_64.whl

Download URL patchcraft-0.5.5-cp312-abi3-manylinux_2_28_x86_64.whl
Size 319.1 kB
Tags CPython 3.12 Linux glibc 2.28+ x86-64 abi3
SHA-256 checksum
How to use checksums
dbaf446bfe4eebe519de4860ca283f0cde81b3448d4dab02227d5ba63630bdd4
BLAKE2b-256 checksum
How to use checksums
a6364fc2dda33a0cb0dad8be0098fcad068a1258d8f03532c986c3dc120c9bff
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.

Transparency log

Release files / patchcraft-0.5.5-cp312-abi3-manylinux_2_28_aarch64.whl

Download URL patchcraft-0.5.5-cp312-abi3-manylinux_2_28_aarch64.whl
Size 310.5 kB
Tags CPython 3.12 Linux glibc 2.28+ ARM64 abi3
SHA-256 checksum
How to use checksums
7690786543467850f1738d7f051ed0624c18dd267c71d5103452031ee002a838
BLAKE2b-256 checksum
How to use checksums
c125f6c6383c130743161f906783a7c1b02b4810e908bfe822f7cc8635560b98
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.

Transparency log

Release files / patchcraft-0.5.5-cp312-abi3-macosx_11_0_arm64.whl

Download URL patchcraft-0.5.5-cp312-abi3-macosx_11_0_arm64.whl
Size 269.4 kB
Tags CPython 3.12 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
9a538cbb08f29e752154a3d9c854947158a426c5f5ac7f5b520723e79be1742f
BLAKE2b-256 checksum
How to use checksums
be6385c3d3ceb0b36c5586b03b822d36f8475283a2a6e8d035d2568c9bef72e2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.

Transparency log

Release files / patchcraft-0.5.5-cp312-abi3-macosx_10_13_x86_64.whl

Download URL patchcraft-0.5.5-cp312-abi3-macosx_10_13_x86_64.whl
Size 274.8 kB
Tags CPython 3.12 abi3 macOS 10.13+ x86-64
SHA-256 checksum
How to use checksums
e6fef1ef43c32e03499c2ea9b0e06c4422a828e1cc249b0f3049c9bab8a3e5fe
BLAKE2b-256 checksum
How to use checksums
37128495460d77858aa85cf5d5569977fbaac94eb1930c38569f753665a5a482
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.5.5 This release

7 release files

0.5.4

7 release files

0.5.3

7 release files

0.5.2

7 release files

0.5.1

7 release files

0.5.0

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page