capella-reader
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 files1; 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.
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
Metadata
Release files for capella-reader 0.2.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| capella_reader-0.2.3.tar.gz | 899.9 kB | With provenance | Details for capella_reader-0.2.3.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| capella_reader-0.2.3-py3-none-any.whl | Python 3 | none | any | Details for capella_reader-0.2.3-py3-none-any.whl |
Total release size: 931.4 kB
Release files / capella_reader-0.2.3.tar.gz
| Download URL | capella_reader-0.2.3.tar.gz |
|---|---|
| Size | 899.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
be948b3703339dac1d9ab8c7c63db9063385226876ef65151ca5d657dd83bff0
|
|
BLAKE2b-256 checksum How to use checksums |
20f0ac29add32a233c28d6f3661639a497b91463de89b633a123ea3d6b11e50e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logRelease files / capella_reader-0.2.3-py3-none-any.whl
| Download URL | capella_reader-0.2.3-py3-none-any.whl |
|---|---|
| Size | 31.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7aba9eec3d457fdc3033192a213a25c8fa6cdd7752fa56b2b23beca8bce8ba58
|
|
BLAKE2b-256 checksum How to use checksums |
e25e1b85df59871bb3044da02dbe1584d6645c956bd4ddc40bc3b59a19d190d3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logSecurity for capella-reader 0.2.3
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for capella_reader-0.2.3.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.