Skip to main content
ReString: string art from a photo

Feed in a portrait. The greedy search picks one thread at a time, and each pull moves the canvas measurably closer to the photo. The output is the picture one uncut thread draws around the nails, plus the nail-by-nail winding order. Black and white uses one thread. Color runs pick a palette: the pure RGB triple, CMY, CMYK or a JSON list of the floss colors you own.

Quick start · Usage · Tuning · Development · Contributing · License

Try it in the browser: the playground runs the same core as a wasm module. Drop a photo, no install. The page deploys from web/ (.github/workflows/pages.yml).

Overview

  • For: makers winding physical boards, machine and plotter owners, and anyone who wants a digital string-art render.
  • Input: a PNG or JPEG photo; face-centered portraits work best.
  • Output: a PNG render of the thread pattern; the Python API also returns the continuous nail-by-nail pull order.
  • Scope: greedy search on a 300 px working grid, five nail-layout families, black-and-white and color palettes, .restring project files, printable winding posters, GIF animations and a physical thread model. Building from source requires a Rust toolchain, there is no pure-Python fallback.

A photo becomes a thread drawing

One run of the CLI: the same portrait at 1000 threads around 300 nails and 3000 threads around 598 nails (--rect -s 0.05).

The project started as a 2020 university course assignment by Kaspar Valk, Kert Tali and Almas Baktubayev. The project poster describes the original idea.

Features

  • One uncut thread: the pull order is continuous by construction, so you wind a single thread by hand or machine.
  • Real thread colors: black-on-white, white-on-black, or a palette. The built-ins are RGB, CMY and CMYK, or load a JSON list of the floss colors you own. CMYK measures 42 to 45 percent better than RGB at an equal budget.
  • Custom boards: polygon, star, multi-ring and SVG-outline layouts via --layout star:5:0.55, plus non-square aspect crops (layouts guide).
  • From photo to board: save a .restring project, re-render in milliseconds, print a numbered-nail poster with the thread length in meters, watch the winding as a GIF.
  • Measured, not guessed: every flag has a degradation curve behind it in the tuning guide.

Quick start

Install

pip install restring-art

Wheels cover linux (x86_64, aarch64), macos (x86_64, arm64) and windows (x86_64) on CPython 3.12 and newer. No Rust toolchain needed. Building from source needs uv and Rust, see Development.

Run your first example

restring -i portrait.png -o out.png --rect -n 2 -l 3000   # best quality, ~1.4 s

Expected result:

[...] [restring.cli] [INFO]: Nails amount: 598

out.png now holds the rendered thread pattern.

Usage

Basic example

Three presets cover most jobs (timed on a 24-core Linux box):

# fast preview, ~0.3 s
restring -i portrait.png -o out.png --rect -l 300

# balanced, ~0.6 s
restring -i portrait.png -o out.png --rect -n 4 -l 2000

# maximum measured quality, ~1.4 s
restring -i portrait.png -o out.png --rect -n 2 -l 3000

CLI reference

