Skip to main content

Basic models and reader for Capella SLCs

Project description

capella-reader

Actions Status PyPI version

Conda-Forge GitHub Discussion

Package for creating typed Python models from Capella Single Look Complex (SLC) GeoTIFF images and JSON metadata.

Capella embeds product metadata in the TIFF tags of its SLC GeoTIFF files[^1]; this package provides utilities for reading and working with that metadata based on the Capella Product Specification version 1.8. Note that the JSON can be parsed with Python's built-in json module; however, this package provides typed models with validation and descriptions for each field for easier integration with other SAR processing libraries. Additionally, this package includes an ISCE3 adapter via the adapters.isce3 subpackage for working with the ISCE3 processing library.

Example datasets can be found on the Capella Open Data catalog.

[^1]: To work with other formats, such as CPHD and SICD, the sarpy library is recommended.

Installation

pip install capella-reader

Quick Start

from capella_reader import CapellaSLC

# Load a Capella SLC GeoTIFF
slc = CapellaSLC.from_file("tests/data/CAPELLA_C14_SM_SLC_HH_20240626150051_20240626150055.tif")

# Access metadata
print(slc.shape)                              # (rows, cols)
print(slc.meta.collect.platform)              # 'capella-14'
print(slc.meta.collect.image.center_pixel.incidence_angle)

Loading Metadata from JSON

You can also load metadata directly from extended metadata JSON files:

from pathlib import Path
from capella_reader import CapellaSLC, CapellaSLCMetadata

md_file = "CAPELLA_C13_SP_SLC_HH_20241126045307_20241126045346_extended.json"
slc = CapellaSLC.from_file(md_file)

# Or from a Python dict
import json
data = json.loads(Path(md_file).read_text())
meta = CapellaSLCMetadata.model_validate(data)

# Round-trip: dump back to JSON
json_str = meta.model_dump_json(indent=2)

Remote Files

With the optional fsspec dependency, you can read directly from URLs:

pip install capella-reader[fsspec]

capella-reader bounds https://capella-open-data.s3.amazonaws.com/data/2025/5/6/CAPELLA_C13_SP_SLC_HH_20250506043806_20250506043816/CAPELLA_C13_SP_SLC_HH_20250506043806_20250506043816.tif
# -121.92123 37.27596 -121.81966 37.36195
# Or from python:
slc = CapellaSLC.from_file("https://capella-open-data.s3.amazonaws.com/.../file.tif")

Examples

The docs/examples/ directory contains visualization scripts demonstrating orbit and image footprint analysis:

Orbit Visualization

# Combined map with orbit track and image footprint
python examples/orbit_footprint_map.py

See docs/examples/README.md for installation requirements and details.

ISCE3 Integration

For users working with ISCE3, conversion utilities are available in the adapters.isce3 subpackage:

from capella_reader import CapellaSLC
from capella_reader import adapters

slc = CapellaSLC.from_file("path/to/slc.tif")

# Convert to ISCE3 data structures
radar_grid = adapters.isce3.get_radar_grid(slc)
orbit = adapters.isce3.get_orbit(slc)
doppler_poly = adapters.isce3.get_doppler_poly(slc)
doppler_lut = adapters.isce3.get_doppler_lut2d(slc)

These utilities require ISCE3 to be installed separately. They are not required for basic metadata parsing.

Example slc model

