Sensors Module

The sensors module provides classes for representing sensor metadata and calibration data.

Overview

Sensor information is crucial for:

  • Coordinate transformations

  • Radiometric calibration

  • Geometric corrections

  • Geolocation accuracy

VISTA supports both continuous sensor models and sampled sensor data.

Core Classes

sensor.Sensor

Base class for sensor position, line-of-sight, geodetic conversion, and radiometric modeling

sampled_sensor.SampledSensor

Sensor implementation using sampled position data with interpolation/extrapolation.

Module Reference

Sensor

Base sensor class for position, line-of-sight, geodetic conversion, and radiometric modeling.

This module defines the Sensor base class which provides a framework for representing sensor platforms and their characteristics. Sensors support position queries, geodetic coordinate conversion, radiometric calibration data (bias, gain, bad pixels), and optional point spread function modeling.

class vista.sensors.sensor.Sensor(name, bias_images=None, bias_image_frames=None, uniformity_gain_images=None, uniformity_gain_image_frames=None, bad_pixel_masks=None, bad_pixel_mask_frames=None, _instance_count=0)[source]

Bases: object

Base class for sensor position, line-of-sight, geodetic conversion, and radiometric modeling

The Sensor class provides a framework for representing sensor platforms and their associated characteristics including projection polynomials and radiometric calibration data.

name

Unique name for this sensor. Used as the primary identifier.

Type:

str

bias_images

3D array of bias/dark frames with shape (num_bias_images, height, width).

Type:

NDArray, optional

bias_image_frames

1D array specifying frame ranges for each bias image.

Type:

NDArray, optional

uniformity_gain_images

3D array of flat-field/gain correction images.

Type:

NDArray, optional

uniformity_gain_image_frames

1D array specifying frame ranges for each uniformity gain image.

Type:

NDArray, optional

bad_pixel_masks

3D array of bad pixel masks.

Type:

NDArray, optional

bad_pixel_mask_frames

1D array specifying frame ranges for each bad pixel mask.

Type:

NDArray, optional

Notes

  • All positions are in Earth-Centered Earth-Fixed (ECEF) Cartesian coordinates

  • Position units are kilometers

  • Positions are represented as (3, N) arrays with x, y, z in each column

  • PSF modeling is optional and can be used for fitting signal blobs to estimate irradiance

  • Sensor names must be unique within a VISTA session

  • Class variable _instance_count tracks the total number of Sensor instances created

Examples

>>> # Subclass can implement get_positions
>>> class MySensor(Sensor):
...     def get_positions(self, times):
...         # Return sensor positions for given times
...         return np.array([[x1, x2], [y1, y2], [z1, z2]])
name: str
bias_images: ndarray[tuple[Any, ...], dtype[_ScalarT]] | None = None
bias_image_frames: ndarray[tuple[Any, ...], dtype[_ScalarT]] | None = None
uniformity_gain_images: ndarray[tuple[Any, ...], dtype[_ScalarT]] | None = None
uniformity_gain_image_frames: ndarray[tuple[Any, ...], dtype[_ScalarT]] | None = None
bad_pixel_masks: ndarray[tuple[Any, ...], dtype[_ScalarT]] | None = None
bad_pixel_mask_frames: ndarray[tuple[Any, ...], dtype[_ScalarT]] | None = None
uuid: str = None
__post_init__()[source]

Initialize sensor instance and increment global counter.

Generates a unique UUID for this sensor instance and initializes internal data structures for tracking associated imagery.

__eq__(other)[source]

Compare Sensors based on UUID

get_imagery_frames_and_times()[source]

Get all unique imagery frames and corresponding times in increasing order.

Returns:

  • frames (NDArray[np.int_]) – Sorted array of unique frame numbers

  • times (NDArray[np.datetime64]) – Sorted array of corresponding timestamps

Return type:

Tuple[ndarray[tuple[Any, …], dtype[_ScalarT]], ndarray[tuple[Any, …], dtype[_ScalarT]]]

Notes

This method aggregates frames and times from all imagery objects that have been registered with this sensor via add_imagery().

add_imagery(imagery)[source]

Register imagery with this sensor and update frame/time tracking.

Adds the imagery’s frames and times to the sensor’s internal registry for coordinate conversion and time-to-frame mapping. Duplicate imagery (by UUID) or imagery without times is ignored.

Parameters:

imagery (Imagery) – Imagery object to register with this sensor

Notes

  • Only imagery with non-None times are tracked

  • Duplicate frame/time pairs are automatically removed

  • This method is typically called automatically when Imagery is created