Flag Meaning Default
-i <path> input image (PNG or JPEG) required
-o <path> output PNG output.png
-d <int> output side length in pixels 300
-n <int> nail step for the circle and rect layouts. Smaller values place more nails. Minimum 1 4
--layout <spec> nail layout: circle, rect, polygon:sides[:count], star:points:inner[:count], rings:r:count,r:count, svg:file.svg[:count], optional @aspect suffix for non-square boards. Custom layouts take counts instead of -n circle
-l <int> iteration budget. Without the flag the search stops after 3 pulls with no improvement run to exhaustion
-s <float> thread strength at export, 0..1. Leave unset and the run derives the value from the actual pull count: k/L with k=104, clamped to 0.03..0.2, within 2 percent of the hand-tuned optimum at the measured budgets auto
-r <int> scan a fixed random subset of candidates each iteration. Trades quality for speed. Good values sit near 100 to 150 all nails
-r1 <float> vertical nail ellipse multiplier 1
-r2 <float> horizontal nail ellipse multiplier 1
--rect nails on the rectangle border instead of the circle. Better for square portraits, about 2x lower error circle
--rgb colored output with three interleaved pure RGB threads black and white
`--palette <name file>` thread palette: rgb, cmy, cmyk, or a JSON file of {"name", "rgb": [r, g, b]} colors (cap 8 threads). CMYK measures 42 to 45 percent better color MSE than RGB at an equal budget
--wb white thread on a black canvas black on white
--weight <float> importance weighting. A Gaussian weight map steers threads toward the image center, where the viewer looks. Suggested range 1.5 to 5.0. Portrait subjects only, about 25 percent more scan time off
--min-chord <px> candidate filter: chords shorter than the distance in working-grid pixels are banned. Portrait measurement: the greedy never picks short chords, the filter only degrades MSE past 250 px. Intended for sparse custom layouts off
--project-out <path> save the nail layout and pull order as a .restring project alongside the PNG off
--project <path> re-render from a .restring project instead of an image: skips the search, applies the current -s and -d off
--anim <path> with --project: animated GIF of the winding sequence. --anim-frames sets the frame count, 180 is about 6 s at 30 fps. A 2000-pull project at the default 600 px export lands near 45 MB; -d 450 brings it to about 28 MB, -d 380 under 25 MB off
--realistic physical thread model: threads of --thread-width mm (default 0.2) on a --canvas-size mm board (default 400). At exports well above 300 px the 0.3 mm width beats the hairline by 35 percent MSE. The search never changes hairline
--rollback truncate a stagnating run to its best-error prefix. Measured no-op: the search never applies non-improving pulls, so nothing rolls back (measurement) off
--metrics <path.json> write a quality report: MSE, correlation, plain and blurred SSIM against the working target, plus pulls, nails and elapsed time. Generate mode only off
--thread-width <mm> thread diameter for --realistic. Birsak used 0.15, typical polyester sits near 0.2 0.2
--canvas-size <mm> board size: the poster printing size (default 260 for --poster) or the physical size behind --realistic (default 400) per feature

Nails sit evenly on the perimeter. The circle starts at 9 o'clock and runs clockwise. The rectangle starts at the top left corner. The pull order is continuous by construction, so you wind one uncut thread.

Save and re-render

The project file keeps the nail layout and the pull order in plain JSON, so a result re-renders in milliseconds without a search and any tool can read the winding sequence:

restring -i portrait.png -o out.png --rect -l 2000 --project-out result.restring
restring --project result.restring -o out2.png   # byte-identical re-render

The current -s and -d apply at re-render time. Schema and compatibility promise: docs/project-format.md.

Wind it for real

Turn a result into a printable poster: numbered nails, the footer with the nail count, pull count and total thread length in meters.

restring -i portrait.png -o out.png --rect -l 2000 --project-out result.restring --poster poster.svg
restring --project result.restring --poster poster.svg --canvas-size 300

Print at 100 percent scale, hammer the nails on the board outline, wind. Printing checklist and thread buying guidance: docs/winding.md.

As a library

from restring import GenerateOptions, generate

result = generate("portrait.png", GenerateOptions(layout="rect", pulls=2000))
result.image.save_png("out.png")
print(result.stats.pulls_per_thread)

The full options table, the Result fields and the deprecated 1.x names: docs/api.md. Images travel as _core.Image objects; the pixel data stays in Rust memory, so calls pass references instead of copying arrays.

Tuning

-s is fitted for you: the run derives the export strength from the actual pull count (104/L clamped to 0.03..0.2) and lands within 2 percent of the hand-tuned optimum at the measured budgets. The working simulation darkens each pull with strength 0.05; the export accumulates with strength -s. When -s is too high for the thread count, the render reaches its target halfway, then overshoots and darkens. Manual -s values stay for special cases, see the tuning guide.

