What’s New in Photutils 3.1?#
Here we highlight some of the new functionality of the 3.1 release. In addition to these changes, Photutils 3.1 includes several smaller improvements and bug fixes, which are described in the full Changelog.
Dependency Version Updates#
Photutils 3.1 bumps the minimum required versions of several key dependencies to provide users with access to the latest features and performance improvements:
Astropy minimum version is now 6.1.7
SciPy minimum version is now 1.14
Regions minimum version is now 0.10
scikit-image minimum version is now 0.24
gwcs minimum version is now 0.22
Shapely minimum version is now 2.1
tqdm minimum version is now 4.67
ASDF Serialization Support#
Photutils 3.1 introduces initial support for serializing
photutils objects to the Advanced Scientific Data Format (ASDF) file format. This requires
installing the asdf package. For some classes, the new optional
asdf-astropy package must also be installed.
>>> import asdf
>>> from photutils.aperture import CircularAperture
>>> aperture = CircularAperture((10, 20), r=5)
>>> with asdf.AsdfFile() as af:
... af['aperture'] = aperture
... af.write_to('aperture.asdf')
>>> with asdf.open('aperture.asdf') as af:
... aperture = af['aperture']
So far, the following classes can be serialized to and deserialized from ASDF files:
Exact Rectangular Aperture Overlap#
Rectangular apertures now support the method='exact' option for
computing pixel-aperture overlap fractions. Previously, requesting
method='exact' for a RectangularAperture or
RectangularAnnulus was silently translated to a
32x subpixel approximation. The new implementation computes the exact
analytic overlap area using a Sutherland-Hodgman polygon-clipping
algorithm:
>>> from photutils.aperture import RectangularAperture
>>> aper = RectangularAperture((10.0, 10.0), w=4.0, h=2.0, theta=0.5)
>>> mask = aper.to_mask(method='exact')
Faster Aperture Photometry#
Aperture photometry is now significantly faster for all built-in aperture shapes. The photometry for all sources is now computed in a single call into compiled code, avoiding the per-source Python overhead of creating individual aperture masks and cutouts. Typical speedups range from ~2x to more than 25x, depending on the aperture shape and overlap method, with the largest gains for fields with many sources. The results are identical to those from previous versions.
Thread-Safe and Free-Threading-Compatible Aperture Photometry#
The new aperture photometry implementation releases the Python global interpreter lock (GIL) during the computation and uses no global state, making it safe to call concurrently from multiple threads. The compiled extensions are also declared compatible with free-threaded CPython builds, so threads can run the computation truly in parallel.
For example, photometry of many apertures on a large image can be parallelized across threads by splitting the source positions into chunks:
>>> from concurrent.futures import ThreadPoolExecutor
>>> import numpy as np
>>> from astropy.table import vstack
>>> from photutils.aperture import (CircularAperture,
... aperture_photometry)
>>> rng = np.random.default_rng(seed=0)
>>> data = rng.random((4096, 4096))
>>> positions = rng.uniform(0, 4096, (100_000, 2))
>>> n_workers = 10
>>> chunks = [CircularAperture(pos, r=5.0)
... for pos in np.array_split(positions, n_workers)]
>>> with ThreadPoolExecutor(max_workers=n_workers) as executor:
... tables = list(executor.map(
... lambda aper: aperture_photometry(data, aper), chunks))
>>> phot_table = vstack(tables)
>>> phot_table['id'] = np.arange(1, len(phot_table) + 1)
Note that the id column of each chunk’s table is numbered starting
from 1, so the last line renumbers the stacked id column to match
the input source order. Because executor.map returns results in
input order, the stacked table rows are in the same order as the input
positions.