CapellaSLC(
    path=PosixPath('tests/data/CAPELLA_C14_SM_SLC_HH_20240626150051_20240626150055.tif'),
    meta=CapellaSLCMetadata(
        software_version='3.2.1',
        software_revision='f8884c31ba9ba129f4d838d6d02dea2c45a114ba-dirty',
        processing_time=datetime.datetime(2025, 5, 8, 17, 38, 5, 669807, tzinfo=TzInfo(UTC)),
        processing_deployment='production',
        product_version='1.10',
        product_type='SLC',
        collect=Collect(
            start_timestamp=datetime.datetime(2024, 6, 26, 15, 0, 51, 295975, tzinfo=TzInfo(UTC)),
            stop_timestamp=datetime.datetime(2024, 6, 26, 15, 0, 55, 793597, tzinfo=TzInfo(UTC)),
            local_datetime=datetime.datetime(2024, 6, 26, 9, 0, 53, 544786, tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=64800))),
            local_timezone='America/Mexico_City',
            platform='capella-14',
            mode='stripmap',
            collect_id='0141157d-3cef-4db0-9239-66c8478e09e6',
            image=ImageMetadata(
                data_type='CInt16',
                length=20395.09163067352,
                width=10071.72523110632,
                rows=19191,
                columns=10631,
                pixel_spacing_row=1.0627985769047086,
                pixel_spacing_column=0.9474459172784211,
                algorithm='backprojection',
                scale_factor=0.004915546693498318,
                range_autofocus=None,
                azimuth_autofocus=None,
                range_window=Window(name='rectangular', parameters={}, broadening_factor=0.8844848400382688),
                processed_range_bandwidth=200000000.0,
                azimuth_window=Window(name='antenna-taper', parameters={'proc_beamwidth': 0.011835009078692163}, broadening_factor=0.9743063402100489),
                processed_azimuth_bandwidth=5022.581708747391,
                image_geometry=ImageGeometry(
                    type='slant_plane',
                    doppler_centroid_polynomial=Poly2D(degree=(3, 3), coefficients=array([[0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.]])),
                    first_line_time=datetime.datetime(2024, 6, 26, 15, 0, 51, 889124, tzinfo=TzInfo(UTC)),
                    delta_line_time=0.00016105733333333333,
                    range_to_first_sample=761275.5576171188,
                    delta_range_sample=0.6171875
                ),
                center_pixel=CenterPixel(
                    incidence_angle=40.93041272468091,
                    look_angle=36.797539101417286,
                    squint_angle=-0.05586304095603156,
                    layover_angle=0.05014323818914027,
                    target_position=ECEFPosition(x=-940324.8666604777, y=-5946543.280362384, z=2098893.4900432685),
                    center_time=datetime.datetime(2024, 6, 26, 15, 0, 53, 544786, tzinfo=TzInfo(UTC))
                ),
                range_resolution=0.6629047106470235,
                ground_range_resolution=1.011849005778887,
                azimuth_resolution=1.280051064876099,
                ground_azimuth_resolution=1.2800517167779046,
                azimuth_looks=1.0,
                range_looks=1.0,
                enl=1.0,
                reference_antenna_position=ECEFPosition(x=-684525.5142771972, y=-6655343.817559726, z=1969607.6195497077),
                reference_target_position=ECEFPosition(x=-942416.8362992472, y=-5947750.607837195, z=2101298.743469007),
                azimuth_beam_pattern_corrected=True,
                elevation_beam_pattern_corrected=True,
                radiometry='beta_nought',
                calibration='full',
                calibration_id='calibration_bundle/61b7b4ca-81a8-45e2-932e-11e1489c02c6',
                nesz_polynomial=Poly1D(degree=3, coefficients=array([ 2.10754181e+05, -5.51826980e-01,  3.61176889e-07,  0.00000000e+00])),
                nesz_peak=-24.100213130335916,
                terrain_models=TerrainModels(
                    focusing=TerrainModelRef(link='ExplicitInflatedWGS84[2234.0048828125]+https://en.wikipedia.org/wiki/World_Geodetic_System', name='ExplicitInflatedWGS84[2234.0048828125]')
                ),
                reference_doppler_centroid=0.0,
                frequency_doppler_centroid_polynomial=Poly2D(
                    degree=(3, 3),
                    coefficients=array([[-1.44553946e-20, -7.48708712e-15, -2.85415191e-09,
         2.73100984e-15],
       [-1.74404613e-18,  3.05520758e-17,  1.18415007e-11,
        -7.73815716e-18],
       [ 4.66622572e-23,  2.51364523e-17,  1.01345640e-11,
        -2.31147587e-17],
       [-3.83941864e-23, -1.36857307e-17, -2.95147334e-12,
         5.47845610e-18]])
                ),
                quantization=Quantization(type='block_adaptive_quantization', block_sample_size=64, mean_bits=0, std_bits=0, sample_bits=6)
            ),
            radar=Radar(
                rank=50,
                center_frequency=9649999872.0,
                pointing=<LookSide.LEFT: 'left'>,
                sampling_frequency=750000000.0,
                transmit_polarization='H',
                receive_polarization='H',
                time_varying_parameters=[
                    RadarTimeVaryingParams(
                        start_timestamps=[datetime.datetime(2024, 6, 26, 15, 0, 51, 301024, tzinfo=TzInfo(UTC))],
                        prf=9901.251518191899,
                        pulse_bandwidth=200000000.0,
                        pulse_duration=2.0197333333333334e-05,
                        rank=50
                    ),
                    RadarTimeVaryingParams(
                        start_timestamps=[
                            datetime.datetime(2024, 6, 26, 15, 0, 51, 801024, tzinfo=TzInfo(UTC)),
                            datetime.datetime(2024, 6, 26, 15, 0, 52, 301024, tzinfo=TzInfo(UTC)),
                            datetime.datetime(2024, 6, 26, 15, 0, 52, 801024, tzinfo=TzInfo(UTC)),
                            datetime.datetime(2024, 6, 26, 15, 0, 53, 301024, tzinfo=TzInfo(UTC)),
                            datetime.datetime(2024, 6, 26, 15, 0, 53, 801024, tzinfo=TzInfo(UTC)),
                            datetime.datetime(2024, 6, 26, 15, 0, 54, 301024, tzinfo=TzInfo(UTC)),
                            datetime.datetime(2024, 6, 26, 15, 0, 54, 801024, tzinfo=TzInfo(UTC)),
                            datetime.datetime(2024, 6, 26, 15, 0, 55, 301024, tzinfo=TzInfo(UTC))
                        ],
                        prf=9900.72869363185,
                        pulse_bandwidth=200000000.0,
                        pulse_duration=2.0197333333333334e-05,
                        rank=50
                    )
                ],
                prf=[
                    PRFEntry(start_timestamps=[datetime.datetime(2024, 6, 26, 15, 0, 51, 301024, tzinfo=TzInfo(UTC))], prf=9901.251518191899),
                    PRFEntry(
                        start_timestamps=[
                            datetime.datetime(2024, 6, 26, 15, 0, 51, 801024, tzinfo=TzInfo(UTC)),
                            datetime.datetime(2024, 6, 26, 15, 0, 52, 301024, tzinfo=TzInfo(UTC)),
                            datetime.datetime(2024, 6, 26, 15, 0, 52, 801024, tzinfo=TzInfo(UTC)),
                            datetime.datetime(2024, 6, 26, 15, 0, 53, 301024, tzinfo=TzInfo(UTC)),
                            datetime.datetime(2024, 6, 26, 15, 0, 53, 801024, tzinfo=TzInfo(UTC)),
                            datetime.datetime(2024, 6, 26, 15, 0, 54, 301024, tzinfo=TzInfo(UTC)),
                            datetime.datetime(2024, 6, 26, 15, 0, 54, 801024, tzinfo=TzInfo(UTC)),
                            datetime.datetime(2024, 6, 26, 15, 0, 55, 301024, tzinfo=TzInfo(UTC))
                        ],
                        prf=9900.72869363185
                    )
                ]
            ),
            state=State(
                coordinate_system=CoordinateSystem(type='ecef'),
                direction='ascending',
                state_vectors=[
                    StateVector(
                        time=datetime.datetime(2024, 6, 26, 15, 0, 21, 199958, tzinfo=TzInfo(UTC)),
                        position=ECEFPosition(x=-850598.581307973, y=-6682238.839189964, z=1807837.287228866),
                        velocity=ECEFVelocity(vx=5117.104280428376, vy=717.1797056134662, vz=5036.528855949608)
                    ),
                    StateVector(
                        time=datetime.datetime(2024, 6, 26, 15, 0, 21, 799957, tzinfo=TzInfo(UTC)),
                        position=ECEFPosition(x=-847528.1256471712, y=-6681807.258344243, z=1810858.8158753526),
                        velocity=ECEFVelocity(vx=5117.763610991626, vy=721.4251640291432, vz=5035.248748046506)
                    ),
...
                    StateVector(
                        time=datetime.datetime(2024, 6, 26, 15, 1, 24, 799857, tzinfo=TzInfo(UTC)),
                        position=ECEFPosition(x=-523139.33015611547, y=-6622363.191185433, z=2123595.7839055755),
                        velocity=ECEFVelocity(vx=5177.000593219978, vy=1164.868056941853, vz=4889.0522055202055)
                    ),
                    StateVector(
                        time=datetime.datetime(2024, 6, 26, 15, 1, 25, 399856, tzinfo=TzInfo(UTC)),
                        position=ECEFPosition(x=-520032.9939516515, y=-6621663.012097265, z=2126528.759803662),
                        velocity=ECEFVelocity(vx=5177.469063184616, vy=1169.066041486259, vz=4887.548741599158)
                    )
                ],
                source='precise_determination'
            ),
            pointing=[
                PointingSample(
                    time=datetime.datetime(2024, 6, 26, 15, 0, 21, 199958, tzinfo=TzInfo(UTC)),
                    attitude=AttitudeQuaternion(q0=0.7514354558972666, q1=0.539089174592113, q2=0.33782447576219665, q3=0.17493464855872806)
                ),
                PointingSample(
                    time=datetime.datetime(2024, 6, 26, 15, 0, 21, 799957, tzinfo=TzInfo(UTC)),
                    attitude=AttitudeQuaternion(q0=0.7513778110356235, q1=0.5391998219192001, q2=0.3379087735187681, q3=0.1746782481659656)
                ),
                ...
                PointingSample(
                    time=datetime.datetime(2024, 6, 26, 15, 1, 25, 399856, tzinfo=TzInfo(UTC)),
                    attitude=AttitudeQuaternion(q0=0.7464363921729759, q1=0.549879633720447, q2=0.3455829675461274, q3=0.14504242371384654)
                )
            ],
            transmit_antenna=Antenna(
                azimuth_beamwidth=0.01211729820681252,
                elevation_beamwidth=0.01219859298030783,
                gain=49.58,
                beam_pattern=Poly2D(
                    degree=(7, 7),
                    coefficients=array([[ 4.96288054e+01,  3.54236916e-04, -8.23751862e+04,
        -5.46972016e+00, -3.85558272e+07,  3.86615092e+04,
         5.23604124e+11,  2.89923661e+08],
       [-3.48671932e-04,  2.20787614e+00, -5.08022213e+00,
        -9.12656092e+03,  6.91149686e+04,  5.63776331e+06,
        ...
         0.00000000e+00,  0.00000000e+00,  0.00000000e+00,
         0.00000000e+00,  0.00000000e+00],
       [-7.85159183e+07,  0.00000000e+00,  0.00000000e+00,
         0.00000000e+00,  0.00000000e+00,  0.00000000e+00,
         0.00000000e+00,  0.00000000e+00]])
                )
            ),
            receive_antenna=Antenna(
                azimuth_beamwidth=0.01211729820681252,
                elevation_beamwidth=0.01219859298030783,
                gain=49.58,
                beam_pattern=Poly2D(
                    degree=(7, 7),
                    coefficients=array([[ 4.96288054e+01,  3.54236916e-04, -8.23751862e+04,
        -5.46972016e+00, -3.85558272e+07,  3.86615092e+04,
         5.23604124e+11,  2.89923661e+08],
       [-3.48671932e-04,  2.20787614e+00, -5.08022213e+00,
        -9.12656092e+03,  6.91149686e+04,  5.63776331e+06,
         ...
       [-7.85159183e+07,  0.00000000e+00,  0.00000000e+00,
         0.00000000e+00,  0.00000000e+00,  0.00000000e+00,
         0.00000000e+00,  0.00000000e+00]])
                )
            )
        )
    )
)

