pixelmap-multiview-python
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:
The mesh fused from them, shown untextured in MeshLab from three sides:
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
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. model.save() writes .obj (textured),
.x3d (textured) or .ply (vertex colours), turned half a revolution about x so that
viewers show the model upright.
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
Release files for pixelmap-multiview-python 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pixelmap_multiview_python-0.1.0.tar.gz | 38.2 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| pixelmap_multiview_python-0.1.0-cp39-abi3-win_amd64.whl | CPython 3.9 | abi3 | Windows x86-64 | Details |
| pixelmap_multiview_python-0.1.0-cp39-abi3-musllinux_1_2_x86_64.whl | CPython 3.9 | abi3 | Linux musl 1.2+ x86-64 | Details |
| pixelmap_multiview_python-0.1.0-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.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | CPython 3.9 | abi3 | Linux glibc 2.17+ ARM64 | Details |
| pixelmap_multiview_python-0.1.0-cp39-abi3-macosx_11_0_arm64.whl | CPython 3.9 | abi3 | macOS 11.0+ ARM64 | Details |
| pixelmap_multiview_python-0.1.0-cp39-abi3-macosx_10_12_x86_64.whl | CPython 3.9 | abi3 | macOS 10.12+ x86-64 | Details |
Total release size: 4.3 MB
Release files / pixelmap_multiview_python-0.1.0.tar.gz
| Download URL | pixelmap_multiview_python-0.1.0.tar.gz |
|---|---|
| Size | 38.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1c3a68a34db7ec6b52d7b39b059de958b67e91877319097035cdedd9db659aa3
|
|
BLAKE2b-256 checksum How to use checksums |
69407d6266f6204c9635774ae4cc09b04b21a99b80f0cf61236a00f86a49aacd
|
| 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 logRelease files / pixelmap_multiview_python-0.1.0-cp39-abi3-win_amd64.whl
| Download URL | pixelmap_multiview_python-0.1.0-cp39-abi3-win_amd64.whl |
|---|---|
| Size | 602.8 kB |
| Tags | CPython 3.9 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
b5cad1990133461d5b4576bedb5dd6898555f6e5766cb3558794a4a0cdf597ea
|
|
BLAKE2b-256 checksum How to use checksums |
e6f8ea46bba0298411a5562f1188bd19d51a2f31a4a420cbf25a04eec675b3e3
|
| 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 logRelease files / pixelmap_multiview_python-0.1.0-cp39-abi3-musllinux_1_2_x86_64.whl
| Download URL | pixelmap_multiview_python-0.1.0-cp39-abi3-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 936.3 kB |
| Tags | CPython 3.9 Linux musl 1.2+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
2de7fdc582df9a8d7859a7dd2ac981c683a97e23eafd86e1217fbaaec35c4f64
|
|
BLAKE2b-256 checksum How to use checksums |
9c8aec6b2fe5b11ecd07855ce37c70302f9c742f814866b47531d65dba42b27f
|
| 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 logRelease files / pixelmap_multiview_python-0.1.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | pixelmap_multiview_python-0.1.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 726.9 kB |
| Tags | CPython 3.9 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
190a48efca70c7365d36266ab355ef503d5d9f26234a1b4bbff7632df0ffae28
|
|
BLAKE2b-256 checksum How to use checksums |
91a451700114aced5a9433d57d6256248d76d8d02b832cadd487cd7797f2ce3a
|
| 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 logRelease files / pixelmap_multiview_python-0.1.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | pixelmap_multiview_python-0.1.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 692.3 kB |
| Tags | CPython 3.9 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
789645681f06c5ab2b17831a22d118035ad5d25f29069f1ae6a09e95168ea9e9
|
|
BLAKE2b-256 checksum How to use checksums |
472736f65acb209f7532a08fe22ea2946c901db1941392ebbdd670ceabb413dd
|
| 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 logRelease files / pixelmap_multiview_python-0.1.0-cp39-abi3-macosx_11_0_arm64.whl
| Download URL | pixelmap_multiview_python-0.1.0-cp39-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 646.2 kB |
| Tags | CPython 3.9 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
7efd2aa28267edaec9a8e5cd675d2e3d5857186bd53678763b98dbb9991a41f7
|
|
BLAKE2b-256 checksum How to use checksums |
d2c9e25c94e944f2277a2de651375f156ab7ed033f010c35a1c8388677d4c8d0
|
| 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 logRelease files / pixelmap_multiview_python-0.1.0-cp39-abi3-macosx_10_12_x86_64.whl
| Download URL | pixelmap_multiview_python-0.1.0-cp39-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 677.0 kB |
| Tags | CPython 3.9 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
56ddf50cc4d2634419cbf2973be74e67d64855dbc71c7f019bb1852c27787d5c
|
|
BLAKE2b-256 checksum How to use checksums |
28d6c41ba8a39c7317c0191a66ecc089d0a90465661127b00a8693021a56c88e
|
| 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