Skip to main content

pixelmap-multiview-python

PyPI Python versions License: MIT

Turn a set of ordinary photos of a scene into a textured 3D model.

Python bindings for the pixelmap_multiview Rust crate. It maps every pair of three or more photos with pixelmap's dense correspondence, works out where each photo was taken from, and fuses all of the views into one mesh. You don't need calibration, markers or measurements.

These 23 phone photos were taken while walking once around a statue:

Twenty-three photos of a bronze statue of a woman carrying a child, taken from all sides while walking around it

The mesh fused from them, shown untextured in MeshLab from three sides:

The reconstructed 3D mesh of the statue, seen from three different angles

Install

pip install pixelmap-multiview-python            # the library; NumPy is the only dependency
pip install "pixelmap-multiview-python[images]"  # plus Pillow, for load_photos()

The distribution is pixelmap-multiview-python, and the import name is pixelmap_multiview. Wheels are published for Linux (x86-64, aarch64, musl), macOS (Apple silicon and Intel) and Windows (x86-64), for CPython 3.9 and newer. You only need a Rust toolchain to build from source.

Quick start

import pixelmap_multiview as pmv

photos, focal = pmv.load_photos(["a.jpg", "b.jpg", "c.jpg", "d.jpg"])
model = pmv.reconstruct(photos, quality="medium", focal_35mm=focal)

print(model)  # <pixelmap_multiview.Model 23314 vertices, 45708 triangles, 4 of 4 photos>
model.save("out/statue.obj")  # + statue.mtl + statue_texture.png, for MeshLab or Blender
model.save("out/statue.html")  # open it in a browser and turn the model around

load_photos turns each photo upright according to its EXIF orientation and scales it to 1200 px on the long side. It also returns the 35 mm-equivalent focal length from EXIF when every photo records the same one. Photos from anywhere else work too: pass a list of uint8 arrays (H, W, 3), (H, W, 4) or (H, W), or Pillow images, all the same size.

What you get

Attribute Contents
model.vertices, model.normals (V, 3) float64
model.triangles (T, 3) uint32, counter-clockwise seen from the normal's side
model.vertex_colours (V, 3) uint8
model.texture.atlas (H, W, 4) uint8, with each part of the surface taken from the photo that sees it best
model.texture.texcoords, .face_texcoords, .face_views the atlas mapping, OBJ convention
model.intrinsics fx, fy, cx, cy, .matrix, and whether the focal length was provided, from EXIF, estimated or refined
model.cameras a Pose per photo (rotation, translation, centre, matrix), or None for a photo left out
model.pairs what two-view geometry made of each pair: coverage, relative pose, inlier ratio, and why it was rejected

All of these are in one frame. The first camera of the best pair sits at the origin looking along +z with +y down the image, and the distance between that pair's cameras is the unit of length. Nothing is metric.

Saving

model.save(path) picks the format from the suffix:

Suffix Writes Opens in
.obj textured Wavefront OBJ, plus <stem>.mtl and <stem>_texture.png MeshLab, Blender, CloudCompare
.x3d textured X3D, plus <stem>_texture.png MeshLab, X3D viewers
.glb binary glTF 2.0, one file with the texture embedded Blender, Windows 3D Viewer, three.js, most game engines
.html one web page with the textured model embedded, shown in 3D any browser
.ply ASCII PLY with a colour per vertex viewers that do not load textures

It returns every file it wrote. The .html page is titled after the file's stem unless you pass title="...". The model is embedded in the page, but the viewer, Google's <model-viewer>, is fetched from cdn.jsdelivr.net, so opening the page needs a network connection. Every format is turned half a revolution about x so that viewers show the model upright; the arrays on model are not turned.

Following along and stopping

A run takes minutes of work. on_event hears about all of it, as data rather than prose:

def on_event(event):
    if event.progress is not None:  # None for log lines and dropped photos
        print(f"{event.progress:5.1%}  {event.message}")
    if isinstance(event, pmv.PairMapped):
        event.points  # (rows, cols, 2): the dense correspondence for this pair, NaN where unmapped


model = pmv.reconstruct(photos, on_event=on_event)

The events are Started, StageProgress, PairProgress, PairMapped, PairRejected, ViewDropped and Log. Each one carries stage, progress and message, along with its own fields. If on_event raises an exception, the run stops at the next checkpoint and the exception propagates. Ctrl-C stops a run the same way.

To keep your own thread free, run the reconstruction as a Job:

job = pmv.Job(photos, quality="medium")
for event in job:  # each event as it happens; ends when the run does
    print(f"{job.status.progress:.0%} {event.message}")
model = job.join()

For code with its own event loop, job.next_event(timeout=0) polls without waiting, and job.status works from any thread. After job.cancel(), join() raises CancelledError, well within a second.

When a capture fails

A run that can't produce a correct model raises an exception rather than returning a plausible-looking wrong one. Each exception names the stage that failed and says what to change:

try:
    model = pmv.reconstruct(photos)
except pmv.ReconstructionError as err:
    print(err.stage, err.reason)  # tracks too_few_tracks
    # only 338 points could be followed across three or more photos (need 500);
    # the photos share too little of the scene
    print(err)

InvalidInputError (also a ValueError) covers too few photos, photos of different sizes, and photos that are too small. ReconstructionError covers captures that can't be reconstructed: a camera that only turned, a flat scene, or photos with too little in common. Match on err.reason rather than on the message. The failure's details are attributes, such as err.found and err.required.

For a good capture:

  • Step sideways between shots rather than turning on the spot.
  • Let neighbouring photos overlap generously.
  • Keep one camera at one zoom setting throughout.

Cost

Mapping the pairs dominates: N photos take N(N−1)/2 pixelmap runs. One pair of 4:3 photos, native build:

