Basic Pushbroom Imager on a Satellite (Pléiades example)

Computing the basic parameters of the satellite pushbroom imager on the French high-res satellite Pléiades.

Sources for the Pléiades data:

Loading the Imager Parameters

An imager is made up of three parts: Optics, Detector and (optionally) Read-out/Write Electronics. We load the configuration files for each part and initialise the Imager object.

[ ]:
# If opticks import fails, try to locate the module
# This can happen building the docs
import os

try:
    import importlib.util

    if importlib.util.find_spec("opticks") is None:
        raise ModuleNotFoundError
except ModuleNotFoundError:
    os.chdir(os.path.join("..", ".."))
    os.getcwd()
[ ]:
from pathlib import Path

from opticks.imaging_model.detector import Channel
from opticks.imaging_model.imager import Imager
from opticks.imaging_model.imaging_chain import ImagingChain

print(f"current working directory: {Path.cwd()}")

file_directory = Path("docs", "examples", "sample_sat_pushbroom")
optics_file_path = file_directory.joinpath("optics.yaml")
detector_file_path = file_directory.joinpath("pan_detector.yaml")
rw_electronics_file_path = file_directory.joinpath("rw_electronics.yaml")

# check whether input files exist
print(
    f"optics file:  [{optics_file_path}] (file exists:  {optics_file_path.is_file()})"
)
print(
    f"detector file exists:  [{detector_file_path}] (file exists:  {detector_file_path.is_file()})"
)
print(
    f"RW electronics file exists:  [{rw_electronics_file_path}] (file exists:  {rw_electronics_file_path.is_file()})"
)

# Init imager object
imager = Imager.from_yaml_file(
    optics_file_path, detector_file_path, rw_electronics_file_path
)
chain = ImagingChain(imager)

# shorthands
optics = imager.optics
detector = imager.detector
rw_electronics = imager.rw_electronics

# select the PAN channel
band_id = "pan"
channel: Channel = detector.channels[band_id]

# binning status
binning_on = channel.is_binned

Setting the Scene

Reference parameters for the camera position and motion with respect to the target are given below:

[ ]:
import numpy as np

from opticks import u

# constants
# ---------
r_earth = 6378.137 * u.km
mu = 398600.5 * u.km**3 / u.s**2

# sat positional params
# ---------------------
sat_altitude = 694.0 * u.km

n = np.sqrt(mu / (r_earth + sat_altitude) ** 3)
ground_vel = (n * r_earth).to("m/s")

# converted to generic params
distance = sat_altitude
target_rel_velocity = ground_vel

print(f"target distance : {distance} ")
print(f"ground velocity : {target_rel_velocity:.6}")
target distance : 694.0 km
ground velocity : 6770.75 m / s

Certain parameters vary with the light characteristics and therefore wavelength dependent. Pléiades PAN works in the range 480 to 820 nm. We will use the centre frequency as the reference wavelength.

Note the widely used bands:

  • blue: 450-485 nm

  • green: 500-565 nm

  • red: 625-750 nm

  • nir: 750-1400 nm

[ ]:
ref_wavelength = channel.centre_wavelength

print(f"reference wavelength : {ref_wavelength}")
reference wavelength : 650.0 nm

Extracting the Imager Parameters

Optical Parameters

Basic and derived optical parameters are given below for the optics:

[ ]:
print("Basic Optical Params:")

print(f"optics id: {optics.name}")
print(f"focal length : {optics.focal_length}")
print(f"aperture diameter : {optics.aperture_diameter}")
print(f"image diameter on focal plane : {optics.image_diam_on_focal_plane}")

print()
print("Derived Optical Params:")

print(f"f-number : {optics.f_number:.4}")
print(f"full optical fov : {optics.full_optical_fov:.4}")
print(f"aperture area : {optics.aperture_area.to('cm**2'):.6}")
print(
    f"spatial cut-off freq  : {optics.spatial_cutoff_freq(ref_wavelength):.5} (at {ref_wavelength})"
)
Basic Optical Params:
optics id: Pléiades TMA Optics
focal length : 12905.0 mm
aperture diameter : 650.0 mm
image diameter on focal plane : 400.0 mm

Derived Optical Params:
f-number : 19.85
full optical fov : 1.776 deg
aperture area : 3318.31 cm2
spatial cut-off freq  : 77.489 cycle / mm (at 650.0 nm)

Detector Parameters

Basic and derived detector parameters are given below for the optics:

[ ]:
print("Basic Detector Params:")

print(f"detector id: {detector.name}")
print(f"detector type : {detector.detector_type}")
print(
    f"horizontal x vertical pixels (detector) : {detector.horizontal_pixels} x {detector.vertical_pixels}"
)
print(
    f"horizontal x vertical pixels (channel) : {channel.horizontal_pixels} x {channel.vertical_pixels}"
)
print(f"binning : {channel.binning if binning_on else 'None'}")
print(
    f"pixel pitch : {channel.pixel_pitch(False)} {f'({channel.pixel_pitch(True)} binned)' if binning_on else ''}"
)
print(f"TDI stages : {'None' if channel.tdi_stages == 1 else channel.tdi_stages}")
print(f"read blocks : {'None' if channel.read_blocks == 1 else channel.read_blocks}")