get_positions(times)[source]

Return sensor positions in Cartesian ECEF coordinates for given times.

Parameters:

times (NDArray[np.datetime64]) – Array of times for which to retrieve sensor positions

Returns:

Sensor positions as (3, N) array where N is the number of times. Each column contains [x, y, z] coordinates in ECEF frame (km).

Return type:

NDArray[np.float64]

Notes

The default implementation returns None. Subclasses should override this method

model_psf(sigma=None, size=None)[source]

Model the sensor’s point spread function (PSF).

This is an optional method that can be overridden by subclasses to provide PSF modeling capability. The PSF can be used to fit signal pixel blobs in imagery to estimate irradiance.

Parameters:
  • sigma (float, optional) – Standard deviation parameter for PSF modeling (e.g., Gaussian width)

  • size (int, optional) – Size of the PSF kernel to generate

Returns:

2D array representing the point spread function, or None if not implemented

Return type:

NDArray[np.float64] or None

Notes

The default implementation returns None. Subclasses should override this method to provide specific PSF models (e.g., Gaussian, Airy disk, etc.).

can_geolocate()[source]

Check if sensor can convert pixels to geodetic coordiantes and vice versa.

Notes

The default implementation returns False. Subclasses can override this method.

Returns:

True if sensor has both forward and reverse geolocation polynomials.

Return type:

bool

can_correct_bad_pixel()[source]

Check if sensor has radiometric bad pixel masks.

Returns:

True if sensor has radiometric bad pixel masks.

Return type:

bool

can_correct_bias()[source]

Check if sensor has bias images

Returns:

True if sensor has bias images.

Return type:

bool

can_correct_non_uniformity()[source]

Check if sensor has uniformity gain images

Returns:

True if sensor has uniformity gain images.

Return type:

bool

geodetic_to_pixel(frame, loc)[source]

Convert geodetic coordinates to pixel coordinates using polynomial coefficients.

Parameters:
  • frame (int) – Frame number for which to perform the conversion

  • loc (EarthLocation) – Astropy EarthLocation object(s) containing geodetic coordinates

Returns:

  • rows (np.ndarray) – Array of row pixel coordinates (NaN for base implementation)

  • columns (np.ndarray) – Array of column pixel coordinates (NaN for base implementation)

Return type:

Tuple[ndarray, ndarray]

Notes

The default implementation returns NaN arrays. Subclasses should override this method to provide geodetic-to-pixel conversion using their specific projection model (e.g., polynomial coefficients).

pixel_to_geodetic(frame, rows, columns)[source]

Convert pixel coordinates to geodetic coordinates using polynomial coefficients.

Parameters:
  • frame (int) – Frame number for which to perform the conversion

  • rows (np.ndarray) – Array of row pixel coordinates

  • columns (np.ndarray) – Array of column pixel coordinates

Returns:

Astropy EarthLocation object(s) with geodetic coordinates (zeros for base implementation)

Return type:

EarthLocation

Notes

The default implementation returns EarthLocation with zero coordinates. Subclasses should override this method to provide pixel-to-geodetic conversion using their specific projection model.

to_hdf5(group)[source]

Save sensor radiometric calibration data to an HDF5 group.

Parameters:

group (h5py.Group) – HDF5 group to write sensor data to (typically sensors/<sensor_uuid>/)

Notes

This method writes radiometric calibration data to the HDF5 group: - bias_images and bias_image_frames - uniformity_gain_images and uniformity_gain_image_frames - bad_pixel_masks and bad_pixel_mask_frames

Subclasses should call super().to_hdf5(group) and then add their own data.

__init__(name, bias_images=None, bias_image_frames=None, uniformity_gain_images=None, uniformity_gain_image_frames=None, bad_pixel_masks=None, bad_pixel_mask_frames=None, _instance_count=0)

Sampled Sensor

Sampled sensor with interpolated position and geodetic conversion capabilities.

This module defines the SampledSensor class, which extends the base Sensor class to provide position retrieval via interpolation/extrapolation from discrete position samples. It also supports geodetic coordinate conversion using ARF (Attitude Reference Frame) polynomials and radiometric gain calibration.

class vista.sensors.sampled_sensor.SampledSensor(name, bias_images=None, bias_image_frames=None, uniformity_gain_images=None, uniformity_gain_image_frames=None, bad_pixel_masks=None, bad_pixel_mask_frames=None, _instance_count=0, positions=None, times=None, frames=None, radiometric_gain=None, pointing=None, poly_pixel_to_arf_azimuth=None, poly_pixel_to_arf_elevation=None, poly_arf_to_row=None, poly_arf_to_col=None)[source]