Quality 800 px 1200 px 1800 px
low 0.60 s 0.56 s 0.50 s
medium 2.25 s 2.30 s 2.38 s
high 5.81 s

Input size barely changes the matching time, because pixelmap scales every photo to a fixed working width first. What a larger input buys is precision in the geometry that follows. Runs are deterministic: the same photos, quality and seed give the same model.

Development

python -m venv .venv && . .venv/bin/activate
pip install maturin pytest pillow ruff numpy
maturin develop --release   # release matters: the tests run whole reconstructions
pytest

License

MIT

Metadata

Release files for pixelmap-multiview-python 0.1.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 pixelmap-multiview-python 0.1.1
File Size Uploaded
pixelmap_multiview_python-0.1.1.tar.gz 40.4 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for pixelmap-multiview-python 0.1.1
File
pixelmap_multiview_python-0.1.1-cp39-abi3-win_amd64.whl CPython 3.9 abi3 Windows x86-64 Details
pixelmap_multiview_python-0.1.1-cp39-abi3-musllinux_1_2_x86_64.whl CPython 3.9 abi3 Linux musl 1.2+ x86-64 Details
pixelmap_multiview_python-0.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.9 abi3 Linux glibc 2.17+ x86-64 Details
pixelmap_multiview_python-0.1.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.9 abi3 Linux glibc 2.17+ ARM64 Details
pixelmap_multiview_python-0.1.1-cp39-abi3-macosx_11_0_arm64.whl CPython 3.9 abi3 macOS 11.0+ ARM64 Details
pixelmap_multiview_python-0.1.1-cp39-abi3-macosx_10_12_x86_64.whl CPython 3.9 abi3 macOS 10.12+ x86-64 Details

Total release size: 4.4 MB

Release files / pixelmap_multiview_python-0.1.1.tar.gz

Download URL pixelmap_multiview_python-0.1.1.tar.gz
Size 40.4 kB
Tags Source
SHA-256 checksum
How to use checksums
916e5e9cbaa41e866789d70681ae0a875b834bf74d348113b3a7a4a8dbedd7aa
BLAKE2b-256 checksum
How to use checksums
1720c80f272ae7fca8f5de8cc36d9bb579ef8314453c38af58d57b90d16a6f6e
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 Oct 4, 2026.

Transparency log

Release files / pixelmap_multiview_python-0.1.1-cp39-abi3-win_amd64.whl

Download URL pixelmap_multiview_python-0.1.1-cp39-abi3-win_amd64.whl
Size 615.5 kB
Tags CPython 3.9 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
059e5336fa85934dd128fd16fed1cbbd96aaa234487ef1b4a66b5068f279977c
BLAKE2b-256 checksum
How to use checksums
52f37c4ea2d799dc2f7b1c82676afa26d55d3492a704d2551272bd604c857961
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 Oct 4, 2026.

Transparency log

Release files / pixelmap_multiview_python-0.1.1-cp39-abi3-musllinux_1_2_x86_64.whl

Download URL pixelmap_multiview_python-0.1.1-cp39-abi3-musllinux_1_2_x86_64.whl
Size 951.3 kB
Tags CPython 3.9 Linux musl 1.2+ x86-64 abi3
SHA-256 checksum
How to use checksums
1d57facce2d0cea6cc83778fd9488b2b12b6be8a60ed2e64d57fc533a500ae28
BLAKE2b-256 checksum
How to use checksums
e0e61af2cc63294dbd32e73f7715b1f4c38094ed6d426a2c1b9e4d521b46cc88
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 Oct 4, 2026.

Transparency log

Release files / pixelmap_multiview_python-0.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL pixelmap_multiview_python-0.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 741.5 kB
Tags CPython 3.9 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
cc50d0a4bc73e8b798afa75868a3458df1670d8a9cfc2b425142b841e4b22865
BLAKE2b-256 checksum
How to use checksums
21268105a4a9eb108a35feecccb2db6e1533b5f999bf0a2da17da8a80f133d02
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 Oct 4, 2026.

Transparency log

Release files / pixelmap_multiview_python-0.1.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL pixelmap_multiview_python-0.1.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 705.0 kB
Tags CPython 3.9 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
74f7fda160858bda67ef6b7f59fe8a27b4b9c79d85ce5d66b5cde40918d477a8
BLAKE2b-256 checksum
How to use checksums
3a0f5022897abad447985deeea045655c61232ebfe9a8525aac0cc93c868dd0f
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 Oct 4, 2026.

Transparency log

Release files / pixelmap_multiview_python-0.1.1-cp39-abi3-macosx_11_0_arm64.whl

Download URL pixelmap_multiview_python-0.1.1-cp39-abi3-macosx_11_0_arm64.whl
Size 661.0 kB
Tags CPython 3.9 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
7e3d84658a880b9b635ef99650efba1eeed7e26345e9a923cddfae27d96294cb
BLAKE2b-256 checksum
How to use checksums
7b2d0e4b5caae7f1282903e4fc0e50e693c60293b0953d6f1643b1dbed351b9c
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 Oct 4, 2026.

Transparency log

Release files / pixelmap_multiview_python-0.1.1-cp39-abi3-macosx_10_12_x86_64.whl

Download URL pixelmap_multiview_python-0.1.1-cp39-abi3-macosx_10_12_x86_64.whl
Size 692.2 kB
Tags CPython 3.9 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
1a6070de9addaa7f3209718ba2f1932c15b142102b6ef19fbbce2cf9c78aa0f3
BLAKE2b-256 checksum
How to use checksums
082f5caef754738aaca8274b9d08187a3a00a66ee83b30a4f3226afda8af335c
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 Oct 4, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.1 This release

7 release files

0.1.0

7 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