Basic Monochrome MWIR Imager on a Drone

Computing the basic parameters of a monochrome imager on a drone. Valid for a single band.

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.

[11]:
# 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_drone_imager")
optics_file_path = file_directory.joinpath("optics.yaml")
detector_file_path = file_directory.joinpath("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 MWIR channel
band_id = "mwir"
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:

[13]:
from opticks import u

# positional params
# ---------------------
distance = 10.0 * u.km

print(f"target distance : {distance} ")
target distance : 10.0 km

Certain parameters vary with the light characteristics and therefore wavelength dependent. For a MWIR sensor, 3000 to 5000 nm can be selected.

Note the widely used bands:

  • blue: 450-485 nm

  • green: 500-565 nm

  • red: 625-750 nm

  • nir: 750-1400 nm

  • swir: 1400-3000 nm

  • mwir: 3000-8000 nm

[14]:
ref_wavelength = 4000 * u.nm

Extracting the Imager Parameters

Optical Parameters

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

[15]:
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: Sample Reflective Optics
focal length : 300.0 mm
aperture diameter : 130.0 mm
image diameter on focal plane : 12.3 mm

Derived Optical Params:
f-number : 2.308
full optical fov : 2.349 deg
aperture area : 132.732 cm2
spatial cut-off freq  : 108.33 cycle / mm (at 4000.0 nm)

Detector Parameters

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

[16]:
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()
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: Sample Full-Frame Detector
detector type : full frame
horizontal x vertical pixels (detector) : 1280 x 720
horizontal x vertical pixels (channel) : 1280 x 720
binning : None
pixel pitch : 8.0 um
TDI stages : None

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

Imager Geometry Parameters

The derived parameters are given below for the imager:

[17]:
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 : 1.528 mdeg
pixel solid angle : 7.11e-10 sr
horizontal full fov: 1.956 deg
vertical full fov: 1.1 deg

Geometric Projection Parameters

[ ]:
# image width and height assuming flat plate and constant Instantaneous FoV
image_width = chain.projected_horiz_img_extent(distance, band_id)
image_height = chain.projected_vert_img_extent(distance, band_id)


print(f"target distance : {distance}")
print(f"image width : {image_width:.4} (at target distance)")
print(f"image height : {image_height:.4} (at target distance)")
print()

# Spatial sample distance at distance
print(f"Spatial sample distance at {distance} target distance:")

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

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

ssd_centre_right = chain.spatial_sample_distance(
    distance, band_id, False, "centre right"
)
print(
    f"ssd right (unbinned): {ssd_centre_right.horiz:.4} , {ssd_centre_right.vert:.4} (horizontal, vertical at target distance)"
)
ssd_centre_top = chain.spatial_sample_distance(distance, band_id, False, "centre top")
print(
    f"ssd top (unbinned): {ssd_centre_top.horiz:.4} , {ssd_centre_top.vert:.4} (horizontal, vertical at target distance)"
)
ssd_corner = chain.spatial_sample_distance(distance, band_id, False, "corner")
print(
    f"ssd corner (unbinned): {ssd_corner.horiz:.4} , {ssd_corner.vert:.4} (horizontal, vertical at target distance)"
)

Timings

[19]:
print(f"frame rate :  {channel.frame_rate:.6} ({channel.frame_duration:.4})")
print(f"max integration duration : {channel.max_integration_duration:.4}")
print(f"actual integration duration : {channel.integration_duration:.4}")
frame rate :  29.97 Hz (33.37 ms)
max integration duration : 33.37 ms
actual integration duration : 13.34 ms

Read-out/Write Electronics

[20]:
print(
    f"pixel read rate : {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"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 : 27.6204 Mpix / s  (MWIR channel)
data write rate (uncompressed, incl. overheads) : 338.073 Mbit / s  (MWIR channel)
data write rate (compressed, incl. overheads) : 140.864 Mbit / s  (MWIR channel)