nav.reproj
Body reprojection and mosaicing utilities.
This module provides the BodyMosaic class for reprojecting planetary body images onto latitude/longitude grids and accumulating them into mosaics.
- class BodyMosaic(*, body_name: str, lat_resolution: float = 0.0017453292519943296, lon_resolution: float = 0.0017453292519943296, lat_range: tuple[float, float] | None=None, lon_range: tuple[float, float] | None=None, dynamic: bool = True, max_incidence: float | None = None, max_emission: float | None = None, max_resolution: float | None = None, edge_margin: int = 3, zoom: int = 1, latlon_type: Literal['centric', 'graphic', 'squashed']='centric', lon_direction: Literal['east', 'west']='east', photometric_model: PhotometricModel | None = None, merge_strategy: BodyMosaicMergeStrategy = BodyMosaicMergeStrategy.BEST_RESOLUTION, image_dtype: DTypeLike = <class 'numpy.float64'>, metadata_dtype: DTypeLike = <class 'numpy.float32'>)[source]
Bases:
objectReprojects and mosaics body images in latitude/longitude space.
Combines reprojected body images into a single mosaic, resolving conflicts using a configurable merge strategy.
The coordinate grid uses the specified resolution in both latitude and longitude. All angular values are in radians.
Internal storage uses a shifted circular buffer to handle longitude wraparound (data spanning the 0/2*pi boundary) without allocating the full 0..2*pi range.
Thread safety: reproject() uses oops backplane computation which is not safe for concurrent use on the same observation. Do not call reproject() from multiple threads on the same observation.
Example usage:
mosaic = BodyMosaic(body_name='MIMAS') for obs in observations: result = mosaic.reproject(obs, data=obs.data) mosaic.add(result) data = mosaic.to_bounded()
- body_name
The name of the body.
- add(repro: BodyReprojResult, *, resolution_threshold: float = 1.0, copy_slop: int = 0, max_incidence: float | None | _UseMosaicLimitsSentinel = USE_MOSAIC_LIMITS, max_emission: float | None | _UseMosaicLimitsSentinel = USE_MOSAIC_LIMITS, max_resolution: float | None | _UseMosaicLimitsSentinel = USE_MOSAIC_LIMITS) → None[source]
Add a reprojected image to the mosaic.
For each valid pixel in repro, it is incorporated into the mosaic according to the merge strategy.
- Parameters:
repro – Result from reproject().
resolution_threshold – Factor by which repro’s effective resolution must exceed the mosaic’s to trigger replacement. Should be >= 1.0 (higher = stricter).
copy_slop – Number of additional pixels around each copied pixel to also copy, reducing isolated-pixel artifacts.
max_incidence – Maximum incidence angle (rad) for pixels that may be written.
USE_MOSAIC_LIMITS(default) uses the mosaic’s constructor value; an explicitfloatoverrides for this call only;Nonedisables the incidence cutoff for this call.max_emission – Same pattern for emission angle (rad).
max_resolution – Same pattern for center resolution in km/pixel (compared to
repro.resolution, as inreproject()).
- Raises:
OverflowError – If the number of images added would exceed the uint16 maximum of 65 535.
ValueError – If repro’s resolution, coordinate system, or photometric model name does not match the mosaic’s configuration.
- property bounds: tuple[tuple[float, float], tuple[float, float]] | None
Current data extent as ((lat_min, lat_max), (lon_min, lon_max)).
Returns the bounding box of all valid data currently in the mosaic. Longitude bounds are in the contiguous arc order (min may be greater than max if the data wraps around the 0/2*pi boundary).
- Returns:
((lat_min, lat_max), (lon_min, lon_max)) in radians, or None if no data has been added yet.
- static generate_latitudes(*, latitude_start: float = -1.5707963267948966, latitude_end: float = 1.5707943267948965, lat_resolution: float = 0.0017453292519943296) → ndarray[tuple[Any, ...], dtype[floating[Any]]][source]
Generate a list of latitudes on resolution boundaries.
The returned latitudes are on lat_resolution grid boundaries and lie within [latitude_start, latitude_end].
- Parameters:
latitude_start – Minimum latitude (rad). Defaults to -pi/2.
latitude_end – Maximum latitude (rad). Defaults to pi/2 minus slop.
lat_resolution – Latitude resolution (rad/pixel).
- Returns:
1-D array of latitudes (rad).
- static generate_longitudes(*, longitude_start: float = 0.0, longitude_end: float = 6.283183307179586, lon_resolution: float = 0.0017453292519943296) → ndarray[tuple[Any, ...], dtype[floating[Any]]][source]
Generate a list of longitudes on resolution boundaries.
- Parameters:
longitude_start – Minimum longitude (rad). Defaults to 0.
longitude_end – Maximum longitude (rad). Defaults to 2*pi minus slop.
lon_resolution – Longitude resolution (rad/pixel).
- Returns:
1-D array of longitudes (rad).
- latitude_longitude_to_pixels(obs: Any, latitude: Any, longitude: Any) → Any[source]
Convert latitude/longitude arrays to (U, V) image pixel coordinates.
- Parameters:
obs – The Observation.
latitude – Latitude values (rad).
longitude – Longitude values (rad).
- Returns:
UV pairs as a polymath UV object.
- reproject(obs: Any, *, data: ndarray[tuple[Any, ...], dtype[floating[Any]]] | None = None, navigation_uncertainty: float = 0.0, mask_only: bool = False, override_backplane: Any = None, subimage_edges: tuple[int, int, int, int] | None = None, mask_bad_areas: bool = False, image_name: str = '') → BodyReprojResult[source]
Reproject the body into a rectangular latitude/longitude space.
- Parameters:
obs – The Observation (with its FOV already configured as needed).
data – Image data to reproject. Defaults to obs.data.
navigation_uncertainty – Maximum uncertainty in pixel pointing (pixels), used to compute eff_resolution.
mask_only – If True, return only the valid-pixel boolean mask. Other result fields will be empty masked arrays.
override_backplane – A Backplane to use instead of creating a new one from obs.
subimage_edges – (u_min, u_max, v_min, v_max) describing the subimage extent if override_backplane covers only a subimage.
mask_bad_areas – If True, expand bad-pixel regions before masking to handle images with transmission errors.
image_name – Optional label stored on the result (e.g. source image stem).
- Returns:
BodyReprojResult with the reprojected data.
When the body has no valid latitude/longitude on the detector (fully outside the field of view or not projected onto the image), this returns immediately with an empty result without building the lat/lon grid or sampling image pixels.
Note
The image data is taken from the zoomed, interpolated image, while the incidence, emission, phase, and resolution are taken from the original non-interpolated data and thus will be slightly more coarse-grained. Backplane masks and limit tests are folded into
ok_body_mask(see below); scatter uses that same mask soimgis never valid wherephase,emission,incidence, orresolutionwould be masked or beyond configured limits.- Raises:
TypeError – If
navigation_uncertaintyis not a real number (boolis not accepted).ValueError – If
navigation_uncertaintyis not finite or is< 0.
- to_bounded(*, lat_range: tuple[float, float] | None = None, lon_range: tuple[float, float] | None = None) → BodyMosaicData[source]
Return the mosaic clipped to the given lat/lon range.
- Parameters:
lat_range – (min_lat, max_lat) in radians. Defaults to current data bounds.
lon_range – (min_lon, max_lon) in radians. May wrap around 0/2*pi (e.g., (5.9, 0.3) means from 5.9 rad through 2*pi to 0.3 rad). Defaults to current data bounds.
- Returns:
BodyMosaicData with the clipped mosaic.
- Raises:
ValueError – If no data has been added yet and no range is specified.
- to_full() → BodyMosaicData[source]
Return the mosaic as a full latitude x longitude grid.
- Returns:
BodyMosaicData covering the full -pi/2 to pi/2 x 0 to 2*pi grid. Pixels with no data are masked.
- class BodyMosaicData(body_name: str, img: ~numpy.ma.MaskedArray, lat_resolution: float, lon_resolution: float, lat_range: tuple[float, float], lon_range: tuple[float, float], latlon_type: ~typing.Literal['centric', 'graphic', 'squashed'], lon_direction: ~typing.Literal['east', 'west'], resolution: ~numpy.ma.MaskedArray, eff_resolution: ~numpy.ma.MaskedArray, phase: ~numpy.ma.MaskedArray, emission: ~numpy.ma.MaskedArray, incidence: ~numpy.ma.MaskedArray, time: ~numpy.ma.MaskedArray, image_number: ~numpy.ma.MaskedArray, photometric_model_name: str | None, image_dtype: ~numpy.dtype, metadata_dtype: ~numpy.dtype, contributing_image_names: tuple[str, ...] = (), sub_solar_lon_per_image: ~numpy.ndarray = <factory>, sub_solar_lat_per_image: ~numpy.ndarray = <factory>, sub_observer_lon_per_image: ~numpy.ndarray = <factory>, sub_observer_lat_per_image: ~numpy.ndarray = <factory>)[source]
Bases:
objectMosaic data returned by BodyMosaic retrieval methods.
This is a
dataclasses.dataclass(); each field is documented on its own member entry below.- eff_resolution: MaskedArray
- emission: MaskedArray
- image_number: MaskedArray
- img: MaskedArray
- incidence: MaskedArray
- classmethod load(path: str | Path | FCPath, *, format_: str | None = None) → BodyMosaicData[source]
Load a BodyMosaicData from a file.
- Parameters:
path – Input path (
str,pathlib.Path, orfilecache.FCPath).format – Explicit format (
'npz'or'fits'). If None, inferred from the file extension.
- Returns:
A new BodyMosaicData with the loaded data.
- Raises:
ValueError – If the file’s kind does not match, or if any array dtype does not match the declared
image_dtype/metadata_dtype, or ifimage_numberis not uint16, or iftimeis not float64.
Example:
data = BodyMosaicData.load('mimas.npz')
- phase: MaskedArray
- resolution: MaskedArray
- save(path: str | Path | FCPath, *, format_: str | None = None, compress: bool = True) → None[source]
Save this BodyMosaicData to a file.
- Parameters:
path – Output path (
str,pathlib.Path, orfilecache.FCPath). The format is inferred from the extension (.npz for NumPy archive, .fits/.fit for FITS) unlessformat_is given.format – Explicit format:
'npz'or'fits'. Overrides extension-based inference.compress – If True (default), use compressed npz. Ignored for FITS.
- Raises:
ValueError – If the format cannot be inferred or is not supported.
Example:
data = mosaic.to_bounded() data.save('mimas.npz') data.save('mimas.fits', format_='fits') data.save('mimas.npz', compress=False) reloaded = BodyMosaicData.load('mimas.npz')
- time: MaskedArray
- class BodyMosaicMergeStrategy(*values)[source]
Bases:
EnumStrategy for resolving per-pixel conflicts when adding data to a BodyMosaic.
- Members:
- BEST_RESOLUTION: Replace an existing pixel only when the new data has
strictly better effective resolution, or if the existing pixel is empty (masked).
- BEST_RESOLUTION = 'best_resolution'
- class BodyReprojResult(body_name: str, img: MaskedArray, lat_resolution: float, lon_resolution: float, lat_idx_range: tuple[int, int], lon_idx_range: tuple[int, int], latlon_type: Literal['centric', 'graphic', 'squashed'], lon_direction: Literal['east', 'west'], resolution: MaskedArray, eff_resolution: MaskedArray, phase: MaskedArray, emission: MaskedArray, incidence: MaskedArray, time: float, photometric_model_name: str | None, image_dtype: dtype, metadata_dtype: dtype, image_name: str = '', sub_solar_lon: float = 0.0, sub_solar_lat: float = 0.0, sub_observer_lon: float = 0.0, sub_observer_lat: float = 0.0)[source]
Bases:
objectData returned by BodyMosaic.reproject().
This is a
dataclasses.dataclass(); each field is documented on its own member entry below.- eff_resolution: MaskedArray
- emission: MaskedArray
- img: MaskedArray
- incidence: MaskedArray
- classmethod load(path: str | Path | FCPath, *, format_: str | None = None) → BodyReprojResult[source]
Load a BodyReprojResult from a file.
- Parameters:
path – Input path (
str,pathlib.Path, orfilecache.FCPath).format – Explicit format (
'npz'or'fits'). If None, inferred from the file extension.
- Returns:
A new BodyReprojResult with the loaded data.
- Raises:
ValueError – If the file’s kind does not match, or if any array dtype does not match the declared
image_dtype/metadata_dtype.
Example:
result = BodyReprojResult.load('reprojection.npz')
- phase: MaskedArray
- resolution: MaskedArray
- save(path: str | Path | FCPath, *, format_: str | None = None, compress: bool = True) → None[source]
Save this BodyReprojResult to a file.
- Parameters:
path – Output path (
str,pathlib.Path, orfilecache.FCPath). The format is inferred from the extension (.npz for NumPy archive, .fits/.fit for FITS) unlessformat_is given.format – Explicit format:
'npz'or'fits'. Overrides extension-based inference.compress – If True (default), use compressed npz. Ignored for FITS.
- Raises:
ValueError – If the format cannot be inferred or is not supported.
Example:
result = mosaic.reproject(obs) result.save('reprojection.npz') result.save('reprojection.fits', format_='fits') reloaded = BodyReprojResult.load('reprojection.npz')
- USE_MOSAIC_LIMITS: Final[_UseMosaicLimitsSentinel] = USE_MOSAIC_LIMITS
Pass to
BodyMosaic.add()formax_incidence/max_emission/max_resolution.Uses the
BodyMosaicconstructor limits instead of per-call overrides.
Ring reprojection and mosaicing utilities.
This module provides the RingMosaic class for reprojecting planetary ring images onto radius/longitude grids and accumulating them into sparse mosaics.
Thread safety: RingMosaic.reproject() is not thread-safe because it may temporarily mutate obs.fov and oops global precision settings. Concurrent calls from separate threads will interfere.
- class RingMosaic(body_name: str, radius_inner: float, radius_outer: float, *, longitude_resolution: float = 0.00034906585039886593, radius_resolution: float = 5.0, merge_strategy: RingMosaicMergeStrategy = RingMosaicMergeStrategy.MOST_COVERAGE_THEN_RESOLUTION, orbit_model: RingOrbitModel | None = None, image_dtype: DTypeLike = <class 'numpy.float64'>, metadata_dtype: DTypeLike = <class 'numpy.float32'>, photometric_model: PhotometricModel | None = None)[source]
Bases:
objectSparse ring mosaic accumulator.
Reprojects ring images onto a radius/longitude grid and accumulates them into a mosaic using true sparse longitude storage. Only longitude columns that actually contain reprojected data are stored; new columns are inserted in sorted order using batched
np.insertcalls.The interpretation of
radius_inner/radius_outerand longitudes depends onorbit_model:orbit_model is None(default): longitudes are inertial J2000 ring longitudes;radius_innerandradius_outerare absolute ring radii in km.orbit_model is not None: longitudes are co-rotating in the model’s frame;radius_innerandradius_outerare signed offsets in km from the orbital radius at each (longitude, time). For an eccentric orbit, that radial position varies betweena (1 - e)anda (1 + e). The offset semantics make an eccentric ring appear as a straight line in the reprojection. A typical 2000 km wide window centred on the orbit usesradius_inner = -1000andradius_outer = +1000.
- Parameters:
body_name – The planet name (e.g. ‘SATURN’). Used to derive the oops ring body name and shadow body name for backplane calls.
radius_inner – Inner radius (km, absolute) when
orbit_model is None; signed offset (km) from the orbital radius at each (longitude, time) otherwise.radius_outer – Outer radius (km, absolute) when
orbit_model is None; signed offset (km) from the orbital radius at each (longitude, time) otherwise. Must be greater thanradius_inner.longitude_resolution – Longitude bin width (rad/pixel).
radius_resolution – Radius bin height (km/pixel).
merge_strategy – How to resolve conflicts when the same longitude column appears in multiple reprojections.
orbit_model – Default RingOrbitModel for co-rotating frame reprojections; selects the offset-radius semantics described above. Can be overridden per-call in reproject(); the override must agree with this default.
image_dtype – NumPy dtype for the reprojected brightness
imgarray. Defaults tonp.float64.metadata_dtype – NumPy dtype for geometry arrays (
mean_radial_resolution,mean_angular_resolution,mean_phase,mean_emission). Defaults tonp.float32.photometric_model – Optional photometric correction applied to
imginreproject()before accumulation.Nonemeans raw brightness.
Notes
reproject() is not thread-safe because it mutates obs.fov and oops global precision settings.
timeis always stored asfloat64regardless ofmetadata_dtype.image_numberis always stored asuint16;_image_countis stored as the image_number before incrementing, so valid image_number values span 0..65535 (a total of 65,536 images).add()raisesOverflowErrorafter the 65,536th image is added.- add(repro: RingReprojResult) → None[source]
Add a reprojected image to the mosaic.
New longitude columns are inserted into the sparse arrays using a single batched
np.insertcall. Existing columns are updated according to the merge strategy.The reprojection’s orbit model and photometric model must match the mosaic’s.
orbit_modelmust be the same instance (or bothNone);photometric_model_namemust equal the mosaic’s photometric model name (or bothNone). Mixing different settings would silently corrupt the mosaic because radii and longitudes have different meanings under different orbit models.- Parameters:
repro – Sparse reprojection result from reproject().
- Raises:
OverflowError – If the number of images added would exceed the uint16 maximum of 65 535.
ValueError – If the reprojection’s body name, grid resolution, radius bounds, dtypes, sparse shapes,
longitude_antimasktrue count vs. sparse column count, orbit model, or photometric model does not match the mosaic’s.
- property bounds: tuple[float, float] | None
Longitude extent of current mosaic data as (min, max) in radians.
Returns None if no data has been added yet.
- static generate_longitudes(longitude_start: float = 0.0, longitude_end: float = 6.283183307179586, *, longitude_resolution: float = 0.00034906585039886593) → ndarray[tuple[Any, ...], dtype[floating[Any]]][source]
Generate a longitude array aligned to grid boundaries.
- Parameters:
longitude_start – Start of the range (rad). Defaults to 0.
longitude_end – End of the range (rad). Defaults to just under 2*pi.
longitude_resolution – Step size (rad/pixel).
- Returns:
1-D array of longitudes (rad) on resolution boundaries, with no value less than longitude_start or greater than longitude_end.
- static generate_radii(radius_inner: float, radius_outer: float, *, radius_resolution: float = 5.0) → ndarray[tuple[Any, ...], dtype[floating[Any]]][source]
Generate a radius array from inner to outer.
- Parameters:
radius_inner – Inner radius (km).
radius_outer – Outer radius (km).
radius_resolution – Step size (km/pixel).
- Returns:
1-D array of radii (km) starting at radius_inner with step radius_resolution, ending at or just before radius_outer.
- static longitude_radius_to_pixels(obs: Any, longitude: ndarray[tuple[Any, ...], dtype[floating[Any]]], radius: ndarray[tuple[Any, ...], dtype[floating[Any]]], *, orbit_model: RingOrbitModel | None = None, ring_body_name: str = 'saturn:ring') → tuple[ndarray[tuple[Any, ...], dtype[floating[Any]]], ndarray[tuple[Any, ...], dtype[floating[Any]]]][source]
Convert longitude/radius pairs to image pixel coordinates.
If orbit_model is given, the longitude values are treated as co-rotating and are converted to inertial before the coordinate lookup.
- Parameters:
obs – The Observation whose FOV is used.
longitude – Longitude array (rad).
radius – Radius array (km).
orbit_model – RingOrbitModel for co-rotating frame conversion, or None for inertial longitude.
ring_body_name – oops ring body name for the surface lookup.
- Returns:
Tuple of (u, v) floating-point pixel coordinate arrays.
- static orbit_pixels(obs: Any, orbit_model: RingOrbitModel, *, ring_body_name: str = 'saturn:ring', longitude_step: float = 3.490658503988659e-05) → tuple[ndarray[tuple[Any, ...], dtype[floating[Any]]], ndarray[tuple[Any, ...], dtype[floating[Any]]]][source]
Return (u, v) pixel pairs for an orbit model feature in an image.
Computes the full 0..2pi longitude range, converts each to image coordinates, and filters to those inside the FOV.
- Parameters:
obs – The Observation (with its FOV already configured as needed).
orbit_model – The ring orbit model to trace.
ring_body_name – oops ring body name.
longitude_step – Longitude step for sampling (rad).
- Returns:
Tuple of (u_pixels, v_pixels) integer pixel coordinate arrays.
- reproject(obs: Any, data: MaskedArray | None = None, *, longitude_range: tuple[float, float] | None = None, radius_range: tuple[float, float] | None = None, margin: int = 3, zoom_amt: int | tuple[int, int] = (1, 1), orbit_model: RingOrbitModel | None = None, uv_range: tuple[int, int, int, int] | None = None, omit_shadow: bool = True, image_name: str = '') → RingReprojResult[source]
Reproject the ring in an image to a sparse radius/longitude grid.
Always returns a sparse RingReprojResult: only longitude columns with valid data are stored in the result. The returned longitude_antimask indicates which of the
n_full_lonbins are present.- Parameters:
obs – The Observation (with its FOV already configured as needed).
data – Image data to reproject. If None, uses obs.data.
longitude_range – (start, end) longitude limits (rad). Defaults to the full 0..2pi range.
radius_range – (inner, outer) radius limits (km). Defaults to the mosaic’s own radius_inner/outer.
margin – Number of edge pixels to exclude. Must be >= 1.
zoom_amt – Positive integer or (radial, longitude) tuple giving the zoom factor for sub-pixel interpolation. Negative values select spline interpolation order (not yet supported).
orbit_model – RingOrbitModel for co-rotating frame conversion. Overrides the default set at construction. None uses inertial longitude.
uv_range – (start_u, end_u, start_v, end_v) to restrict the image region reprojected.
omit_shadow – If True, mask pixels inside the planet’s shadow.
image_name – Optional label stored on the result (e.g. source image stem).
- Returns:
RingReprojResult with sparse image and metadata arrays.
- Raises:
NotImplementedError – If negative zoom_amt (spline order) is requested.
TypeError – If
longitude_rangeorradius_rangeis not a tuple/list of numeric endpoints, or an endpoint has the wrong type.ValueError – If
longitude_rangeorradius_rangehas wrong length, non-finite values, longitudes outside[0, _MAX_LONGITUDE],start > end,radius_inner >= radius_outer, ifzoom_amtis a sequence with length other than 2, ifn_longitude_bins_zoomis not a multiple ofl_zoom_amt, or for other argument errors.TypeError – If
zoom_amtis not an int (excludingbool) or a length-2 tuple/list of ints, or an element is not int-like.
- to_bounded(*, longitude_range: tuple[float, float]) → RingMosaicData[source]
Return the mosaic restricted to the given longitude range.
The returned img has shape [n_radius, n_bins_in_range] where every bin from the start to end of the range is included. Bins without data are fully masked.
- Parameters:
longitude_range – (start, end) in radians.
- Returns:
RingMosaicData covering exactly the requested longitude range.
- Raises:
TypeError – If
longitude_rangeis not a tuple/list of two numbers, or an endpoint has the wrong type (same rules asreproject()).ValueError – If endpoints are non-finite, outside
[0, _MAX_LONGITUDE], orstart > end(same rules asreproject()).
- to_full() → RingMosaicData[source]
Return the mosaic as a dense full-circle array.
The returned img has shape [n_radius, n_full_lon]. Longitude columns with no data are fully masked.
- Returns:
RingMosaicData with a full-circle dense array.
- to_sparse() → RingMosaicData[source]
Return the mosaic in its native sparse representation.
The returned img has shape [n_radius, n_valid_longitudes] and the longitude_antimask has length n_full_lon with True at each stored column.
- Returns:
RingMosaicData with the sparse internal arrays.
- class RingMosaicData(body_name: str, ring_body_name: str, shadow_body_name: str, longitude_resolution: float, radius_resolution: float, radius_inner: float, radius_outer: float, longitude_antimask: ndarray[tuple[Any, ...], dtype[bool]], img: MaskedArray, longitude_range: tuple[float, float] | None, mean_radial_resolution: MaskedArray, mean_angular_resolution: MaskedArray, mean_phase: MaskedArray, mean_emission: MaskedArray, mean_incidence: float, image_number: MaskedArray, time: MaskedArray, image_dtype: dtype, metadata_dtype: dtype, contributing_image_names: tuple[str, ...] = (), orbit_model_name: str | None = None, photometric_model_name: str | None = None)[source]
Bases:
objectMosaic data returned by RingMosaic retrieval methods.
The meaning of
radius_inner/radius_outerand longitudes depends on whetherorbit_model_nameisNone:orbit_model_name is None: longitudes are inertial J2000 ring longitudes;radius_innerandradius_outerare absolute ring radii in km.orbit_model_name is not None: longitudes are co-rotating in the named orbit model’s frame;radius_innerandradius_outerare signed offsets in km from the orbital radius at each (longitude, time) — i.e. fromorbit_model.radius_at_longitude(inertial_lon, et).
This is a
dataclasses.dataclass(); each field is documented on its own member entry below.- image_number: MaskedArray
- img: MaskedArray
- classmethod load(path: str | Path | FCPath, *, format_: str | None = None) → RingMosaicData[source]
Load a RingMosaicData from a file.
- Parameters:
path – Input path (
str,pathlib.Path, orfilecache.FCPath).format – Explicit format (
'npz'or'fits'). If None, inferred from the file extension.
- Returns:
A new RingMosaicData with the loaded data.
- Raises:
ValueError – If the file’s kind does not match, or if any array dtype does not match the declared
image_dtype/metadata_dtype, or ifimage_numberis not uint16, or iftimeis not float64.
Example:
data = RingMosaicData.load('saturn_rings.npz')
- mean_angular_resolution: MaskedArray
- mean_emission: MaskedArray
- mean_phase: MaskedArray
- mean_radial_resolution: MaskedArray
- save(path: str | Path | FCPath, *, format_: str | None = None, compress: bool = True) → None[source]
Save this RingMosaicData to a file.
- Parameters:
path – Output path (
str,pathlib.Path, orfilecache.FCPath). The format is inferred from the extension (.npz for NumPy archive, .fits/.fit for FITS) unlessformat_is given.format – Explicit format:
'npz'or'fits'.compress – If True (default), use compressed npz. Ignored for FITS.
- Raises:
ValueError – If the format cannot be inferred or is not supported.
Example:
data = ring_mosaic.to_bounded(longitude_range=(0.0, math.pi)) data.save('saturn_rings.npz') data.save('saturn_rings.fits', format_='fits') reloaded = RingMosaicData.load('saturn_rings.npz')
- time: MaskedArray
- class RingMosaicMergeStrategy(*values)[source]
Bases:
EnumStrategy for resolving per-longitude-column conflicts when adding data to a RingMosaic.
- Members:
- BEST_RESOLUTION: Replace an existing longitude column only when the new
data has strictly better mean radial resolution for that column.
- MOST_COVERAGE_THEN_RESOLUTION: Fill empty (missing) longitude columns
first; for already-present columns, replace only when the new data has better mean radial resolution.
- BEST_RESOLUTION = 'best_resolution'
- MOST_COVERAGE_THEN_RESOLUTION = 'most_coverage_then_resolution'
- class RingReprojResult(body_name: str, img: MaskedArray, longitude_resolution: float, radius_resolution: float, radius_inner: float, radius_outer: float, longitude_antimask: ndarray[tuple[Any, ...], dtype[bool]], mean_radial_resolution: ndarray[tuple[Any, ...], dtype[floating[Any]]], mean_angular_resolution: ndarray[tuple[Any, ...], dtype[floating[Any]]], mean_phase: ndarray[tuple[Any, ...], dtype[floating[Any]]], mean_emission: ndarray[tuple[Any, ...], dtype[floating[Any]]], incidence: float, time: float, orbit_model: RingOrbitModel | None, image_dtype: dtype, metadata_dtype: dtype, photometric_model_name: str | None = None, image_name: str = '')[source]
Bases:
objectData returned by RingMosaic.reproject().
The image and per-longitude metadata arrays are always sparse: only longitude columns with valid data are included. Use
longitude_antimaskto reconstruct the actual longitude values.The interpretation of
radius_inner/radius_outerand longitudes depends on whetherorbit_modelisNone:orbit_model is None: longitudes are inertial J2000 ring longitudes;radius_innerandradius_outerare absolute ring radii in km.orbit_model is not None: longitudes are co-rotating;radius_innerandradius_outerare signed offsets in km from the orbit’s actual radial position at each (longitude, time). For an eccentric orbit, that radial position varies betweena (1 - e)anda (1 + e); the offset semantics make an eccentric ring appear as a straight line in the reprojection.
This is a
dataclasses.dataclass(); each field is documented on its own member entry below.- img: MaskedArray
- classmethod load(path: str | Path | FCPath, *, format_: str | None = None) → RingReprojResult[source]
Load a RingReprojResult from a file.
- Parameters:
path – Input path (
str,pathlib.Path, orfilecache.FCPath).format – Explicit format (
'npz'or'fits'). If None, inferred from the file extension.
- Returns:
A new RingReprojResult with the loaded data.
- Raises:
ValueError – If the file’s kind does not match, or if any array dtype does not match the declared
image_dtype/metadata_dtype.
Example:
result = RingReprojResult.load('ring_reproj.npz')
- orbit_model: RingOrbitModel | None
- save(path: str | Path | FCPath, *, format_: str | None = None, compress: bool = True) → None[source]
Save this RingReprojResult to a file.
- Parameters:
path – Output path (
str,pathlib.Path, orfilecache.FCPath). The format is inferred from the extension (.npz for NumPy archive, .fits/.fit for FITS) unlessformat_is given.format – Explicit format:
'npz'or'fits'.compress – If True (default), use compressed npz. Ignored for FITS.
- Raises:
ValueError – If the format cannot be inferred or is not supported.
Example:
result = ring_mosaic.reproject(obs) result.save('ring_reproj.npz') reloaded = RingReprojResult.load('ring_reproj.npz')
Cartographic model creation for planetary bodies.
Creates navigation model images by projecting a lat/lon body mosaic back onto the image coordinate system for use in navigation correlation.
Thread safety: create_cartographic_model() creates a Backplane from the provided observation and is not thread-safe. Do not call it from multiple threads with the same observation.
- class CartographicModelResult(model_img: ndarray[tuple[Any, ...], dtype[floating[Any]]], resolution_ratio: float)[source]
Bases:
objectResult returned by create_cartographic_model().
This is a
dataclasses.dataclass();model_imgis the model image in observation pixel coordinates[v, u](0.0 outside mosaic coverage or where the body is not visible), andresolution_ratiois the ratio of the median mosaic effective resolution to the image-center resolution (both km/pixel; 1.0 means equal, greater than 1.0 means the mosaic is coarser).
- create_cartographic_model(mosaic_data: BodyMosaicData, obs: Any, *, body_name: str, latlon_type: Literal['centric', 'graphic', 'squashed'] = 'centric', lon_direction: Literal['east', 'west'] = 'east') → CartographicModelResult | None[source]
Create a navigation model by projecting a body mosaic onto image coordinates.
For each pixel in the observation image, the corresponding latitude and longitude on the body surface is computed using the observation geometry. The mosaic is sampled at those coordinates via bilinear interpolation to produce a model image suitable for correlation with the actual image.
Thread safety: Not thread-safe. Creates a Backplane from obs, which involves shared state. Do not call from multiple threads with the same observation.
- Parameters:
mosaic_data – Body mosaic in lat/lon coordinates, typically obtained from BodyMosaic.to_bounded() or BodyMosaic.to_full().
obs – The oops Observation for which the model is being created.
body_name – Name of the planetary body (e.g. ‘MIMAS’).
latlon_type – Coordinate system for latitude/longitude. One of ‘centric’, ‘graphic’, or ‘squashed’. Must match the type used to build the mosaic.
lon_direction – Longitude direction. One of ‘east’ or ‘west’. Must match the direction used to build the mosaic.
- Returns:
CartographicModelResult with the model image and resolution ratio, or None if the mosaic contains no valid data.
Photometric correction models for body and ring reprojections.
All models implement the PhotometricModel protocol and apply a correction to
raw pixel brightness based on the local illumination and viewing geometry.
Default is no correction (pass photometric_model=None to BodyMosaic or
RingMosaic).
All angles are in radians throughout.
- class LambertModel(name: str = 'lambert', min_cos_incidence: float = 0.01)[source]
Bases:
objectLambert (Lambertian) photometric correction.
Divides the data by cos(incidence) to normalize for the amount of sunlight falling on the surface element. The emission angle is not included because Lambert’s law only describes the illumination term; the viewing geometry is independent. Near-grazing incidence is clamped to avoid division by near-zero values.
min_cos_incidence(default 0.01, ~84°) clamps the Lambert denominator; see the member entry below.- Raises:
TypeError – If
min_cos_incidenceis not a real scalar (boolis rejected).ValueError – If
min_cos_incidenceis not finite or is not in[1e-15, 1](valid cosine clamp range).
- correct(data: ndarray[tuple[Any, ...], dtype[floating[Any]]], *, incidence: ndarray[tuple[Any, ...], dtype[floating[Any]]], emission: ndarray[tuple[Any, ...], dtype[floating[Any]]], phase: ndarray[tuple[Any, ...], dtype[floating[Any]]]) → ndarray[tuple[Any, ...], dtype[floating[Any]]][source]
Apply Lambert photometric correction.
- Parameters:
data – Raw pixel brightness values.
incidence – Incidence angle at each pixel (rad).
emission – Emission angle at each pixel (rad). Not used.
phase – Phase angle at each pixel (rad). Not used.
- Returns:
Data divided by max(cos(incidence), min_cos_incidence).
- uncorrect(data: ndarray[tuple[Any, ...], dtype[floating[Any]]], *, incidence: ndarray[tuple[Any, ...], dtype[floating[Any]]], emission: ndarray[tuple[Any, ...], dtype[floating[Any]]], phase: ndarray[tuple[Any, ...], dtype[floating[Any]]]) → ndarray[tuple[Any, ...], dtype[floating[Any]]][source]
Undo
correct()(multiply by clampedcos(incidence)).- Parameters:
- Returns:
Uncorrected values
data * max(cos(incidence), min_cos_incidence),NDArrayFloatType, same shape asdata(inverse of division incorrect()using the same clamping).
- class LommelSeeligerModel(name: str = 'lommel_seeliger', min_cos_incidence: float = 0.01, min_denom: float = 1e-15)[source]
Bases:
objectLommel-Seeliger photometric correction.
Models single-scattering on a surface with a bidirectional reflectance proportional to 1/(mu0 + mu), where mu0=cos(incidence) and mu=cos(emission). The correction factor applied to the data is (mu0 + mu) / (2 * mu0), which normalizes out this scattering model.
min_cos_incidencedefaults to 0.01; see the member entry below.- Raises:
TypeError – If a numeric field is not a real scalar (
boolis rejected).ValueError – If a field is not finite or is outside its allowed range.
- correct(data: ndarray[tuple[Any, ...], dtype[floating[Any]]], *, incidence: ndarray[tuple[Any, ...], dtype[floating[Any]]], emission: ndarray[tuple[Any, ...], dtype[floating[Any]]], phase: ndarray[tuple[Any, ...], dtype[floating[Any]]]) → ndarray[tuple[Any, ...], dtype[floating[Any]]][source]
Apply Lommel-Seeliger photometric correction.
- Parameters:
data – Raw pixel brightness values.
incidence – Incidence angle at each pixel (rad).
emission – Emission angle at each pixel (rad).
phase – Phase angle at each pixel (rad). Not used.
- Returns:
Data multiplied by
denom / (2 * cos_i), wherecos_iismax(cos(incidence), min_cos_incidence),cos_e = cos(emission), anddenomiscos_i + cos_ewith magnitude floored tomin_denomwhile preserving sign (same rule asuncorrect()).
- uncorrect(data: ndarray[tuple[Any, ...], dtype[floating[Any]]], *, incidence: ndarray[tuple[Any, ...], dtype[floating[Any]]], emission: ndarray[tuple[Any, ...], dtype[floating[Any]]], phase: ndarray[tuple[Any, ...], dtype[floating[Any]]]) → ndarray[tuple[Any, ...], dtype[floating[Any]]][source]
Undo
correct()using the same incidence clamping and signed denom floor.- Parameters:
data – Values after Lommel-Seeliger
correct().incidence – Incidence angles (rad); cosine clamped with
min_cos_incidence.emission – Emission angles (rad);
cos(emission)contributes to the denominator.phase – Accepted for API symmetry; not used.
- Returns:
NDArrayFloatTypeof same shape asdata,data * (2*cos_i) / denomwheredenomiscos_i + cos_ewith the same signed floor ascorrect().
- class MinnaertModel(name: str = 'minnaert', k: float = 0.5, min_cos_incidence: float = 0.01, min_cos_emission: float = 0.01)[source]
Bases:
objectMinnaert photometric correction.
Generalizes the Lambert model with a limb-darkening exponent k. For k=1 this reduces to the Lambert correction (1/cos_i). For k=0.5 this provides a uniform disk appearance across many surfaces.
The correction divides data by cos(incidence)^k * cos(emission)^(k-1).
Defaults:
k= 0.5,min_cos_incidence=min_cos_emission= 0.01; see member entries below.- Raises:
TypeError – If a numeric field is not a real scalar (
boolis rejected).ValueError – If a field is not finite or cosine clamps are outside
(0, 1].
- correct(data: ndarray[tuple[Any, ...], dtype[floating[Any]]], *, incidence: ndarray[tuple[Any, ...], dtype[floating[Any]]], emission: ndarray[tuple[Any, ...], dtype[floating[Any]]], phase: ndarray[tuple[Any, ...], dtype[floating[Any]]]) → ndarray[tuple[Any, ...], dtype[floating[Any]]][source]
Apply Minnaert photometric correction.
- Parameters:
data – Raw pixel brightness values.
incidence – Incidence angle at each pixel (rad).
emission – Emission angle at each pixel (rad).
phase – Phase angle at each pixel (rad). Not used.
- Returns:
Data divided by cos(incidence)^k * cos(emission)^(k-1).
- uncorrect(data: ndarray[tuple[Any, ...], dtype[floating[Any]]], *, incidence: ndarray[tuple[Any, ...], dtype[floating[Any]]], emission: ndarray[tuple[Any, ...], dtype[floating[Any]]], phase: ndarray[tuple[Any, ...], dtype[floating[Any]]]) → ndarray[tuple[Any, ...], dtype[floating[Any]]][source]
Undo
correct()using the samemin_cos_incidence/min_cos_emissionclamps.- Parameters:
data – Corrected radiance / reflectance (same shape as for
correct()).incidence – Incidence angles (rad);
cosclamped tomin_cos_incidence.emission – Emission angles (rad);
cosclamped tomin_cos_emission.phase – Accepted for API symmetry; not used.
- Returns:
NDArrayFloatType, same shape asdata, multiplying bycos_i**k * cos_e**(k-1)with the same clamped cosines ascorrect().
- class PhotometricModel(*args, **kwargs)[source]
Bases:
ProtocolProtocol for photometric correction applied during reprojection.
Implementations must provide a string name and a correct() method. The correct() method receives all three angles as keyword-only NDArrayFloatType parameters (all in radians) and returns the corrected data array.
- correct(data: ndarray[tuple[Any, ...], dtype[floating[Any]]], *, incidence: ndarray[tuple[Any, ...], dtype[floating[Any]]], emission: ndarray[tuple[Any, ...], dtype[floating[Any]]], phase: ndarray[tuple[Any, ...], dtype[floating[Any]]]) → ndarray[tuple[Any, ...], dtype[floating[Any]]][source]
Apply photometric correction to data.
- Parameters:
data – Raw pixel brightness values to correct.
incidence – Incidence angle at each pixel (rad).
emission – Emission angle at each pixel (rad).
phase – Phase angle at each pixel (rad).
- Returns:
Corrected pixel brightness values, same shape as data.
- photometric_model_from_name(name: str | None) → LambertModel | LommelSeeligerModel | MinnaertModel | None[source]
Return a
LambertModel,LommelSeeligerModel, orMinnaertModel.- Parameters:
name – Stored model label from a reprojection/mosaic file, or
None.- Returns:
A fresh model instance, or
NonewhennameisNoneor normalizes to an empty string /none/null(explicit “no model”).- Raises:
ValueError – If
nameis non-empty after normalization but not one of the supported aliases (see Notes).
Notes
Accepted aliases (case-insensitive, spaces and hyphens map to underscores):
lambert;lommel_seeliger/lommelseeliger;minnaert.
Ring orbit models for co-rotating frame transformations.
Provides the RingOrbitModel frozen dataclass representing a Keplerian ring orbit with precession, along with pre-defined instances for the F ring core and B ring outer edge.
All angular quantities are in radians and all time quantities are in seconds (ET/TDB) unless otherwise stated. Day-based rates are converted internally.
- FRING_CORE = RingOrbitModel(name='F-RING-CORE-ALBERS-2007', a=140221.3, e=0.00235, w0=0.4223696789826278, dw=0.04712825312697688, mean_motion=10.157187928076281, epoch_utc='2007-01-01')
F ring core orbit model from Albers et al. 2012 Table 3 Fit #2.
epoch_utc='2007-01-01'anchors the co-rotating mean-motion frame;w0is the longitude of pericenter at J2000 (not atepoch_utc). The2007in the name refers to the co-rotation epoch.
- class RingOrbitModel(name: str, a: float, e: float, w0: float, dw: float, mean_motion: float, epoch_utc: str)[source]
Bases:
objectKeplerian orbit model for a ring feature with apsidal precession.
All angular parameters are in radians. Rate parameters (
dw,mean_motion) are in radians per day; they are converted to per-second internally when needed.a(km) is the semi-major axis (positive).eis eccentricity in[0, 1).w0is the longitude of pericenter at J2000 (rad); at observation timeet(TDB seconds) the pericenter direction isw0 + dw * et / 86400.dwis apsidal precession (rad/day) from J2000.mean_motion(rad/day) drives the co-rotating frame.epoch_utcis an ISO UTC string anchoring that frame, independent of the J2000 apsidal reference.This is a
dataclasses.dataclass(); fields are documented on each member below.- corotating_to_inertial(co_long: ndarray[tuple[Any, ...], dtype[floating[Any]]], et: float) → ndarray[tuple[Any, ...], dtype[floating[Any]]][source]
Convert co-rotating longitude (rad) to inertial longitude (rad).
- Parameters:
co_long – Co-rotating longitude array (rad).
et – Observation time as ephemeris time (TDB seconds).
- Returns:
Inertial longitude array (rad), wrapped to [0, 2*pi).
- inertial_to_corotating(longitude: ndarray[tuple[Any, ...], dtype[floating[Any]]], et: float) → ndarray[tuple[Any, ...], dtype[floating[Any]]][source]
Convert inertial longitude (rad) to co-rotating longitude (rad).
- Parameters:
longitude – Inertial longitude array (rad).
et – Observation time as ephemeris time (TDB seconds).
- Returns:
Co-rotating longitude array (rad), wrapped to [0, 2*pi).
- longitude_radius(et: float, *, step: float = 0.00017453292519943296) → tuple[ndarray[tuple[Any, ...], dtype[floating[Any]]], ndarray[tuple[Any, ...], dtype[floating[Any]]]][source]
Return arrays of (longitude, radius) covering the full 0..2pi range.
- Parameters:
et – Observation time as ephemeris time (TDB seconds).
step – Longitude step size (rad). Defaults to 0.01 degrees.
- Returns:
Tuple of (longitudes, radii) arrays, each of length int(2*pi / step).
- Raises:
ValueError – If step is not a finite positive number.
- radius_at_longitude(longitude: ndarray[tuple[Any, ...], dtype[floating[Any]]], et: float) → ndarray[tuple[Any, ...], dtype[floating[Any]]][source]
Return the ring radius (km) at each inertial longitude and time.
Uses the standard Keplerian orbit equation with a precessing pericenter. The pericenter direction at time
etisw0 + dw * et:w0is the value of curly-pi at J2000 (the standard epoch), anddwis integrated from J2000. The independentepoch_utcfield anchors only the co-rotating mean-motion frame (see_longitude_shift).- Parameters:
longitude – Inertial (true) longitude array (rad).
et – Observation time as ephemeris time (TDB seconds).
- Returns:
Radius in km at each element of longitude.
- get_orbit_model_by_name(name: str) → RingOrbitModel | None[source]
Return a pre-defined
RingOrbitModelby its name, orNone.- Parameters:
name – Model name (e.g.
'F-RING-CORE-ALBERS-2007').- Returns:
The matching
RingOrbitModelfrom_KNOWN_ORBIT_MODELS, orNoneif the name is not found.- Raises:
ValueError – If
nameis empty or whitespace-only.