Skip to main content

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_name accepts a string or a filesystem path object such as pathlib.Path.
  • data must 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.
  • scale must 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), where C is 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 tolerance 1e-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). Use loaded[..., 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_pixels argument to a positive built-in integer to reject images whose H * W exceeds that limit before allocating their raster. The default None imposes no pixel-count limit. Booleans, other types, zero, and negative limits raise ValueError before 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)

Source distribution for justpfm 1.1.0
File Size Uploaded
justpfm-1.1.0.tar.gz 31.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for justpfm 1.1.0
File Interpreter ABI Platform
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

Release history Release notifications | RSS feed

1.2.1

2 release files

1.2.0

2 release files

This release

1.1.0 This release

2 release files

1.0.0

2 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