Development

Using pixi for pacakage management and task running:

# after installing pixi: curl -fsSL https://pixi.sh/install.sh | sh
pixi install

To create a shell for development

pixi shell -e test
pre-commit install

You can see all pixi tasks with:

pixi task list

Download test data (required for integration tests):

pixi run download-test-data        # Get the sample data images
pixi run test                      # Run all tests (requires test data)

Run linting and formatting:

pixi run --environment test check
pixi run --environment test test

References

Capella Product Specification version 1.8

License

See LICENSE.txt

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

capella_reader-0.2.2.tar.gz (590.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

capella_reader-0.2.2-py3-none-any.whl (31.5 kB view details)

Uploaded Python 3

File details

Details for the file capella_reader-0.2.2.tar.gz.

File metadata

  • Download URL: capella_reader-0.2.2.tar.gz
  • Upload date:
  • Size: 590.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for capella_reader-0.2.2.tar.gz
Algorithm Hash digest
SHA256 79569b40f766baa572a06c3e4611588a7f82e9050e787d587c520fc078cac6a7
MD5 722d39069e7fe99b8f1abe9257d02f19
BLAKE2b-256 c5a8784b2d59dbcbea59d37195795819284c29977f3710a1afc604acee21c3a2

See more details on using hashes here.

Provenance

The following attestation bundles were made for capella_reader-0.2.2.tar.gz:

Publisher: cd.yml on capellaspace/capella-reader

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file capella_reader-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: capella_reader-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 31.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for capella_reader-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 846a61c5c5df8d9dd248b0d69a4dbc10e6f38eba65402ea19f1faaa551bf2dea
MD5 84ddbd6762cf37a2987f4f0fb48afadb
BLAKE2b-256 93c21eb036c3706c0ed7d2f59d77ac3b94c98f635723d478f2127c34406084d7

See more details on using hashes here.

Provenance

The following attestation bundles were made for capella_reader-0.2.2-py3-none-any.whl:

Publisher: cd.yml on capellaspace/capella-reader

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page