print()
print("Derived Detector Params:")

print(
    f"Nyquist freq : {channel.nyquist_freq(False):.4} {f'({channel.nyquist_freq(True):.4} binned)' if binning_on else ''}"
)
print(f"number of pixels (full frame) : {detector.pixel_count:.4}")
print(
    f"number of pixels (full frame, channel) : {channel.pixel_count_frame(False):.4} {f'({channel.pixel_count_frame(True):.4} binned)' if binning_on else ''}"
)
Basic Detector Params:
detector id: Pléiades PAN Detector
detector type : pushbroom
horizontal x vertical pixels (detector) : 30000 x 13
horizontal x vertical pixels (channel) : 30000 x 13
binning : None
pixel pitch : 13.0 um
TDI stages : 13
read blocks : None

Derived Detector Params:
Nyquist freq : 38.46 cycle / mm
number of pixels (full frame) : 0.39 Mpix
number of pixels (full frame, channel) : 0.39 Mpix

Imager Geometry Parameters

The derived parameters are given below for the imager:

[ ]:
print(
    f"ifov : {imager.ifov(band_id, False):.4} {f'({imager.ifov(band_id, True):.4} binned)' if binning_on else ''}"
)
print(
    f"pixel solid angle : {imager.pix_solid_angle(band_id, False):.4} {f'({imager.pix_solid_angle(band_id, True):.4} binned)' if binning_on else ''}"
)
print(f"horizontal full fov: {imager.horizontal_fov(band_id):.4}")
print(f"vertical full fov: {imager.vertical_fov(band_id):.4}")
ifov : 0.05771 mdeg
pixel solid angle : 1.015e-12 sr
horizontal full fov: 1.731 deg
vertical full fov: 0.0007503 deg

Geometric Projection Parameters

[ ]:
# swath assuming flat plate and constant Instantaneous FoV
swath = chain.projected_horiz_img_extent(distance, band_id)

print(f"image swath : {swath:.4} (disregarding Earth curvature)")

# Ground sample distance at Nadir
if binning_on:
    ssd_nadir_binned = chain.spatial_sample_distance(distance, band_id, True, "centre")
    print(
        f"ssd at nadir (binned): {ssd_nadir_binned.horiz:.4} , {ssd_nadir_binned.vert:.4} (horizontal, vertical at target distance)"
    )

ssd_nadir = chain.spatial_sample_distance(distance, band_id, False, "centre")
print(
    f"ssd at nadir (unbinned): {ssd_nadir.horiz:.4} , {ssd_nadir.vert:.4} (horizontal, vertical at target distance)"
)

Timings

[ ]:
print(
    f"line duration : {channel.frame_duration:.4} ({channel.frame_rate:.6}) (with binning where applicable)"
)
print(f"max integration duration : {channel.max_integration_duration:.4} (no binning)")
print(f"actual integration duration : {channel.integration_duration:.4} (no binning)")
print(
    f"total TDI column duration : {channel.total_tdi_col_duration:.4} ({channel.tdi_stages}x stages) (with binning where applicable)"
)
line duration : 0.1019 ms (9811.6 Hz) (with binning where applicable)
max integration duration : 0.1019 ms (no binning)
actual integration duration : 0.03 ms (no binning)
total TDI column duration : 1.325 ms (13x stages) (with binning where applicable)

Read-out/Write Electronics

[11]:
print(
    f"pixel read rate (without TDI) : {channel.pix_read_rate(channel.frame_rate, False, False):.6} {f'({channel.pix_read_rate(channel.frame_rate, True, False):.6} binned)' if binning_on else ''} ({channel.name} channel)"
)
print(
    f"pixel read rate (with TDI) : {channel.pix_read_rate(channel.frame_rate, False, True):.6} {f'({detector.pix_read_rate(channel.frame_rate, True, True):.6} binned)' if binning_on else ''} ({channel.name} channel)"
)

print(
    f"data write rate (uncompressed, incl. overheads) : {imager.data_write_rate(band_id, False, False):.6} {f'({imager.data_write_rate(band_id, True, False):.6} binned)' if binning_on else ''} ({channel.name} channel)"
)
print(
    f"data write rate (compressed, incl. overheads) : {imager.data_write_rate(band_id, False, True):.6} {f'({imager.data_write_rate(band_id, True, True):.6} binned)' if binning_on else ''} ({channel.name} channel)"
)
pixel read rate (without TDI) : 294.348 Mpix / s  (PAN channel)
pixel read rate (with TDI) : 3826.52 Mpix / s  (PAN channel)
data write rate (uncompressed, incl. overheads) : 3638.14 Mbit / s  (PAN channel)
data write rate (compressed, incl. overheads) : 909.535 Mbit / s  (PAN channel)

References

  1. Design of the high resolution optical instrument for the Pleiades HR Earth observation satellites