Skip to content

Getting started

This guide walks you from install to your first reproducible dark frame.

Install

pip install getframes

The core install depends on NumPy, SciPy, and astropy (used for FITS I/O, WCS projection, and catalogs). For the plotting examples, add the examples extra, which pulls in matplotlib:

pip install "getframes[examples]"

Your first frame

import getframes as gf

cam = gf.Camera.from_preset("andor_ikon_m934")
frame = cam.dark_frame(exposure=60.0, temperature=-60.0, seed=0)

A Frame holds the pixel data and metadata:

frame.data        # numpy array of ADU, shape (height, width)
frame.shape       # (1024, 1024)
frame.stats()     # mean / median / std / min / max
frame.metadata    # how the frame was generated

Frame is array-like, so NumPy works directly:

import numpy as np
np.asarray(frame).mean()

Reproducibility

Pass seed= to get identical output every time:

a = cam.dark_frame(10.0, -20.0, seed=7)
b = cam.dark_frame(10.0, -20.0, seed=7)
assert (a.data == b.data).all()

Without a seed, the camera's internal random generator advances, giving a new frame each call.

Custom cameras

Not in the preset library? Describe the detector yourself with CameraConfig:

cam = gf.Camera(
    gf.CameraConfig(
        name="My Lab CMOS",
        sensor_type="CMOS",
        resolution=(2048, 2048),
        pixel_size_um=6.5,
        quantum_efficiency=0.82,
        full_well_e=30_000,
        bit_depth=12,
        gain_e_per_adu=0.8,
        bias_offset_adu=300,
        read_noise_e=1.8,
        dark_current_e_per_s=0.5,
    )
)

Building a master dark

Average a series to beat down the random noise:

import numpy as np

frames = cam.dark_series(exposure=10.0, n_frames=50, temperature=15.0, seed=0)
master = np.mean([np.asarray(f) for f in frames], axis=0)

Saving to FITS

With astropy installed:

frame.to_fits("dark.fits", overwrite=True)

The metadata is written to the FITS header where it fits the keyword constraints.

Beyond dark frames

Dark frames are the photon_rate = 0 special case of the general signal path. The same Camera also produces illuminated frames and rendered scenes:

# Drive the detector with an incident photon rate (scalar or per-pixel array):
flat = cam.expose(photon_rate=5_000.0, exposure=1.0, seed=0)
bias = cam.bias_frame(seed=0)

# Or render astronomical sources through a PSF and telescope:
scene = gf.Scene(
    shape=cam.resolution,
    optics=gf.Telescope(aperture_diameter_m=2.5, throughput=0.3,
                        plate_scale_arcsec_per_pixel=0.4, band=gf.Bandpass.johnson("V")),
    psf=gf.MoffatPSF(fwhm_arcsec=1.1, beta=3.0),
    sources=[gf.PointSource(x=512, y=512, magnitude=20.0)],
    sky=gf.Sky(surface_brightness_mag_arcsec2=21.0),
)
frame = cam.observe(scene, exposure=300.0, seed=0)

Every illuminated frame can carry the noise-free ground truth it was generated from (frame.truth), which is what makes getframes useful for validating a pipeline. See Observing scenes and Spectral mode for the full story.