Flag Recommended Rule
--rect always on for square portraits 1.8 to 2.7x lower error than the circle
-s leave unset, auto fits the pull count measured optimum scales as 1 over thread count, k=104 clamped 0.03 to 0.2
-l up to saturation, about 2500 to 2900 threads beyond saturation the search finds no improvements
-n 4 for the knee, 2 for maximum quality 844 circle nails buy 6 percent error reduction at 4x the cost
-r 100 to 150 while exploring, drop for the final run k=100 saves 40 percent of the time for 15 percent error
-d 300 exporting above the working grid hurts
--weight 3.0 for portraits, off for other subjects center MSE falls 24 to 28 percent, global MSE rises 3 percent
--min-chord off the greedy never picks short chords, thresholds bite past 230 px at up to 24 percent MSE cost

Every flag, the numbers behind the rules and the degradation curves: docs/tuning.md.

Development

git clone git@github.com:InzGIBA/ReString.git
cd ReString
uv sync

Requirements: uv and a Rust toolchain with a C linker for cargo. uv fetches Python 3.14 and compiles the core on first sync. No system C compiler (minimal WSL or container)? Use zig as the cargo linker — the recipe lives in docs/architecture.md.

Source checkouts prefix every command with uv run, for example uv run restring -i portrait.png -o out.png --rect -l 300.

Checks

uv run pytest                   # 211 tests, ~11 s
uv run ruff check .
uv run python scripts/benchmark.py --label before   # or after, then --plot
uv run python scripts/make_gallery.py               # regenerate docs/img/gallery.png

CI runs the same checks on every push to main and every pull request, on linux, macos and windows (.github/workflows/ci.yml). A v* tag builds wheels for three platforms, publishes to PyPI and opens the GitHub Release (.github/workflows/release.yml).

Documentation

Document Contents
docs/architecture.md how the pipeline works, the Rust core, correctness model, performance, build, wasm target
docs/api.md the 2.0 facade: GenerateOptions, Result, deprecated 1.x names
docs/tuning.md every flag, degradation curves, recommended values, presets
docs/layouts.md layout specs, the clockwise ordering rule, non-square boards
docs/project-format.md the .restring schema and the compatibility promise
docs/winding.md printing checklist, reading the poster, buying thread
docs/history.md the migration story: stages, numbers, rejected hypotheses
docs/research/ twelve experiment docs with the numbers behind every verdict, plus the related-work survey

Roadmap

Open experiment ideas arrive through the research-idea issue template: each states a question, a hypothesis and a pre-registered success bar. Closed questions live next to their measurements in docs/research/, so nobody re-litigates them. Answered so far:

  • Re-run the RGB sweeps: working strength 0.05 adopted, 13 to 17 percent better MSE (measurement).
  • Test the working grid above 300 px: keep 300, under 3 percent MSE at up to 2.4x time (measurement).

Contributing

Issues and pull requests are welcome. Templates structure both: bug reports carry the command line and image, feature requests check the measured dead ends, research ideas state a pre-registered success bar, and the PR template asks for the measurement or test evidence.

Wound a physical board? Post the photos under the "show and tell" label. Physical outputs are the best advertisement a string-art project gets. The example gallery (examples/gallery/) shows the configuration families and the commands behind every tile.

  1. Describe the problem or the proposed improvement.
  2. Make a focused change.
  3. Add or update relevant tests and documentation.
  4. Run the checks above and open a pull request.

License

MIT. See LICENSE.

Photo credits

Portrait fixtures in tests/data/ are square face-centered crops of studio photos provided for this project. The demo image above uses the portrait_female.png fixture. Earlier fixtures came from Unsplash under the Unsplash License.

Metadata

Release files for restring-art 2.0.1

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

Source distribution (sdist)

Source distribution for restring-art 2.0.1
File Size Uploaded Provenance Actions
restring_art-2.0.1.tar.gz 49.6 kB With provenance Details for restring_art-2.0.1.tar.gz

Built distributions (wheels)