Bases: Sensor

Sensor implementation using sampled position data with interpolation/extrapolation.

SampledSensor stores discrete position samples at known times and provides position estimates at arbitrary times through interpolation (within the time range) or extrapolation (outside the time range). For single-position sensors, the same position is returned for all query times.

positions

Sensor positions as (3, N) array where N is the number of samples. Each column contains [x, y, z] ECEF coordinates in kilometers. Required - will raise ValueError in __post_init__ if not provided.

Type:

NDArray[np.float64]

times

Times corresponding to each position sample. Must have length N. Required - will raise ValueError in __post_init__ if not provided.

Type:

NDArray[np.datetime64]

frames

Sensor frames numbers corresponding to each time sample. Must have length N. Required - will raise ValueError in __post_init__ if not provided.

Type:

NDArray[np.int64]

radiometric_gain

1D array of multiplicative factors for each frame to convert from counts to irradiance in units of kW/km²/sr.

Type:

NDArray, optional

pointing

Sensor pointing unit vectors in ECEF coordinates. Shape: (3, num_frames). Each column is the direction the sensor is pointing for that frame.

Type:

NDArray[np.float64], optional

poly_pixel_to_arf_azimuth

Polynomial coefficients for converting (column, row) to ARF azimuth (radians). Shape: (num_frames, num_coeffs) where num_coeffs depends on polynomial order.

Type:

NDArray[np.float64], optional

poly_pixel_to_arf_elevation

Polynomial coefficients for converting (column, row) to ARF elevation (radians). Shape: (num_frames, num_coeffs) where num_coeffs depends on polynomial order.

Type:

NDArray[np.float64], optional

poly_arf_to_row

Polynomial coefficients for converting (azimuth, elevation) to row. Shape: (num_frames, num_coeffs) where num_coeffs depends on polynomial order.

Type:

NDArray[np.float64], optional

poly_arf_to_col

Polynomial coefficients for converting (azimuth, elevation) to column. Shape: (num_frames, num_coeffs) where num_coeffs depends on polynomial order.

Type:

NDArray[np.float64], optional

get_positions(times)[source]

Return interpolated/extrapolated sensor positions for given times

Notes

  • Duplicate times in the input are automatically removed during initialization

  • For 2+ unique samples: uses linear interpolation within range, linear extrapolation outside

  • For 1 sample: returns the same position for all query times (stationary sensor)

  • Positions must be (3, N) arrays with x, y, z in each column

  • All coordinates are in ECEF Cartesian frame with units of kilometers

  • ARF (Attitude Reference Frame) is a local coordinate system where the X-axis points along the sensor pointing direction

Examples

>>> import numpy as np
>>> # Create sensor with multiple position samples
>>> positions = np.array([[1000, 1100, 1200],
...                       [2000, 2100, 2200],
...                       [3000, 3100, 3200]])  # (3, 3) array
>>> times = np.array(['2024-01-01T00:00:00',
...                   '2024-01-01T00:01:00',
...                   '2024-01-01T00:02:00'], dtype='datetime64')
>>> sensor = SampledSensor(positions=positions, times=times)
>>> # Get interpolated position
>>> query_times = np.array(['2024-01-01T00:00:30'], dtype='datetime64')
>>> pos = sensor.get_positions(query_times)
>>> pos.shape
(3, 1)
>>> # Create stationary sensor with single position
>>> positions_static = np.array([[1000], [2000], [3000]])  # (3, 1) array
>>> times_static = np.array(['2024-01-01T00:00:00'], dtype='datetime64')
>>> sensor_static = SampledSensor(positions=positions_static, times=times_static)
>>> # Returns same position for any query time
>>> pos = sensor_static.get_positions(query_times)
positions: ndarray[tuple[Any, ...], dtype[float64]] | None = None
times: ndarray[tuple[Any, ...], dtype[datetime64]] | None = None
frames: ndarray[tuple[Any, ...], dtype[int64]] | None = None
radiometric_gain: ndarray[tuple[Any, ...], dtype[_ScalarT]] | None = None
pointing: ndarray[tuple[Any, ...], dtype[float64]] | None = None
poly_pixel_to_arf_azimuth: ndarray[tuple[Any, ...], dtype[float64]] | None = None
poly_pixel_to_arf_elevation: ndarray[tuple[Any, ...], dtype[float64]] | None = None
poly_arf_to_row: ndarray[tuple[Any, ...], dtype[float64]] | None = None
poly_arf_to_col: ndarray[tuple[Any, ...], dtype[float64]] | None = None
__post_init__()[source]

