Skip to main content

astroapers

astroapers aperture logo astroapers wordmark

astroapers (aap in short) is a Rust-backed Python package for exact aperture overlap, bbox-tight weights, and aperture summation in pixel coordinates.

Key Concepts

Each concept below has a one-line summary. Click Details to see more.

Aperture (class PixelAp): An aperture is defined by a single closed-curve geometry.
ap = aap.CircAp((x, y), r)

Annulus (class PixelAp): An annulus is a special kind of aperture, defined by two closed-curve geometries: an inner one and an outer one.
an = aap.CircAn((x, y), r_in, r_out)

Bounding Box (class BoundingBox): The smallest rectangle that contains the aperture.
bboxes = ap.bboxes()

  • General users will not need to access this at all.
  • Defined using four ints: (ixmin, ixmax, iymin, iymax).
  • It is a small helper to reduce calculation time and memory usage in aap by working only with the pixels relevant to the aperture, instead of the full image.
  • Use bbox.to_fits_section(shape) for DS9 or IRAF-style FITS image section ([x1:x2,y1:y2], 1-indexed).

Weights (class list[ndarray]): One bbox-tight array per aperture position, holding the exact fraction (0 to 1) of each pixel covered by the aperture.
weights = ap.weights_exact()

  • In real usage, users generally won’t need it directly because the aperture methods (especially apsum) will use them internally.
  • weights_center() returns bbox-tight binary weights: 1 if the pixel center is inside the aperture, otherwise 0.
  • Use exact weights for flux sums and effective area. Use center weights when you want a simple pixel sample, such as background values in an annulus.
  • It is bbox-tight, so to get the full image-shaped mask, use ap.bboxes()[i].to_image(ap.weights_exact()[i], shape).

Sampled values (class list[ndarray]): Raw, unweighted image values selected by weights_center().
samples = ap.sampled_values(image)

  • samples, pix = ap.sampled_values(image, return_pix=True) also returns absolute pixel-center coordinate arrays aligned with the sampled values. For 2-D apertures, each coordinate tuple is (yy, xx); subtract the aperture center to get dy/dx, and use np.hypot(dx, dy) for Euclidean distance.
  • By default, it returns one 1-d array per aperture position, even for a scalar aperture.
  • Use flat=True for one concatenated array plus offsets (see documentation for details).
  • Expected usage: ann.sampled_values() for custom statistics such as median, sigma clipping, or robust background estimation.

Weighted values (class list[ndarray]): Image values multiplied by weights_exact(), for positive-overlap pixels.
weighted = ap.weighted_values(image)

  • It is not designed for end users except for deep investigation/debugging.
  • By default, it returns one 1-d array per aperture position, even for a scalar aperture.
  • Use flat=True for one concatenated array plus offsets (see documentation for details).

Aperture sum (class float or 1-d array): It internally combines bbox-tight weights with the matching BoundingBox to calculate the sum, which is memory- and computation-efficient.
apsum, npix = ap.apsum_exact(image)

Area (class float): Area is the analytic geometric area of the aperture, e.g., \(\pi r^2\) for a circle.
area = ap.area

Effective area (class float or 1-d array): It is the sum of weights after excluding pixels outside the image and/or masked pixels. Therefore, npix \(\le\) ap.area.
npix = ap.npix_exact(shape, mask=...)

  • The image shape is required to correctly calculate npix (e.g., to exclude pixels outside the image). Optionally, a mask can also be passed to exclude masked pixels. Users may discard objects by badphot = npix/area < criterion (e.g., objects near the image edge or affected by bad pixel mask, bpm).

Source sum (called srcsum in the tutorials): The background-subtracted source flux.
srcsum = apsum - background * npix

  • Since background estimation may require complicated or custom algorithms, it is not a built-in method in aap. Instead, users are expected to do: bkg = background(an.sampled_values(image)[0], **kwargs) and then compute ap.apsum_exact(image)[0] - bkg * ap.npix_exact(image.shape).

First-Look at aap

The object layer is usually the clearest way to express a measurement. The raw Rust extension, imported as aapr, is the expert performance layer for fixed workflows where source coordinates already live in C-contiguous arrays.

import numpy as np
import astroapers as aap
import astroapers._rust as aapr

data = np.ones((16, 16), dtype=float)
xpos = np.ascontiguousarray([8.0], dtype=np.float64)
ypos = np.ascontiguousarray([8.0], dtype=np.float64)
raw_data = np.ascontiguousarray(data, dtype=np.float64)