Table of built distributions (wheels) for restring-art 2.0.1
File Actions
restring_art-2.0.1-cp312-abi3-win_amd64.whl CPython 3.12 abi3 Windows x86-64 Details for restring_art-2.0.1-cp312-abi3-win_amd64.whl
restring_art-2.0.1-cp312-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl CPython 3.12 abi3 Linux glibc 2.17+ x86-64 Details for restring_art-2.0.1-cp312-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
restring_art-2.0.1-cp312-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl CPython 3.12 abi3 Linux glibc 2.17+ ARM64 Details for restring_art-2.0.1-cp312-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
restring_art-2.0.1-cp312-abi3-macosx_11_0_arm64.whl CPython 3.12 abi3 macOS 11.0+ ARM64 Details for restring_art-2.0.1-cp312-abi3-macosx_11_0_arm64.whl
restring_art-2.0.1-cp312-abi3-macosx_10_12_x86_64.whl CPython 3.12 abi3 macOS 10.12+ x86-64 Details for restring_art-2.0.1-cp312-abi3-macosx_10_12_x86_64.whl

Total release size: 3.4 MB

Release files / restring_art-2.0.1.tar.gz

Download URL restring_art-2.0.1.tar.gz
Size 49.6 kB
Tags Source
SHA-256 checksum
How to use checksums
b16d9ada4e0d9c3226a169ac4013a251fb61b5081ff07b6ae306bf5348151652
BLAKE2b-256 checksum
How to use checksums
8dd4dc0e40837e4039c9355ad4a1f40a6f3913ca2dd593a1e5fe5a8ffc20df55
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 11, 2026.

Transparency log

Release files / restring_art-2.0.1-cp312-abi3-win_amd64.whl

Download URL restring_art-2.0.1-cp312-abi3-win_amd64.whl
Size 599.7 kB
Tags CPython 3.12 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
c13a0d4dd5932e7ccf59f6b4c3171ccae2a53c15592714daf45b9958a5434765
BLAKE2b-256 checksum
How to use checksums
30e6e9e0c6392ffa5f61a69109513a76884578bf8823fd810b7a6f9c10ffe4de
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 11, 2026.

Transparency log

Release files / restring_art-2.0.1-cp312-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl

Download URL restring_art-2.0.1-cp312-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
Size 723.4 kB
Tags CPython 3.12 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
b4914c91370751ee6e52b166d66043e4c0d8c3cfd3ccbf671708f938167b6b9f
BLAKE2b-256 checksum
How to use checksums
165ca6f98b7ff7c4ce1fb287b653d3c2f1c14f03b3ef24d2e40860dc9cb645f1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 11, 2026.

Transparency log

Release files / restring_art-2.0.1-cp312-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl

Download URL restring_art-2.0.1-cp312-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
Size 712.0 kB
Tags CPython 3.12 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
1d0d7103deb9b2947f3e5ef7148d55c8b08990ea32af21a4c5bff0d353ff6c85
BLAKE2b-256 checksum
How to use checksums
4b87285a88af9f1ef145f6035917094635c3c1f749f9789657f17f232e378db2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 11, 2026.

Transparency log

Release files / restring_art-2.0.1-cp312-abi3-macosx_11_0_arm64.whl

Download URL restring_art-2.0.1-cp312-abi3-macosx_11_0_arm64.whl
Size 667.9 kB
Tags CPython 3.12 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
49a24d7140baeb328a2e147d17f80be3e91c21abd0e76dce291cd3b063d55299
BLAKE2b-256 checksum
How to use checksums
824d94fd1cb23edf38e46760daef67ac0dd97aa81d2d0d02cb8f80a606aca300
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 11, 2026.

Transparency log

Release files / restring_art-2.0.1-cp312-abi3-macosx_10_12_x86_64.whl

Download URL restring_art-2.0.1-cp312-abi3-macosx_10_12_x86_64.whl
Size 688.4 kB
Tags CPython 3.12 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
6bfe992a69cef052f0c6e7de5176832de2799b33265c50d8d27b1a99ee7b3f2e
BLAKE2b-256 checksum
How to use checksums
411980e68f9ab0e95a1427531cc2904ec8bcc0a5c4ed8c4b788b7a74c7df1f6e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 11, 2026.

Transparency log

Security for restring-art 2.0.1

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.

Full provenance

All 6 files in this release have been uploaded with provenance metadata.

Select from 6 files

Attestation for restring_art-2.0.1.tar.gz

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 Nov 11, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

2.0.1 This release

6 release files Full provenance

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