Validate inputs and remove duplicate times.

Ensures positions and times have compatible shapes and removes any duplicate time entries along with their corresponding positions.

Raises:

ValueError – If positions or times are not provided, or if they have incompatible shapes.

can_geolocate()[source]

Check if sensor can convert pixels to geodetic coordinates and vice versa.

Returns:

True if sensor has all required ARF geolocation data: pointing vectors and both forward (pixel→ARF) and reverse (ARF→pixel) polynomials.

Return type:

bool

get_positions(times)[source]

Return sensor positions for given times via interpolation/extrapolation.

Parameters:

times (NDArray[np.datetime64]) – Array of times for which to retrieve sensor positions

Returns:

Sensor positions as (3, N) array where N is the number of query times. Each column contains [x, y, z] coordinates in ECEF frame (km).

Return type:

NDArray[np.float64]

Notes

  • For sensors with 1 sample: returns the single position for all times

  • For sensors with 2+ samples: uses linear interpolation within the time range and linear extrapolation outside the range

pixel_to_geodetic(frame, rows, columns)[source]

Convert pixel coordinates to geodetic coordinates using ARF polynomials.

Uses ARF (Attitude Reference Frame) polynomials to map (row, column) pixel coordinates to geodetic coordinates by ray-casting to the Earth’s surface. Pixels that do not intersect Earth will have NaN coordinates.

Parameters:
  • frame (int) – Frame number for which to perform the conversion

  • rows (np.ndarray) – Array of row pixel coordinates

  • columns (np.ndarray) – Array of column pixel coordinates

Returns:

Astropy EarthLocation object(s) with geodetic coordinates. Returns NaN coordinates for pixels that do not intersect Earth. Returns zero coordinates if polynomials are not available or frame not found.

Return type:

EarthLocation

Notes

  • Requires ARF polynomials and pointing vectors to be defined

  • Frame must exist in self.frames array

  • Off-Earth pixels will have NaN lat/lon/height values

geodetic_to_pixel(frame, loc)[source]

Convert geodetic coordinates to pixel coordinates using ARF polynomials.

Uses ARF (Attitude Reference Frame) polynomials to map geodetic coordinates (latitude, longitude, altitude) to (row, column) pixel coordinates. This method properly handles targets at any altitude, not just ground level.

Parameters:
  • frame (int) – Frame number for which to perform the conversion

  • loc (EarthLocation) – Astropy EarthLocation object(s) containing geodetic coordinates

Returns:

  • rows (np.ndarray) – Array of row pixel coordinates (zeros if polynomials unavailable)

  • columns (np.ndarray) – Array of column pixel coordinates (zeros if polynomials unavailable)

Return type:

Tuple[ndarray, ndarray]

Notes

  • Requires ARF polynomials and pointing vectors to be defined

  • Frame must exist in self.frames array

  • Returns zero coordinates if polynomials are not available or frame not found

  • Properly handles targets at any altitude (not limited to ground level)

to_hdf5(group)[source]

Save sampled sensor data to an HDF5 group.

Parameters:

group (h5py.Group) – HDF5 group to write sensor data to (typically sensors/<sensor_name>/)

Notes

This method extends the base Sensor.to_hdf5() by adding: - Position data (positions, times) in position/ subgroup - Geolocation polynomials in geolocation/ subgroup - Radiometric gain values in radiometric/ subgroup

__init__(name, bias_images=None, bias_image_frames=None, uniformity_gain_images=None, uniformity_gain_image_frames=None, bad_pixel_masks=None, bad_pixel_mask_frames=None, _instance_count=0, positions=None, times=None, frames=None, radiometric_gain=None, pointing=None, poly_pixel_to_arf_azimuth=None, poly_pixel_to_arf_elevation=None, poly_arf_to_row=None, poly_arf_to_col=None)
name: str

Basic Usage

Creating a Sensor

from vista.sensors import Sensor

# Create a sensor with calibration data
sensor = Sensor(
    name="My Sensor",
    focal_length=50.0,
    pixel_size=5.0e-6,
    width=1024,
    height=768
)

Using Sensor Data

# Access sensor properties
print(f"Focal length: {sensor.focal_length} mm")
print(f"Field of view: {sensor.fov} degrees")

# Use sensor for transformations
from vista.transforms import sensor_to_ground
ground_point = sensor_to_ground(pixel_x, pixel_y, sensor)