Skip to content

Coordinate Conventions

Camera Frame

tetra3rs uses a right-handed camera frame:

  • +X — right
  • +Y — down
  • +Z — boresight (into the scene)

Pixel Coordinates

All pixel coordinates use the geometric image center as the origin.

Pixel centers sit at integer indices (a single bright pixel at column c, row r produces a centroid at x_px = c, y_px = r before centering), so for an image W pixels wide and H pixels tall the geometric center is at

\[ (c_x,\;c_y) = \left(\frac{W-1}{2},\;\frac{H-1}{2}\right), \]

and the centered coordinates handed to / returned by the solver are x = c − (W−1)/2, y = r − (H−1)/2:

  • x = 0, y = 0 is the geometric center of the image.
  • For even dimensions this is the intersection of the four central pixels (e.g. for W = 3840, c_x = 1919.5, between columns 1919 and 1920).
  • For odd dimensions it is the center of the single middle pixel.
  • +X is to the right, +Y is downward.

This matches the geometric-center convention used by FITS WCS, astropy, astrometry.net, OpenCV, and scikit-image (FITS' 1-indexed center (W+1)/2 is the same point as the 0-indexed (W−1)/2), so a crpix taken from any of those tools can be used directly.

This applies to:

  • Input centroids (both Centroid objects and numpy arrays)
  • SolveResult.pixel_to_world() / world_to_pixel() inputs and outputs
  • ExtractionResult.centroids returned by extract_centroids() and extract_centroids_fast()

Centered origin — not a corner origin

tetra3rs always uses a center origin: the boresight, the gnomonic projection, and the distortion field are all symmetric about the image center, so that is the natural reference for pointing geometry. This differs from image-processing libraries (numpy, OpenCV indexing), which use a top-left corner origin with the first pixel center at (0, 0) because it suits raster array access, not pointing.

You only deal with the corner convention at the input boundary: if your centroids are corner-indexed (e.g. raw detector row/column), subtract ((W−1)/2, (H−1)/2) to convert them to the center origin before passing them to the solver. extract_centroids() / extract_centroids_fast() already return center-origin coordinates.

Changed in 0.8.0

Earlier versions placed the origin at (W/2, H/2) — a half-pixel right of and below the geometric center. 0.8.0 moved it to ((W−1)/2, (H−1)/2) to match the FITS / astropy / OpenCV convention. This removes a ~½-pixel bias when comparing tetra3rs solutions against those tools (it was internally consistent, so it never biased tetra3rs-only solves). See issue #28.

CRPIX — projection reference offset

crpix in CameraModel shifts the projection origin away from the geometric image center:

  • crpix = [0, 0] (default) puts the projection origin at the geometric center, so the boresight (+Z) is the sky direction imaged at the center pixel.
  • crpix = [dx, dy] moves the projection origin by (dx, dy) pixels; the solver subtracts crpix from each pixel coordinate before projecting.

crpix is not the distortion center

crpix is the tangent-plane projection origin. The optical axis — where lens (radial/tangential) distortion is physically centered — is a separate quantity, carried by RadialDistortion::center. On mosaic cameras (TESS, Kepler, …) the optical axis can lie far off the detector (near a CCD corner), while crpix stays near the image center so the solver's FOV-radius queries and pattern geometry remain valid. See the camera model page.

Sky Coordinates (ICRS)

Solved attitudes are in the International Celestial Reference System (ICRS):

  • RA (Right Ascension) — in degrees, range [0°, 360°)
  • Dec (Declination) — in degrees, range [−90°, +90°]
  • Roll — position angle of camera +Y measured East of North, in degrees

Rotation Representation

The solved attitude is available as:

  • A 3×3 rotation matrix (rotation_matrix_icrs_to_camera) mapping ICRS unit vectors to camera-frame unit vectors.
  • Decomposed RA, Dec, Roll angles.
  • In the Rust API, a UnitQuaternion (qicrs2cam).

What the attitude points at

The solved attitude's boresight — camera +Z, equivalently the reported RA/Dec — is the sky direction that images onto the projection origin: the center pixel ((W−1)/2, (H−1)/2) when crpix = [0, 0], or the crpix location when set.

Center pixel, not the optical boresight

+Z points at the sky direction landing on the center pixel, not at the camera's optical/lens axis. On a real camera the optical axis (the distortion center, RadialDistortion::center) generally falls on a different pixel, so it corresponds to a slightly different sky direction. tetra3rs always reports the attitude of the center-pixel boresight; if you need the pointing of the optical axis, project that pixel through pixel_to_world().