Skip to main content

PatchCraft

Encode one image into patches, decode it back, 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 it is worth between 2.6x and 14x 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].

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 1571 tests and passes on Python 3.12, 3.13 and 3.14, on Ubuntu and on Windows alike.

Two limits are worth knowing before you depend on it. Every figure on this page was measured on CPU, and no CUDA path has ever executed in the test matrix, so the pipeline does preserve the device you hand it while the exactness numbers stay unverified on GPU. The other limit is that no external project has consumed the published API in real use yet, and that consumption is this project's own stated gate 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 the public surface, captured against an older release
  • 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. There is no DOI yet, so if you need to cite this work the BibTeX entry is in the guide.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

patchcraft-0.5.1.tar.gz (320.5 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

patchcraft-0.5.1-py3-none-any.whl (34.6 kB view details)

Uploaded Python 3

patchcraft-0.5.1-cp312-abi3-win_amd64.whl (159.9 kB view details)

Uploaded CPython 3.12+Windows x86-64

patchcraft-0.5.1-cp312-abi3-manylinux_2_28_x86_64.whl (316.2 kB view details)

Uploaded CPython 3.12+manylinux: glibc 2.28+ x86-64

patchcraft-0.5.1-cp312-abi3-manylinux_2_28_aarch64.whl (307.6 kB view details)

Uploaded CPython 3.12+manylinux: glibc 2.28+ ARM64

patchcraft-0.5.1-cp312-abi3-macosx_11_0_arm64.whl (266.4 kB view details)

Uploaded CPython 3.12+macOS 11.0+ ARM64

patchcraft-0.5.1-cp312-abi3-macosx_10_13_x86_64.whl (271.8 kB view details)

Uploaded CPython 3.12+macOS 10.13+ x86-64

File details

Details for the file patchcraft-0.5.1.tar.gz.

File metadata

  • Download URL: patchcraft-0.5.1.tar.gz
  • Upload date:
  • Size: 320.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for patchcraft-0.5.1.tar.gz
Algorithm Hash digest
SHA256 c448d1b63455a331876c855df811c00657738313dc5772f4afefbc5326898566
MD5 1f5f99d59674d61f92613f9c33f86c11
BLAKE2b-256 ac21b0d78a53d7fb7d7db09f33a6887be2a79fccac7ff7c3285874ff8834feb4

See more details on using hashes here.

Provenance

The following attestation bundles were made for patchcraft-0.5.1.tar.gz:

Publisher: release.yml on LeoPR/PatchCraft

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file patchcraft-0.5.1-py3-none-any.whl.

File metadata

  • Download URL: patchcraft-0.5.1-py3-none-any.whl
  • Upload date:
  • Size: 34.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for patchcraft-0.5.1-py3-none-any.whl
Algorithm Hash digest
SHA256 cd6d5ec89eadf01d5aa1d5bc2febb18eba4fb630d57acb0c8b22f3aaa5f39014
MD5 d7d57cc73735f362afcc2295394707c0
BLAKE2b-256 1cfc0642e895cc6de037f4763dc042a5fbea5797f38ec4b168d63241a9fc3249

See more details on using hashes here.

Provenance

The following attestation bundles were made for patchcraft-0.5.1-py3-none-any.whl:

Publisher: release.yml on LeoPR/PatchCraft

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file patchcraft-0.5.1-cp312-abi3-win_amd64.whl.

File metadata

  • Download URL: patchcraft-0.5.1-cp312-abi3-win_amd64.whl
  • Upload date:
  • Size: 159.9 kB
  • Tags: CPython 3.12+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for patchcraft-0.5.1-cp312-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 7c6feda2decdedd800a3de2187186eeace3120e01568a6d1b7b9c3dd8f9d3fc1
MD5 7eeae77aa98c0fc3599ea726deb2fcac
BLAKE2b-256 0f135c826e7beb43d18871c2da0e680bf2d5a6341ffdc7f17e4f62e0d01392db

See more details on using hashes here.

Provenance

The following attestation bundles were made for patchcraft-0.5.1-cp312-abi3-win_amd64.whl:

Publisher: release.yml on LeoPR/PatchCraft

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file patchcraft-0.5.1-cp312-abi3-manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for patchcraft-0.5.1-cp312-abi3-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 5fadd33f54cb9b090bdaa402bc357cb02eb198995d9b682b0ec98305762f2e3f
MD5 01394127d2ff81975cfcaef977433ca7
BLAKE2b-256 54185137d45537ccbb3ece7258b656886954c5acfed9fa4d5951a937e2fa1e66

See more details on using hashes here.

Provenance

The following attestation bundles were made for patchcraft-0.5.1-cp312-abi3-manylinux_2_28_x86_64.whl:

Publisher: release.yml on LeoPR/PatchCraft

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file patchcraft-0.5.1-cp312-abi3-manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for patchcraft-0.5.1-cp312-abi3-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 b61c809b7c86e6fa7a00edec567314facde800b639a832c6245b0bf95b6d809a
MD5 e423b73fcee4d440dbec4dc4714688a3
BLAKE2b-256 e8a9ad7f26716de380b305ba9dc1efa501b6d2db3cd987c12e794626b66ed93f

See more details on using hashes here.

Provenance

The following attestation bundles were made for patchcraft-0.5.1-cp312-abi3-manylinux_2_28_aarch64.whl:

Publisher: release.yml on LeoPR/PatchCraft

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file patchcraft-0.5.1-cp312-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for patchcraft-0.5.1-cp312-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 9a5e9e6e15710821db9d9bfea8cc8d07aa9b9dd8d60a5c4f1fb55cb0e39e6682
MD5 82f708eaf9a57b580f3cada50fb6d8e9
BLAKE2b-256 7e89d0895f3b6991ff690d2085d3a9461dc983ed93cad079991c5afed5da1428

See more details on using hashes here.

Provenance

The following attestation bundles were made for patchcraft-0.5.1-cp312-abi3-macosx_11_0_arm64.whl:

Publisher: release.yml on LeoPR/PatchCraft

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file patchcraft-0.5.1-cp312-abi3-macosx_10_13_x86_64.whl.

File metadata

File hashes

Hashes for patchcraft-0.5.1-cp312-abi3-macosx_10_13_x86_64.whl
Algorithm Hash digest
SHA256 77e284bea2994df114b5c4105a6a9edbc4a392949b91e98ea87a140897020c85
MD5 162a1deb72df74cc1674adc1b43833e9
BLAKE2b-256 c1dccddd52f93b7281fd95756d3d121e1e9a260f4fc26665d296258b0618b695

See more details on using hashes here.

Provenance

The following attestation bundles were made for patchcraft-0.5.1-cp312-abi3-macosx_10_13_x86_64.whl:

Publisher: release.yml on LeoPR/PatchCraft

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.5.4

7 files

0.5.3

7 files

0.5.2

7 files

This release

0.5.1 This release

7 files

0.5.0

2 files

0.2.2

2 files

0.2.1

2 files

0.2.0

2 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