justPFM
A small NumPy-based Python module to read and write Portable Float Map images.
Install
Supported Python versions: 3.7–3.14. See SUPPORT.md for the compatibility policy and interpreter-specific test tooling.
pip install justpfm
Usage
from pathlib import Path
import numpy as np
from justpfm import read_pfm, write_pfm
# Grayscale: the first array axis is height, and rows are top-first.
path = Path("grayscale.pfm")
pixels = np.arange(15, dtype=np.float32).reshape(3, 5)
write_pfm(path, pixels)
loaded = read_pfm(path)
assert loaded.shape == (3, 5, 1)
np.testing.assert_array_equal(loaded[..., 0], pixels)
# RGB uses the last axis for red, green, and blue.
rgb = np.zeros((3, 5, 3), dtype=np.float32)
rgb[..., 0] = 1
write_pfm("rgb.pfm", rgb, scale=0.5)
np.testing.assert_array_equal(read_pfm("rgb.pfm"), rgb * 0.5)
The existing from justpfm import justpfm import remains supported.
API contract
write_pfm(file_name, data, scale=1) -> None
file_nameaccepts a string or a filesystem path object such aspathlib.Path.datamust be a NumPy float32 array with positive height and width, shaped(H, W),(H, W, 1), or(H, W, 3). Both little- and big-endian float32 dtypes are supported, including noncontiguous arrays. Pixel NaNs and infinities are allowed.scalemust be positive and finite. The writer stores the original pixel samples and records the scale in the header. The header sign describes the array's byte order; callers supply only the magnitude.- Rows are written bottom-first as required by PFM.
read_pfm(file_name, *, max_pixels=None) -> numpy.ndarray
- Returns float32 pixels shaped
(H, W, C), whereCis 1 for grayscale or 3 for RGB. The returned dtype retains the file's byte order. - Applies the header scale magnitude to pixel values, except magnitudes within
math.isclose(scale, 1.0)(relative tolerance1e-9). Thus nonunit write scales generally change values when read back; scaling uses float32 arithmetic and may overflow or underflow for extreme values. - Returns top-first rows using a view with negative row strides. For native
byte order and contiguous storage, use
np.ascontiguousarray(loaded, dtype=np.float32). Useloaded[..., 0]to remove the grayscale channel axis. - Requires positive dimensions, a finite nonzero header scale, and exactly the expected raster bytes. It checks the file size before allocating pixel data and limits reads to the declared number of samples.
- Each of the three header lines must end in a newline and occupy at most
4096 bytes including the newline. Overlong and truncated headers raise
ValueError; header reads use bounded buffers. - Set the keyword-only
max_pixelsargument to a positive built-in integer to reject images whoseH * Wexceeds that limit before allocating their raster. The defaultNoneimposes no pixel-count limit. Booleans, other types, zero, and negative limits raiseValueErrorbefore the file is opened. The limit counts pixels rather than channel samples, so RGB and grayscale images of equal dimensions have the same pixel count.
Invalid shapes, dtypes, dimensions, scales, headers, and payload lengths raise
ValueError. Filesystem failures such as missing files or denied access raise
OSError subclasses. Both functions operate on filesystem files, not in-memory
file-like objects. Sufficient memory is still required. For untrusted files,
choose a pixel limit appropriate for the application, for example:
loaded = read_pfm("upload.pfm", max_pixels=4_000_000)
The raster needs 4 bytes per grayscale pixel or 12 bytes per RGB pixel. This
limit bounds that allocation, not total process memory, application copies, or
other overhead. With max_pixels=None, large valid images remain supported.
Interchange with Netpbm
Use scale=1 to exchange normalized pixel intensities with Netpbm. Its
pfmtopam reader divides samples by the header magnitude, while justPFM
multiplies by it. Nonunit scales therefore have different intensity semantics.
The tests use files generated by the actual Netpbm implementation and verify
both directions; fixture provenance and examples
document the difference.
Saving files
Writes use a temporary file in the destination directory, then atomically replace its directory entry after writing and closing. A failed write preserves an existing destination and cleans up the temporary file. The destination directory must permit creating temporary files and replacing entries.
Existing regular file permission bits are preserved. New files have private
permissions (0600 on POSIX). A destination symlink is replaced with a regular
file; its target is left unchanged. Other metadata, including ownership, is not
copied. Atomic replacement does not guarantee durability after a power failure.
Writes use bulk serialization with reusable pixel scratch space bounded by the larger of 8 MiB and one image row. Data already contiguous in file order needs no pixel scratch buffer. See the before/after measurements for the speed and memory tradeoff.
Development
See CONTRIBUTING.md for setup and commit-hook installation.
Run pre-commit run --all-files to execute the same lint, formatting, tests,
100% statement/branch coverage, and packaging gates used by CI.
Property tests vary dimensions, pixel values, array layout, byte order, scale,
and malformed payload sizes. They also check the reader against independently
encoded binary fixtures. Local and CI runs use the same deterministic Hypothesis
profile: 60 examples per property, no example database, and no timing deadline.
Health checks remain enabled. Hypothesis uses 6.79.4 on Python 3.7–3.9 and
6.168.0 on Python 3.10–3.14, with matching pins in the test extra and tox.
Each interpreter runs the same properties; deterministic examples are repeatable
within its pinned toolchain, not guaranteed identical across Hypothesis versions.
Metadata
Release files for justpfm 1.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 | |
|---|---|---|---|
| justpfm-1.1.0.tar.gz | 31.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| justpfm-1.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 40.6 kB
Release files / justpfm-1.1.0.tar.gz
| Download URL | justpfm-1.1.0.tar.gz |
|---|---|
| Size | 31.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1093f094bacaae5265256e42a86f393efeda1233442bd29d8ab9873254b3ac0f
|
|
BLAKE2b-256 checksum How to use checksums |
3285e4fb70ee61725c688521c086069d42e533681bd39a7bae0603ba1a4e2207
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.25
|
Release files / justpfm-1.1.0-py3-none-any.whl
| Download URL | justpfm-1.1.0-py3-none-any.whl |
|---|---|
| Size | 8.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
68bd73ef43bd3a4dc4fd2a4c3295b726c2b4fee1b1ffd0b869573a3ff94f07ff
|
|
BLAKE2b-256 checksum How to use checksums |
de3252fd6cdc394b2778bec89495f067a42825f1871ca885569cfcfe21f02738
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.25
|