ap = aap.CircAp((xpos[0], ypos[0]), r=3.0)
obj_apsum, obj_npix = ap.apsum_exact(data)

# Raw Rust call: no Python validation, masks, or result shaping.
apsum = aapr.apsum_circ_exact_sum(raw_data, xpos, ypos, 3.0)

assert np.allclose(apsum[0], obj_apsum)
apsum
[28.274333882308134]

Coordinates follow the SEP/Photutils pixel convention: pixel (x, y) is centered at integer coordinates and covers [x - 0.5, x + 0.5].

Apertures own a read-only float64 copy of their centers, including with validate=False. Editing the constructor input leaves the aperture unchanged; ap.positions cannot be edited or reassigned. Construct a new aperture to move it, for example aap.CircAp((9.0, 8.0), r=ap.r). See construction and repeated measurements for batching and reuse guidance.

For already valid inputs, validate=False can make aperture construction roughly twice as fast in some cases, although construction already takes microseconds in local CircAp measurements: 1.66 µs → 0.75 µs for a single center. The gain depends on the input and machine; total photometry speedups can be smaller. The performance guide above gives the timing context.

This site has these documentation sections:

  • Aperture gallery, a visual catalog of built-in apertures, annuli, and custom PathAp line/circular-arc geometries.
  • Performance, guidance for raw Rust calls, dtype behavior, and parallel threshold tuning.
  • Benchmarks, a small reproducible benchmark table.
  • API reference, generated from the package docstrings.
  • Rust API, a landing page for the generated rustdoc output.
  • Tutorials, written as runnable Quarto documents.

Tutorial order

  1. Quickstart aperture sums: create a synthetic image and measure circular aperture flux.
  2. Bbox-tight weights: inspect ap.weights_exact(), ap.bboxes(), BoundingBox.to_image(), and BoundingBox.to_fits_section().
  3. Annulus background: estimate a local background from an annulus and subtract it from source flux.
  4. Multiple apertures and radial profiles: measure several apertures, check edge coverage, and sample multiple radii.
  5. Custom weights and composition: combine bbox-tight weights and use externally supplied masks.
  6. Different inner and outer annulus shapes: use rectangular and elliptical annuli whose inner and outer boundaries do not need to be scaled copies of the same shape.
  7. Wedge aperture sums for jet structures: measure an asymmetric comet jet with annular wedge apertures and raw Rust weights.
  8. PathAp custom line and arc apertures: define custom closed apertures from straight lines, circular arcs, and explicit holes.
  9. Isophote fitting: ELLIPSE analysis: fit ellipticity and position-angle profiles with photutils.isophote (plus a Sérsic fit with astropy.modeling), then measure exact curves of growth (EllipAp) and the light between neighbouring isophotes (split-geometry EllipAn).

Dtype caveats

Geometry calculations and public aperture-sum results are float64. The raw astroapers._rust functions, conventionally imported as aapr, are the maximum-performance interface: they expect C-contiguous arrays with the dtype matching the function name, such as apsum_circ_exact_sum_f32 for float32 data. For additional raw-call patterns, inspect astroapers.kernels; it is the Python layer that calls _rust internally.

Bbox-tight weights from PixelAp.weights_exact() are float64. User-supplied weights passed to BoundingBox methods preserve only float32 and float64. Other numeric or boolean weight dtypes, including extended precision dtypes such as float128/longdouble where available, are converted to float64. Array-producing helpers such as BoundingBox.to_image(), weighted_cutout(), and weighted_values() preserve float32 when both data and weights are float32, but BoundingBox.apsum() and BoundingBox.npix() always accumulate in float64.

Bad-pixel arrays passed as mask= are converted to boolean, with True meaning excluded.

Rust Crate Use

astroapers is also usable as a Rust crate. The public Rust aperture helpers do not depend on PyO3 or NumPy unless the Python extension feature is enabled.

[dependencies]
astroapers = "<version>"

Replace <version> with the release version you want to depend on.

use astroapers::{circle_bbox, circle_exact_weight_at_pixel};

let bbox = circle_bbox(8.0, 8.0, 3.0);
let mut area = 0.0;

for y in bbox.y_range() {
    for x in bbox.x_range() {
        area += circle_exact_weight_at_pixel(8.0, 8.0, 3.0, x, y);
    }
}

assert!((area - std::f64::consts::PI * 9.0).abs() < 1.0e-12);

The current Rust-facing API exposes bbox-tight extents and exact pixel weights for circles, ellipses, and rectangles. Build local Rust API docs with:

cargo doc --no-default-features --open