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

  • Aperture (class PixelAp):
    • ap = aap.CircAp((x, y), r)
    • An aperture is defined by a single closed-curve geometry.
  • Annulus (class PixelAp):
    • an = aap.CircAn((x, y), r_in, r_out)
    • An annulus is a special kind of aperture, defined by two closed-curve geometries: an inner one and an outer one.
  • Bounding Box (class BoundingBox):
    • bboxes = ap.bboxes().
    • General users will not need to access this at all.
    • The smallest rectangle that contains the aperture.
    • 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])
    • In real usage, users generally won’t need it directly because the aperture methods (especially apsum) will use them internally.
    • weights = ap.weights_exact() returns one bbox-tight fractional-overlap array per aperture position.
    • weights_exact() returns values between 0 and 1: the exact fraction of each pixel covered by the aperture.
    • 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 and weighted values (class list[ndarray]):
    • samples = ap.sampled_values(image) returns raw, unweighted image values selected by weights_center().
    • 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.
    • weighted = ap.weighted_values(image) is not designed for end users except for deep investigation/degugging. It returns image * weights_exact() values for positive-overlap pixels.
    • By default, both methods return 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.
  • Aperture sum (class float or 1-d array):
    • apsum = ap.apsum_exact(image).
    • It internally combines bbox-tight weights with the matching BoundingBox to calculate the sum, which is memory- and computation-efficient.
  • Area (class float):
    • area = ap.area.
    • Area is the analytic geometric area of the aperture, e.g., \(\pi r^2\) for a circle.
  • Effective area (class float or 1-d array):
    • npix = ap.npix_exact(shape, mask=...).
    • It is the sum of weights after excluding pixels outside the image and/or masked pixels. Therefore, npix \(\le\) ap.area.
    • 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.
    • It is the background-subtracted source flux, calculated as 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.

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

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. Mixed 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 comet jets: 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.

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.