From 3b0cfbe36da1fc89dffc9de10d4b95fdbc381816 Mon Sep 17 00:00:00 2001 From: edparris Date: Thu, 7 Sep 2023 12:27:18 -0400 Subject: [PATCH 01/11] feat: add SICD sensor model with planar projection (#10) --- schemas/sicd.xsdata.xml | 52 + .../sicd/SICD_schema_V1.2.1_2018_12_13.xsd | 1119 +++++ .../SICD_schema_V1.3.0_2021_11_30_FINAL.xsd | 1109 +++++ setup.cfg | 1 + src/aws/osml/formats/__init__.py | 0 src/aws/osml/formats/sicd/__init__.py | 1 + src/aws/osml/formats/sicd/models/__init__.py | 273 ++ .../osml/formats/sicd/models/sicd_v1_2_1.py | 3622 ++++++++++++++++ .../osml/formats/sicd/models/sicd_v1_3_0.py | 3654 +++++++++++++++++ src/aws/osml/gdal/gdal_utils.py | 8 +- src/aws/osml/gdal/sensor_model_factory.py | 22 +- .../osml/gdal/sicd_sensor_model_builder.py | 186 + src/aws/osml/photogrammetry/__init__.py | 19 + .../osml/photogrammetry/sicd_sensor_model.py | 813 ++++ .../osml/gdal/test_sensor_model_factory.py | 29 + .../photogrammetry/test_sicd_sensor_model.py | 197 + test/data/sicd/capella-sicd121-chip1.ntf | 3 + test/data/sicd/capella-sicd121-chip2.ntf | 3 + test/data/sicd/example.sicd121.capella.xml | 402 ++ test/data/sicd/example.sicd121.pfa.xml | 1156 ++++++ test/data/sicd/example.sicd121.rma.xml | 1577 +++++++ test/data/sicd/umbra-sicd121-chip1.ntf | 3 + 22 files changed, 14243 insertions(+), 6 deletions(-) create mode 100644 schemas/sicd.xsdata.xml create mode 100644 schemas/sicd/SICD_schema_V1.2.1_2018_12_13.xsd create mode 100644 schemas/sicd/SICD_schema_V1.3.0_2021_11_30_FINAL.xsd create mode 100644 src/aws/osml/formats/__init__.py create mode 100644 src/aws/osml/formats/sicd/__init__.py create mode 100644 src/aws/osml/formats/sicd/models/__init__.py create mode 100644 src/aws/osml/formats/sicd/models/sicd_v1_2_1.py create mode 100644 src/aws/osml/formats/sicd/models/sicd_v1_3_0.py create mode 100644 src/aws/osml/gdal/sicd_sensor_model_builder.py create mode 100644 src/aws/osml/photogrammetry/sicd_sensor_model.py create mode 100644 test/aws/osml/photogrammetry/test_sicd_sensor_model.py create mode 100644 test/data/sicd/capella-sicd121-chip1.ntf create mode 100644 test/data/sicd/capella-sicd121-chip2.ntf create mode 100644 test/data/sicd/example.sicd121.capella.xml create mode 100644 test/data/sicd/example.sicd121.pfa.xml create mode 100644 test/data/sicd/example.sicd121.rma.xml create mode 100644 test/data/sicd/umbra-sicd121-chip1.ntf diff --git a/schemas/sicd.xsdata.xml b/schemas/sicd.xsdata.xml new file mode 100644 index 0000000..ad89471 --- /dev/null +++ b/schemas/sicd.xsdata.xml @@ -0,0 +1,52 @@ + + + + sicd.models + dataclasses + filenames + reStructuredText + allGlobals + true + false + false + false + false + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schemas/sicd/SICD_schema_V1.2.1_2018_12_13.xsd b/schemas/sicd/SICD_schema_V1.2.1_2018_12_13.xsd new file mode 100644 index 0000000..df36ea4 --- /dev/null +++ b/schemas/sicd/SICD_schema_V1.2.1_2018_12_13.xsd @@ -0,0 +1,1119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/schemas/sicd/SICD_schema_V1.3.0_2021_11_30_FINAL.xsd b/schemas/sicd/SICD_schema_V1.3.0_2021_11_30_FINAL.xsd new file mode 100644 index 0000000..c1ee10e --- /dev/null +++ b/schemas/sicd/SICD_schema_V1.3.0_2021_11_30_FINAL.xsd @@ -0,0 +1,1109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setup.cfg b/setup.cfg index 5390bc6..cb2cca5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -45,6 +45,7 @@ install_requires = geojson>=3.0.0 pyproj>=3.6.0 omegaconf==2.3.0;python_version<'3.10.0' + xsdata>=23.8 defusedxml>=0.7.1 [options.packages.find] diff --git a/src/aws/osml/formats/__init__.py b/src/aws/osml/formats/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/aws/osml/formats/sicd/__init__.py b/src/aws/osml/formats/sicd/__init__.py new file mode 100644 index 0000000..b2a4ba5 --- /dev/null +++ b/src/aws/osml/formats/sicd/__init__.py @@ -0,0 +1 @@ +# nothing here diff --git a/src/aws/osml/formats/sicd/models/__init__.py b/src/aws/osml/formats/sicd/models/__init__.py new file mode 100644 index 0000000..80d9651 --- /dev/null +++ b/src/aws/osml/formats/sicd/models/__init__.py @@ -0,0 +1,273 @@ +"""This file was generated by xsdata, v23.8, on 2023-08-30 17:21:13 + +Generator: DataclassGenerator +See: https://xsdata.readthedocs.io/ +""" +from .sicd_v1_2_1 import SICD as Type21SICD +from .sicd_v1_2_1 import AntennaType as Type21AntennaType +from .sicd_v1_2_1 import AntParamType as Type21AntParamType +from .sicd_v1_2_1 import ArrayDouble as Type21ArrayDouble +from .sicd_v1_2_1 import AzAutofocus as Type21AzAutofocus +from .sicd_v1_2_1 import CollectionInfoType as Type21CollectionInfoType +from .sicd_v1_2_1 import CollectType as Type21CollectType +from .sicd_v1_2_1 import ComplexType as Type21ComplexType +from .sicd_v1_2_1 import CornerStringType as Type21CornerStringType +from .sicd_v1_2_1 import DirParamType as Type21DirParamType +from .sicd_v1_2_1 import DirParamTypeSgn as Type21DirParamTypeSgn +from .sicd_v1_2_1 import DualPolarizationType +from .sicd_v1_2_1 import EarthModel as Type21EarthModel +from .sicd_v1_2_1 import ErrorDecorrFuncType as Type21ErrorDecorrFuncType +from .sicd_v1_2_1 import ErrorStatisticsType as Type21ErrorStatisticsType +from .sicd_v1_2_1 import GainPhasePolyType as Type21GainPhasePolyType +from .sicd_v1_2_1 import GeoDataType as Type21GeoDataType +from .sicd_v1_2_1 import GeoInfoType as Type21GeoInfoType +from .sicd_v1_2_1 import GridType as Type21GridType +from .sicd_v1_2_1 import ImageBeamComp as Type21ImageBeamComp +from .sicd_v1_2_1 import ImageCreationType as Type21ImageCreationType +from .sicd_v1_2_1 import ImageDataType as Type21ImageDataType +from .sicd_v1_2_1 import ImageFormAlgo as Type21ImageFormAlgo +from .sicd_v1_2_1 import ImageFormationType as Type21ImageFormationType +from .sicd_v1_2_1 import ImageGridType as Type21ImageGridType +from .sicd_v1_2_1 import ImagePixelType as Type21ImagePixelType +from .sicd_v1_2_1 import ImagePlane as Type21ImagePlane +from .sicd_v1_2_1 import LatLonCornerStringType as Type21LatLonCornerStringType +from .sicd_v1_2_1 import LatLonCornerType as Type21LatLonCornerType +from .sicd_v1_2_1 import LatLonHAECornerRestrictType as Type21LatLonHAECornerRestrictType +from .sicd_v1_2_1 import LatLonHAECornerStringType as Type21LatLonHAECornerStringType +from .sicd_v1_2_1 import LatLonHAERestrictionType as Type21LatLonHAERestrictionType +from .sicd_v1_2_1 import LatLonHAEType as Type21LatLonHAEType +from .sicd_v1_2_1 import LatLonRestrictionType as Type21LatLonRestrictionType +from .sicd_v1_2_1 import LatLonType as Type21LatLonType +from .sicd_v1_2_1 import LineType as Type21LineType +from .sicd_v1_2_1 import MatchInfoType as Type21MatchInfoType +from .sicd_v1_2_1 import NoiseLevelType as Type21NoiseLevelType +from .sicd_v1_2_1 import OrientationType as Type21OrientationType +from .sicd_v1_2_1 import ParameterType as Type21ParameterType +from .sicd_v1_2_1 import PFAType as Type21PFAType +from .sicd_v1_2_1 import Polarization1Type, Polarization2Type +from .sicd_v1_2_1 import Poly1DType as Type21Poly1DType +from .sicd_v1_2_1 import Poly2DType as Type21Poly2DType +from .sicd_v1_2_1 import PolyCoef1DType as Type21PolyCoef1DType +from .sicd_v1_2_1 import PolyCoef2DType as Type21PolyCoef2DType +from .sicd_v1_2_1 import PolygonType as Type21PolygonType +from .sicd_v1_2_1 import PositionType as Type21PositionType +from .sicd_v1_2_1 import PosVelErrFrame as Type21PosVelErrFrame +from .sicd_v1_2_1 import RadarCollectionType as Type21RadarCollectionType +from .sicd_v1_2_1 import RadarModeType as Type21RadarModeType +from .sicd_v1_2_1 import RadiometricType as Type21RadiometricType +from .sicd_v1_2_1 import RgAutofocus as Type21RgAutofocus +from .sicd_v1_2_1 import RgAzCompType as Type21RgAzCompType +from .sicd_v1_2_1 import RMAImageType as Type21RMAImageType +from .sicd_v1_2_1 import RMAlgorithm as Type21RMAlgorithm +from .sicd_v1_2_1 import RMAType as Type21RMAType +from .sicd_v1_2_1 import RowColType as Type21RowColType +from .sicd_v1_2_1 import RowColvertexType as Type21RowColvertexType +from .sicd_v1_2_1 import SCPCOAType as Type21SCPCOAType +from .sicd_v1_2_1 import SideOfTrack as Type21SideOfTrack +from .sicd_v1_2_1 import STBeamComp as Type21STBeamComp +from .sicd_v1_2_1 import TimelineType as Type21TimelineType +from .sicd_v1_2_1 import WFParametersRcvDemodType as Type21WFParametersRcvDemodType +from .sicd_v1_2_1 import XYZPolyAttributeType as Type21XYZPolyAttributeType +from .sicd_v1_2_1 import XYZPolyType as Type21XYZPolyType +from .sicd_v1_2_1 import XYZType as Type21XYZType +from .sicd_v1_3_0 import SICD as Type30SICD +from .sicd_v1_3_0 import AntennaType as Type30AntennaType +from .sicd_v1_3_0 import AntParamType as Type30AntParamType +from .sicd_v1_3_0 import ArrayDouble as Type30ArrayDouble +from .sicd_v1_3_0 import AzAutofocus as Type30AzAutofocus +from .sicd_v1_3_0 import CollectionInfoType as Type30CollectionInfoType +from .sicd_v1_3_0 import CollectType as Type30CollectType +from .sicd_v1_3_0 import ComplexType as Type30ComplexType +from .sicd_v1_3_0 import CornerStringType as Type30CornerStringType +from .sicd_v1_3_0 import DirParamType as Type30DirParamType +from .sicd_v1_3_0 import DirParamTypeSgn as Type30DirParamTypeSgn +from .sicd_v1_3_0 import EarthModel as Type30EarthModel +from .sicd_v1_3_0 import ErrorDecorrFuncType as Type30ErrorDecorrFuncType +from .sicd_v1_3_0 import ErrorStatisticsType as Type30ErrorStatisticsType +from .sicd_v1_3_0 import GainPhasePolyType as Type30GainPhasePolyType +from .sicd_v1_3_0 import GeoDataType as Type30GeoDataType +from .sicd_v1_3_0 import GeoInfoType as Type30GeoInfoType +from .sicd_v1_3_0 import GridType as Type30GridType +from .sicd_v1_3_0 import ImageBeamComp as Type30ImageBeamComp +from .sicd_v1_3_0 import ImageCreationType as Type30ImageCreationType +from .sicd_v1_3_0 import ImageDataType as Type30ImageDataType +from .sicd_v1_3_0 import ImageFormAlgo as Type30ImageFormAlgo +from .sicd_v1_3_0 import ImageFormationType as Type30ImageFormationType +from .sicd_v1_3_0 import ImageGridType as Type30ImageGridType +from .sicd_v1_3_0 import ImagePixelType as Type30ImagePixelType +from .sicd_v1_3_0 import ImagePlane as Type30ImagePlane +from .sicd_v1_3_0 import LatLonCornerStringType as Type30LatLonCornerStringType +from .sicd_v1_3_0 import LatLonCornerType as Type30LatLonCornerType +from .sicd_v1_3_0 import LatLonHAECornerRestrictType as Type30LatLonHAECornerRestrictType +from .sicd_v1_3_0 import LatLonHAECornerStringType as Type30LatLonHAECornerStringType +from .sicd_v1_3_0 import LatLonHAERestrictionType as Type30LatLonHAERestrictionType +from .sicd_v1_3_0 import LatLonHAEType as Type30LatLonHAEType +from .sicd_v1_3_0 import LatLonRestrictionType as Type30LatLonRestrictionType +from .sicd_v1_3_0 import LatLonType as Type30LatLonType +from .sicd_v1_3_0 import LineType as Type30LineType +from .sicd_v1_3_0 import MatchInfoType as Type30MatchInfoType +from .sicd_v1_3_0 import NoiseLevelType as Type30NoiseLevelType +from .sicd_v1_3_0 import OrientationType as Type30OrientationType +from .sicd_v1_3_0 import ParameterType as Type30ParameterType +from .sicd_v1_3_0 import PFAType as Type30PFAType +from .sicd_v1_3_0 import Poly1DType as Type30Poly1DType +from .sicd_v1_3_0 import Poly2DType as Type30Poly2DType +from .sicd_v1_3_0 import PolyCoef1DType as Type30PolyCoef1DType +from .sicd_v1_3_0 import PolyCoef2DType as Type30PolyCoef2DType +from .sicd_v1_3_0 import PolygonType as Type30PolygonType +from .sicd_v1_3_0 import PositionType as Type30PositionType +from .sicd_v1_3_0 import PosVelErrFrame as Type30PosVelErrFrame +from .sicd_v1_3_0 import RadarCollectionType as Type30RadarCollectionType +from .sicd_v1_3_0 import RadarModeType as Type30RadarModeType +from .sicd_v1_3_0 import RadiometricType as Type30RadiometricType +from .sicd_v1_3_0 import RgAutofocus as Type30RgAutofocus +from .sicd_v1_3_0 import RgAzCompType as Type30RgAzCompType +from .sicd_v1_3_0 import RMAImageType as Type30RMAImageType +from .sicd_v1_3_0 import RMAlgorithm as Type30RMAlgorithm +from .sicd_v1_3_0 import RMAType as Type30RMAType +from .sicd_v1_3_0 import RowColType as Type30RowColType +from .sicd_v1_3_0 import RowColvertexType as Type30RowColvertexType +from .sicd_v1_3_0 import SCPCOAType as Type30SCPCOAType +from .sicd_v1_3_0 import SideOfTrack as Type30SideOfTrack +from .sicd_v1_3_0 import STBeamComp as Type30STBeamComp +from .sicd_v1_3_0 import TimelineType as Type30TimelineType +from .sicd_v1_3_0 import WFParametersRcvDemodType as Type30WFParametersRcvDemodType +from .sicd_v1_3_0 import XYZPolyAttributeType as Type30XYZPolyAttributeType +from .sicd_v1_3_0 import XYZPolyType as Type30XYZPolyType +from .sicd_v1_3_0 import XYZType as Type30XYZType + +__all__ = [ + "Type21AntParamType", + "Type21AntennaType", + "Type21ArrayDouble", + "Type21CollectionInfoType", + "Type21CollectType", + "Type21ComplexType", + "Type21CornerStringType", + "Type21DirParamType", + "Type21DirParamTypeSgn", + "DualPolarizationType", + "Type21ErrorDecorrFuncType", + "Type21ErrorStatisticsType", + "Type21GainPhasePolyType", + "Type21GeoDataType", + "Type21EarthModel", + "Type21GeoInfoType", + "Type21GridType", + "Type21ImagePlane", + "Type21ImageGridType", + "Type21ImageCreationType", + "Type21ImageDataType", + "Type21ImagePixelType", + "Type21ImageFormationType", + "Type21AzAutofocus", + "Type21ImageBeamComp", + "Type21ImageFormAlgo", + "Type21RgAutofocus", + "Type21STBeamComp", + "Type21LatLonCornerStringType", + "Type21LatLonCornerType", + "Type21LatLonHAECornerRestrictType", + "Type21LatLonHAECornerStringType", + "Type21LatLonHAERestrictionType", + "Type21LatLonHAEType", + "Type21LatLonRestrictionType", + "Type21LatLonType", + "Type21LineType", + "Type21MatchInfoType", + "Type21NoiseLevelType", + "Type21OrientationType", + "Type21PFAType", + "Type21ParameterType", + "Polarization1Type", + "Polarization2Type", + "Type21Poly1DType", + "Type21Poly2DType", + "Type21PolyCoef1DType", + "Type21PolyCoef2DType", + "Type21PolygonType", + "Type21PosVelErrFrame", + "Type21PositionType", + "Type21RMAType", + "Type21RMAImageType", + "Type21RMAlgorithm", + "Type21RadarCollectionType", + "Type21RadarModeType", + "Type21RadiometricType", + "Type21RgAzCompType", + "Type21RowColType", + "Type21RowColvertexType", + "Type21SCPCOAType", + "Type21SideOfTrack", + "Type21SICD", + "Type21TimelineType", + "Type21WFParametersRcvDemodType", + "Type21XYZPolyAttributeType", + "Type21XYZPolyType", + "Type21XYZType", + "Type30AntParamType", + "Type30AntennaType", + "Type30ArrayDouble", + "Type30CollectionInfoType", + "Type30CollectType", + "Type30ComplexType", + "Type30CornerStringType", + "Type30DirParamType", + "Type30DirParamTypeSgn", + "Type30ErrorDecorrFuncType", + "Type30ErrorStatisticsType", + "Type30GainPhasePolyType", + "Type30GeoDataType", + "Type30EarthModel", + "Type30GeoInfoType", + "Type30GridType", + "Type30ImagePlane", + "Type30ImageGridType", + "Type30ImageCreationType", + "Type30ImageDataType", + "Type30ImagePixelType", + "Type30ImageFormationType", + "Type30AzAutofocus", + "Type30ImageBeamComp", + "Type30ImageFormAlgo", + "Type30RgAutofocus", + "Type30STBeamComp", + "Type30LatLonCornerStringType", + "Type30LatLonCornerType", + "Type30LatLonHAECornerRestrictType", + "Type30LatLonHAECornerStringType", + "Type30LatLonHAERestrictionType", + "Type30LatLonHAEType", + "Type30LatLonRestrictionType", + "Type30LatLonType", + "Type30LineType", + "Type30MatchInfoType", + "Type30NoiseLevelType", + "Type30OrientationType", + "Type30PFAType", + "Type30ParameterType", + "Type30Poly1DType", + "Type30Poly2DType", + "Type30PolyCoef1DType", + "Type30PolyCoef2DType", + "Type30PolygonType", + "Type30PosVelErrFrame", + "Type30PositionType", + "Type30RMAType", + "Type30RMAImageType", + "Type30RMAlgorithm", + "Type30RadarCollectionType", + "Type30RadarModeType", + "Type30RadiometricType", + "Type30RgAzCompType", + "Type30RowColType", + "Type30RowColvertexType", + "Type30SCPCOAType", + "Type30SideOfTrack", + "Type30SICD", + "Type30TimelineType", + "Type30WFParametersRcvDemodType", + "Type30XYZPolyAttributeType", + "Type30XYZPolyType", + "Type30XYZType", +] diff --git a/src/aws/osml/formats/sicd/models/sicd_v1_2_1.py b/src/aws/osml/formats/sicd/models/sicd_v1_2_1.py new file mode 100644 index 0000000..a458140 --- /dev/null +++ b/src/aws/osml/formats/sicd/models/sicd_v1_2_1.py @@ -0,0 +1,3622 @@ +"""This file was generated by xsdata, v23.8, on 2023-08-30 17:21:13 + +Generator: DataclassGenerator +See: https://xsdata.readthedocs.io/ +""" +from dataclasses import dataclass, field +from enum import Enum +from typing import List, Optional + +from xsdata.models.datatype import XmlDateTime + +__NAMESPACE__ = "urn:SICD:1.2.1" + + +@dataclass +class ArrayDouble: + value: Optional[float] = field( + default=None, + metadata={ + "required": True, + }, + ) + index: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +class CollectType(Enum): + MONOSTATIC = "MONOSTATIC" + BISTATIC = "BISTATIC" + + +@dataclass +class ComplexType: + real: Optional[float] = field( + default=None, + metadata={ + "name": "Real", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + imag: Optional[float] = field( + default=None, + metadata={ + "name": "Imag", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + + +class CornerStringType(Enum): + FRFC_1 = "1:FRFC" + FRLC_2 = "2:FRLC" + LRLC_3 = "3:LRLC" + LRFC_4 = "4:LRFC" + + +class DirParamTypeSgn(Enum): + PLUS_ONE = 1 + MINUS_ONE = -1 + + +class DualPolarizationType(Enum): + V_V = "V:V" + V_H = "V:H" + V_RHC = "V:RHC" + V_LHC = "V:LHC" + H_V = "H:V" + H_H = "H:H" + H_RHC = "H:RHC" + H_LHC = "H:LHC" + RHC_RHC = "RHC:RHC" + RHC_LHC = "RHC:LHC" + RHC_V = "RHC:V" + RHC_H = "RHC:H" + LHC_RHC = "LHC:RHC" + LHC_LHC = "LHC:LHC" + LHC_V = "LHC:V" + LHC_H = "LHC:H" + OTHER = "OTHER" + UNKNOWN = "UNKNOWN" + + +@dataclass +class ErrorDecorrFuncType: + corr_coef_zero: Optional[float] = field( + default=None, + metadata={ + "name": "CorrCoefZero", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + decorr_rate: Optional[float] = field( + default=None, + metadata={ + "name": "DecorrRate", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + + +class EarthModel(Enum): + WGS_84 = "WGS_84" + + +class ImagePlane(Enum): + SLANT = "SLANT" + GROUND = "GROUND" + OTHER = "OTHER" + + +class ImageGridType(Enum): + RGAZIM = "RGAZIM" + RGZERO = "RGZERO" + XRGYCR = "XRGYCR" + XCTYAT = "XCTYAT" + PLANE = "PLANE" + + +@dataclass +class ImageCreationType: + application: Optional[str] = field( + default=None, + metadata={ + "name": "Application", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + date_time: Optional[XmlDateTime] = field( + default=None, + metadata={ + "name": "DateTime", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + site: Optional[str] = field( + default=None, + metadata={ + "name": "Site", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + profile: Optional[str] = field( + default=None, + metadata={ + "name": "Profile", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + + +class ImagePixelType(Enum): + RE32F_IM32F = "RE32F_IM32F" + RE16I_IM16I = "RE16I_IM16I" + AMP8I_PHS8I = "AMP8I_PHS8I" + + +class AzAutofocus(Enum): + NO = "NO" + GLOBAL = "GLOBAL" + SV = "SV" + + +class ImageBeamComp(Enum): + NO = "NO" + SV = "SV" + + +class ImageFormAlgo(Enum): + PFA = "PFA" + RMA = "RMA" + RGAZCOMP = "RGAZCOMP" + OTHER = "OTHER" + + +class RgAutofocus(Enum): + NO = "NO" + GLOBAL = "GLOBAL" + SV = "SV" + + +class STBeamComp(Enum): + NO = "NO" + GLOBAL = "GLOBAL" + SV = "SV" + + +@dataclass +class LatLonHAERestrictionType: + lat: Optional[float] = field( + default=None, + metadata={ + "name": "Lat", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + "min_inclusive": -90.0, + "max_inclusive": 90.0, + }, + ) + lon: Optional[float] = field( + default=None, + metadata={ + "name": "Lon", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + "min_inclusive": -180.0, + "max_inclusive": 180.0, + }, + ) + hae: Optional[float] = field( + default=None, + metadata={ + "name": "HAE", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + + +@dataclass +class LatLonHAEType: + lat: Optional[float] = field( + default=None, + metadata={ + "name": "Lat", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + lon: Optional[float] = field( + default=None, + metadata={ + "name": "Lon", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + hae: Optional[float] = field( + default=None, + metadata={ + "name": "HAE", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + + +@dataclass +class LatLonRestrictionType: + lat: Optional[float] = field( + default=None, + metadata={ + "name": "Lat", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + "min_inclusive": -90.0, + "max_inclusive": 90.0, + }, + ) + lon: Optional[float] = field( + default=None, + metadata={ + "name": "Lon", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + "min_inclusive": -180.0, + "max_inclusive": 180.0, + }, + ) + + +@dataclass +class LatLonType: + lat: Optional[float] = field( + default=None, + metadata={ + "name": "Lat", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + lon: Optional[float] = field( + default=None, + metadata={ + "name": "Lon", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + + +class NoiseLevelType(Enum): + ABSOLUTE = "ABSOLUTE" + RELATIVE = "RELATIVE" + + +class OrientationType(Enum): + UP = "UP" + DOWN = "DOWN" + LEFT = "LEFT" + RIGHT = "RIGHT" + ARBITRARY = "ARBITRARY" + + +@dataclass +class ParameterType: + value: str = field( + default="", + metadata={ + "required": True, + }, + ) + name: Optional[str] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +class Polarization1Type(Enum): + V = "V" + H = "H" + RHC = "RHC" + LHC = "LHC" + OTHER = "OTHER" + UNKNOWN = "UNKNOWN" + SEQUENCE = "SEQUENCE" + + +class Polarization2Type(Enum): + V = "V" + H = "H" + RHC = "RHC" + LHC = "LHC" + OTHER = "OTHER" + + +@dataclass +class PolyCoef1DType: + value: Optional[float] = field( + default=None, + metadata={ + "required": True, + }, + ) + exponent1: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class PolyCoef2DType: + value: Optional[float] = field( + default=None, + metadata={ + "required": True, + }, + ) + exponent1: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + exponent2: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +class PosVelErrFrame(Enum): + ECF = "ECF" + RIC_ECF = "RIC_ECF" + RIC_ECI = "RIC_ECI" + + +class RMAImageType(Enum): + RMAT = "RMAT" + RMCR = "RMCR" + INCA = "INCA" + + +class RMAlgorithm(Enum): + OMEGA_K = "OMEGA_K" + CSA = "CSA" + RG_DOP = "RG_DOP" + + +class RadarModeType(Enum): + SPOTLIGHT = "SPOTLIGHT" + STRIPMAP = "STRIPMAP" + DYNAMIC_STRIPMAP = "DYNAMIC STRIPMAP" + + +@dataclass +class RowColType: + row: Optional[int] = field( + default=None, + metadata={ + "name": "Row", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + col: Optional[int] = field( + default=None, + metadata={ + "name": "Col", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + + +class SideOfTrack(Enum): + L = "L" + R = "R" + + +class WFParametersRcvDemodType(Enum): + STRETCH = "STRETCH" + CHIRP = "CHIRP" + + +@dataclass +class XYZType: + x: Optional[float] = field( + default=None, + metadata={ + "name": "X", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + y: Optional[float] = field( + default=None, + metadata={ + "name": "Y", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + z: Optional[float] = field( + default=None, + metadata={ + "name": "Z", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + + +@dataclass +class CollectionInfoType: + collector_name: Optional[str] = field( + default=None, + metadata={ + "name": "CollectorName", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + illuminator_name: Optional[str] = field( + default=None, + metadata={ + "name": "IlluminatorName", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + core_name: Optional[str] = field( + default=None, + metadata={ + "name": "CoreName", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + collect_type: Optional[CollectType] = field( + default=None, + metadata={ + "name": "CollectType", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + radar_mode: Optional["CollectionInfoType.RadarMode"] = field( + default=None, + metadata={ + "name": "RadarMode", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + classification: Optional[str] = field( + default=None, + metadata={ + "name": "Classification", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + country_code: List[str] = field( + default_factory=list, + metadata={ + "name": "CountryCode", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + parameter: List[ParameterType] = field( + default_factory=list, + metadata={ + "name": "Parameter", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + + @dataclass + class RadarMode: + mode_type: Optional[RadarModeType] = field( + default=None, + metadata={ + "name": "ModeType", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + mode_id: Optional[str] = field( + default=None, + metadata={ + "name": "ModeID", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + + +@dataclass +class ErrorStatisticsType: + composite_scp: Optional["ErrorStatisticsType.CompositeSCP"] = field( + default=None, + metadata={ + "name": "CompositeSCP", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + components: Optional["ErrorStatisticsType.Components"] = field( + default=None, + metadata={ + "name": "Components", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + additional_parms: Optional["ErrorStatisticsType.AdditionalParms"] = field( + default=None, + metadata={ + "name": "AdditionalParms", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + + @dataclass + class CompositeSCP: + rg: Optional[float] = field( + default=None, + metadata={ + "name": "Rg", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + az: Optional[float] = field( + default=None, + metadata={ + "name": "Az", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + rg_az: Optional[float] = field( + default=None, + metadata={ + "name": "RgAz", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + + @dataclass + class Components: + pos_vel_err: Optional["ErrorStatisticsType.Components.PosVelErr"] = field( + default=None, + metadata={ + "name": "PosVelErr", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + radar_sensor: Optional["ErrorStatisticsType.Components.RadarSensor"] = field( + default=None, + metadata={ + "name": "RadarSensor", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + tropo_error: Optional["ErrorStatisticsType.Components.TropoError"] = field( + default=None, + metadata={ + "name": "TropoError", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + iono_error: Optional["ErrorStatisticsType.Components.IonoError"] = field( + default=None, + metadata={ + "name": "IonoError", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + + @dataclass + class PosVelErr: + frame: Optional[PosVelErrFrame] = field( + default=None, + metadata={ + "name": "Frame", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + p1: Optional[float] = field( + default=None, + metadata={ + "name": "P1", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + p2: Optional[float] = field( + default=None, + metadata={ + "name": "P2", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + p3: Optional[float] = field( + default=None, + metadata={ + "name": "P3", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + v1: Optional[float] = field( + default=None, + metadata={ + "name": "V1", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + v2: Optional[float] = field( + default=None, + metadata={ + "name": "V2", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + v3: Optional[float] = field( + default=None, + metadata={ + "name": "V3", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + corr_coefs: Optional["ErrorStatisticsType.Components.PosVelErr.CorrCoefs"] = field( + default=None, + metadata={ + "name": "CorrCoefs", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + position_decorr: Optional[ErrorDecorrFuncType] = field( + default=None, + metadata={ + "name": "PositionDecorr", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + + @dataclass + class CorrCoefs: + p1_p2: Optional[float] = field( + default=None, + metadata={ + "name": "P1P2", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + p1_p3: Optional[float] = field( + default=None, + metadata={ + "name": "P1P3", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + p1_v1: Optional[float] = field( + default=None, + metadata={ + "name": "P1V1", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + p1_v2: Optional[float] = field( + default=None, + metadata={ + "name": "P1V2", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + p1_v3: Optional[float] = field( + default=None, + metadata={ + "name": "P1V3", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + p2_p3: Optional[float] = field( + default=None, + metadata={ + "name": "P2P3", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + p2_v1: Optional[float] = field( + default=None, + metadata={ + "name": "P2V1", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + p2_v2: Optional[float] = field( + default=None, + metadata={ + "name": "P2V2", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + p2_v3: Optional[float] = field( + default=None, + metadata={ + "name": "P2V3", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + p3_v1: Optional[float] = field( + default=None, + metadata={ + "name": "P3V1", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + p3_v2: Optional[float] = field( + default=None, + metadata={ + "name": "P3V2", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + p3_v3: Optional[float] = field( + default=None, + metadata={ + "name": "P3V3", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + v1_v2: Optional[float] = field( + default=None, + metadata={ + "name": "V1V2", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + v1_v3: Optional[float] = field( + default=None, + metadata={ + "name": "V1V3", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + v2_v3: Optional[float] = field( + default=None, + metadata={ + "name": "V2V3", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + + @dataclass + class RadarSensor: + range_bias: Optional[float] = field( + default=None, + metadata={ + "name": "RangeBias", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + clock_freq_sf: Optional[float] = field( + default=None, + metadata={ + "name": "ClockFreqSF", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + transmit_freq_sf: Optional[float] = field( + default=None, + metadata={ + "name": "TransmitFreqSF", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + range_bias_decorr: Optional[ErrorDecorrFuncType] = field( + default=None, + metadata={ + "name": "RangeBiasDecorr", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + + @dataclass + class TropoError: + tropo_range_vertical: Optional[float] = field( + default=None, + metadata={ + "name": "TropoRangeVertical", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + tropo_range_slant: Optional[float] = field( + default=None, + metadata={ + "name": "TropoRangeSlant", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + tropo_range_decorr: Optional[ErrorDecorrFuncType] = field( + default=None, + metadata={ + "name": "TropoRangeDecorr", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + + @dataclass + class IonoError: + iono_range_vertical: Optional[float] = field( + default=None, + metadata={ + "name": "IonoRangeVertical", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + iono_range_rate_vertical: Optional[float] = field( + default=None, + metadata={ + "name": "IonoRangeRateVertical", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + iono_rg_rg_rate_cc: Optional[float] = field( + default=None, + metadata={ + "name": "IonoRgRgRateCC", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + iono_range_vert_decorr: Optional[ErrorDecorrFuncType] = field( + default=None, + metadata={ + "name": "IonoRangeVertDecorr", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + + @dataclass + class AdditionalParms: + parameter: List[ParameterType] = field( + default_factory=list, + metadata={ + "name": "Parameter", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "min_occurs": 1, + }, + ) + + +@dataclass +class ImageFormationType: + rcv_chan_proc: Optional["ImageFormationType.RcvChanProc"] = field( + default=None, + metadata={ + "name": "RcvChanProc", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + tx_rcv_polarization_proc: Optional[DualPolarizationType] = field( + default=None, + metadata={ + "name": "TxRcvPolarizationProc", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + tstart_proc: Optional[float] = field( + default=None, + metadata={ + "name": "TStartProc", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + tend_proc: Optional[float] = field( + default=None, + metadata={ + "name": "TEndProc", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + tx_frequency_proc: Optional["ImageFormationType.TxFrequencyProc"] = field( + default=None, + metadata={ + "name": "TxFrequencyProc", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + segment_identifier: Optional[str] = field( + default=None, + metadata={ + "name": "SegmentIdentifier", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + image_form_algo: Optional[ImageFormAlgo] = field( + default=None, + metadata={ + "name": "ImageFormAlgo", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + stbeam_comp: Optional[STBeamComp] = field( + default=None, + metadata={ + "name": "STBeamComp", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + image_beam_comp: Optional[ImageBeamComp] = field( + default=None, + metadata={ + "name": "ImageBeamComp", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + az_autofocus: Optional[AzAutofocus] = field( + default=None, + metadata={ + "name": "AzAutofocus", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + rg_autofocus: Optional[RgAutofocus] = field( + default=None, + metadata={ + "name": "RgAutofocus", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + processing: List["ImageFormationType.Processing"] = field( + default_factory=list, + metadata={ + "name": "Processing", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + polarization_calibration: Optional["ImageFormationType.PolarizationCalibration"] = field( + default=None, + metadata={ + "name": "PolarizationCalibration", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + + @dataclass + class RcvChanProc: + num_chan_proc: Optional[int] = field( + default=None, + metadata={ + "name": "NumChanProc", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + prfscale_factor: Optional[float] = field( + default=None, + metadata={ + "name": "PRFScaleFactor", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + chan_index: List[int] = field( + default_factory=list, + metadata={ + "name": "ChanIndex", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "min_occurs": 1, + }, + ) + + @dataclass + class TxFrequencyProc: + min_proc: Optional[float] = field( + default=None, + metadata={ + "name": "MinProc", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + max_proc: Optional[float] = field( + default=None, + metadata={ + "name": "MaxProc", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + + @dataclass + class Processing: + type_value: Optional[str] = field( + default=None, + metadata={ + "name": "Type", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + applied: Optional[bool] = field( + default=None, + metadata={ + "name": "Applied", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + parameter: List[ParameterType] = field( + default_factory=list, + metadata={ + "name": "Parameter", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + + @dataclass + class PolarizationCalibration: + distort_correction_applied: Optional[bool] = field( + default=None, + metadata={ + "name": "DistortCorrectionApplied", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + distortion: Optional["ImageFormationType.PolarizationCalibration.Distortion"] = field( + default=None, + metadata={ + "name": "Distortion", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + + @dataclass + class Distortion: + calibration_date: Optional[XmlDateTime] = field( + default=None, + metadata={ + "name": "CalibrationDate", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + a: Optional[float] = field( + default=None, + metadata={ + "name": "A", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + f1: Optional[ComplexType] = field( + default=None, + metadata={ + "name": "F1", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + q1: Optional[ComplexType] = field( + default=None, + metadata={ + "name": "Q1", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + q2: Optional[ComplexType] = field( + default=None, + metadata={ + "name": "Q2", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + f2: Optional[ComplexType] = field( + default=None, + metadata={ + "name": "F2", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + q3: Optional[ComplexType] = field( + default=None, + metadata={ + "name": "Q3", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + q4: Optional[ComplexType] = field( + default=None, + metadata={ + "name": "Q4", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + gain_error_a: Optional[float] = field( + default=None, + metadata={ + "name": "GainErrorA", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + gain_error_f1: Optional[float] = field( + default=None, + metadata={ + "name": "GainErrorF1", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + gain_error_f2: Optional[float] = field( + default=None, + metadata={ + "name": "GainErrorF2", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + phase_error_f1: Optional[float] = field( + default=None, + metadata={ + "name": "PhaseErrorF1", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + phase_error_f2: Optional[float] = field( + default=None, + metadata={ + "name": "PhaseErrorF2", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + + +@dataclass +class LatLonCornerStringType(LatLonType): + index: Optional[CornerStringType] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class LatLonCornerType(LatLonType): + index: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + "min_inclusive": 1, + "max_inclusive": 4, + }, + ) + + +@dataclass +class LatLonHAECornerRestrictType(LatLonHAERestrictionType): + index: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + "min_inclusive": 1, + "max_inclusive": 4, + }, + ) + + +@dataclass +class LatLonHAECornerStringType(LatLonHAEType): + index: Optional[CornerStringType] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class LineType: + endpoint: List["LineType.Endpoint"] = field( + default_factory=list, + metadata={ + "name": "Endpoint", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "min_occurs": 2, + }, + ) + size: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + @dataclass + class Endpoint(LatLonType): + index: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class MatchInfoType: + num_match_types: Optional[int] = field( + default=None, + metadata={ + "name": "NumMatchTypes", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + match_type: List["MatchInfoType.MatchType"] = field( + default_factory=list, + metadata={ + "name": "MatchType", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "min_occurs": 1, + }, + ) + + @dataclass + class MatchType: + type_id: Optional[str] = field( + default=None, + metadata={ + "name": "TypeID", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + current_index: Optional[int] = field( + default=None, + metadata={ + "name": "CurrentIndex", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + num_match_collections: Optional[int] = field( + default=None, + metadata={ + "name": "NumMatchCollections", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + match_collection: List["MatchInfoType.MatchType.MatchCollection"] = field( + default_factory=list, + metadata={ + "name": "MatchCollection", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + index: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + @dataclass + class MatchCollection: + core_name: Optional[str] = field( + default=None, + metadata={ + "name": "CoreName", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + match_index: Optional[int] = field( + default=None, + metadata={ + "name": "MatchIndex", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + parameter: List[ParameterType] = field( + default_factory=list, + metadata={ + "name": "Parameter", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + index: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class Poly1DType: + coef: List[PolyCoef1DType] = field( + default_factory=list, + metadata={ + "name": "Coef", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "min_occurs": 1, + }, + ) + order1: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class Poly2DType: + coef: List[PolyCoef2DType] = field( + default_factory=list, + metadata={ + "name": "Coef", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "min_occurs": 1, + }, + ) + order1: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + order2: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class PolygonType: + vertex: List["PolygonType.Vertex"] = field( + default_factory=list, + metadata={ + "name": "Vertex", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "min_occurs": 3, + }, + ) + size: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + @dataclass + class Vertex(LatLonRestrictionType): + index: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class RowColvertexType(RowColType): + index: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class SCPCOAType: + scptime: Optional[float] = field( + default=None, + metadata={ + "name": "SCPTime", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + arppos: Optional[XYZType] = field( + default=None, + metadata={ + "name": "ARPPos", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + arpvel: Optional[XYZType] = field( + default=None, + metadata={ + "name": "ARPVel", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + arpacc: Optional[XYZType] = field( + default=None, + metadata={ + "name": "ARPAcc", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + side_of_track: Optional[SideOfTrack] = field( + default=None, + metadata={ + "name": "SideOfTrack", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + slant_range: Optional[float] = field( + default=None, + metadata={ + "name": "SlantRange", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + ground_range: Optional[float] = field( + default=None, + metadata={ + "name": "GroundRange", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + doppler_cone_ang: Optional[float] = field( + default=None, + metadata={ + "name": "DopplerConeAng", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + graze_ang: Optional[float] = field( + default=None, + metadata={ + "name": "GrazeAng", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + "min_inclusive": 0.0, + "max_inclusive": 90.0, + }, + ) + incidence_ang: Optional[float] = field( + default=None, + metadata={ + "name": "IncidenceAng", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + "min_inclusive": 0.0, + "max_inclusive": 90.0, + }, + ) + twist_ang: Optional[float] = field( + default=None, + metadata={ + "name": "TwistAng", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + "min_inclusive": -90.0, + "max_inclusive": 90.0, + }, + ) + slope_ang: Optional[float] = field( + default=None, + metadata={ + "name": "SlopeAng", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + "min_inclusive": 0.0, + "max_inclusive": 90.0, + }, + ) + azim_ang: Optional[float] = field( + default=None, + metadata={ + "name": "AzimAng", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + "min_inclusive": 0.0, + "max_inclusive": 360.0, + }, + ) + layover_ang: Optional[float] = field( + default=None, + metadata={ + "name": "LayoverAng", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + "min_inclusive": 0.0, + "max_inclusive": 360.0, + }, + ) + + +@dataclass +class DirParamType: + uvect_ecf: Optional[XYZType] = field( + default=None, + metadata={ + "name": "UVectECF", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + ss: Optional[float] = field( + default=None, + metadata={ + "name": "SS", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + imp_resp_wid: Optional[float] = field( + default=None, + metadata={ + "name": "ImpRespWid", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + sgn: Optional[DirParamTypeSgn] = field( + default=None, + metadata={ + "name": "Sgn", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + imp_resp_bw: Optional[float] = field( + default=None, + metadata={ + "name": "ImpRespBW", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + kctr: Optional[float] = field( + default=None, + metadata={ + "name": "KCtr", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + delta_k1: Optional[float] = field( + default=None, + metadata={ + "name": "DeltaK1", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + delta_k2: Optional[float] = field( + default=None, + metadata={ + "name": "DeltaK2", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + delta_kcoapoly: Optional[Poly2DType] = field( + default=None, + metadata={ + "name": "DeltaKCOAPoly", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + wgt_type: Optional["DirParamType.WgtType"] = field( + default=None, + metadata={ + "name": "WgtType", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + wgt_funct: Optional["DirParamType.WgtFunct"] = field( + default=None, + metadata={ + "name": "WgtFunct", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + + @dataclass + class WgtType: + window_name: Optional[str] = field( + default=None, + metadata={ + "name": "WindowName", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + parameter: List[ParameterType] = field( + default_factory=list, + metadata={ + "name": "Parameter", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + + @dataclass + class WgtFunct: + wgt: List[ArrayDouble] = field( + default_factory=list, + metadata={ + "name": "Wgt", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "min_occurs": 2, + }, + ) + size: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class GainPhasePolyType: + gain_poly: Optional[Poly2DType] = field( + default=None, + metadata={ + "name": "GainPoly", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + phase_poly: Optional[Poly2DType] = field( + default=None, + metadata={ + "name": "PhasePoly", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + + +@dataclass +class GeoInfoType: + desc: List[ParameterType] = field( + default_factory=list, + metadata={ + "name": "Desc", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + point: Optional[LatLonRestrictionType] = field( + default=None, + metadata={ + "name": "Point", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + line: Optional[LineType] = field( + default=None, + metadata={ + "name": "Line", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + polygon: Optional[PolygonType] = field( + default=None, + metadata={ + "name": "Polygon", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + geo_info: List["GeoInfoType"] = field( + default_factory=list, + metadata={ + "name": "GeoInfo", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + name: Optional[str] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class ImageDataType: + pixel_type: Optional[ImagePixelType] = field( + default=None, + metadata={ + "name": "PixelType", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + amp_table: Optional["ImageDataType.AmpTable"] = field( + default=None, + metadata={ + "name": "AmpTable", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + num_rows: Optional[int] = field( + default=None, + metadata={ + "name": "NumRows", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + num_cols: Optional[int] = field( + default=None, + metadata={ + "name": "NumCols", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + first_row: Optional[int] = field( + default=None, + metadata={ + "name": "FirstRow", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + first_col: Optional[int] = field( + default=None, + metadata={ + "name": "FirstCol", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + full_image: Optional["ImageDataType.FullImage"] = field( + default=None, + metadata={ + "name": "FullImage", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + scppixel: Optional[RowColType] = field( + default=None, + metadata={ + "name": "SCPPixel", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + valid_data: Optional["ImageDataType.ValidData"] = field( + default=None, + metadata={ + "name": "ValidData", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + + @dataclass + class AmpTable: + amplitude: List[ArrayDouble] = field( + default_factory=list, + metadata={ + "name": "Amplitude", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "min_occurs": 256, + "max_occurs": 256, + }, + ) + size: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + @dataclass + class FullImage: + num_rows: Optional[int] = field( + default=None, + metadata={ + "name": "NumRows", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + num_cols: Optional[int] = field( + default=None, + metadata={ + "name": "NumCols", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + + @dataclass + class ValidData: + vertex: List[RowColvertexType] = field( + default_factory=list, + metadata={ + "name": "Vertex", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "min_occurs": 3, + }, + ) + size: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class PFAType: + fpn: Optional[XYZType] = field( + default=None, + metadata={ + "name": "FPN", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + ipn: Optional[XYZType] = field( + default=None, + metadata={ + "name": "IPN", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + polar_ang_ref_time: Optional[float] = field( + default=None, + metadata={ + "name": "PolarAngRefTime", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + polar_ang_poly: Optional[Poly1DType] = field( + default=None, + metadata={ + "name": "PolarAngPoly", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + spatial_freq_sfpoly: Optional[Poly1DType] = field( + default=None, + metadata={ + "name": "SpatialFreqSFPoly", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + krg1: Optional[float] = field( + default=None, + metadata={ + "name": "Krg1", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + krg2: Optional[float] = field( + default=None, + metadata={ + "name": "Krg2", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + kaz1: Optional[float] = field( + default=None, + metadata={ + "name": "Kaz1", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + kaz2: Optional[float] = field( + default=None, + metadata={ + "name": "Kaz2", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + stdeskew: Optional["PFAType.STDeskew"] = field( + default=None, + metadata={ + "name": "STDeskew", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + + @dataclass + class STDeskew: + applied: Optional[bool] = field( + default=None, + metadata={ + "name": "Applied", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + stdsphase_poly: Optional[Poly2DType] = field( + default=None, + metadata={ + "name": "STDSPhasePoly", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + + +@dataclass +class RMAType: + rmalgo_type: Optional[RMAlgorithm] = field( + default=None, + metadata={ + "name": "RMAlgoType", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + image_type: Optional[RMAImageType] = field( + default=None, + metadata={ + "name": "ImageType", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + rmat: Optional["RMAType.RMAT"] = field( + default=None, + metadata={ + "name": "RMAT", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + rmcr: Optional["RMAType.RMCR"] = field( + default=None, + metadata={ + "name": "RMCR", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + inca: Optional["RMAType.INCA"] = field( + default=None, + metadata={ + "name": "INCA", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + + @dataclass + class RMAT: + pos_ref: Optional[XYZType] = field( + default=None, + metadata={ + "name": "PosRef", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + vel_ref: Optional[XYZType] = field( + default=None, + metadata={ + "name": "VelRef", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + dop_cone_ang_ref: Optional[float] = field( + default=None, + metadata={ + "name": "DopConeAngRef", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + + @dataclass + class RMCR: + pos_ref: Optional[XYZType] = field( + default=None, + metadata={ + "name": "PosRef", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + vel_ref: Optional[XYZType] = field( + default=None, + metadata={ + "name": "VelRef", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + dop_cone_ang_ref: Optional[float] = field( + default=None, + metadata={ + "name": "DopConeAngRef", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + + @dataclass + class INCA: + time_capoly: Optional[Poly1DType] = field( + default=None, + metadata={ + "name": "TimeCAPoly", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + r_ca_scp: Optional[float] = field( + default=None, + metadata={ + "name": "R_CA_SCP", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + freq_zero: Optional[float] = field( + default=None, + metadata={ + "name": "FreqZero", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + drate_sfpoly: Optional[Poly2DType] = field( + default=None, + metadata={ + "name": "DRateSFPoly", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + dop_centroid_poly: Optional[Poly2DType] = field( + default=None, + metadata={ + "name": "DopCentroidPoly", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + dop_centroid_coa: Optional[bool] = field( + default=None, + metadata={ + "name": "DopCentroidCOA", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + + +@dataclass +class RadarCollectionType: + tx_frequency: Optional["RadarCollectionType.TxFrequency"] = field( + default=None, + metadata={ + "name": "TxFrequency", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + ref_freq_index: Optional[int] = field( + default=None, + metadata={ + "name": "RefFreqIndex", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + waveform: Optional["RadarCollectionType.Waveform"] = field( + default=None, + metadata={ + "name": "Waveform", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + tx_polarization: Optional[Polarization1Type] = field( + default=None, + metadata={ + "name": "TxPolarization", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + tx_sequence: Optional["RadarCollectionType.TxSequence"] = field( + default=None, + metadata={ + "name": "TxSequence", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + rcv_channels: Optional["RadarCollectionType.RcvChannels"] = field( + default=None, + metadata={ + "name": "RcvChannels", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + area: Optional["RadarCollectionType.Area"] = field( + default=None, + metadata={ + "name": "Area", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + parameter: List[ParameterType] = field( + default_factory=list, + metadata={ + "name": "Parameter", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + + @dataclass + class TxFrequency: + min: Optional[float] = field( + default=None, + metadata={ + "name": "Min", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + max: Optional[float] = field( + default=None, + metadata={ + "name": "Max", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + + @dataclass + class Waveform: + wfparameters: List["RadarCollectionType.Waveform.WFParameters"] = field( + default_factory=list, + metadata={ + "name": "WFParameters", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "min_occurs": 1, + }, + ) + size: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + @dataclass + class WFParameters: + tx_pulse_length: Optional[float] = field( + default=None, + metadata={ + "name": "TxPulseLength", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + tx_rfbandwidth: Optional[float] = field( + default=None, + metadata={ + "name": "TxRFBandwidth", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + tx_freq_start: Optional[float] = field( + default=None, + metadata={ + "name": "TxFreqStart", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + tx_fmrate: Optional[float] = field( + default=None, + metadata={ + "name": "TxFMRate", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + rcv_demod_type: Optional[WFParametersRcvDemodType] = field( + default=None, + metadata={ + "name": "RcvDemodType", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + rcv_window_length: Optional[float] = field( + default=None, + metadata={ + "name": "RcvWindowLength", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + adcsample_rate: Optional[float] = field( + default=None, + metadata={ + "name": "ADCSampleRate", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + rcv_ifbandwidth: Optional[float] = field( + default=None, + metadata={ + "name": "RcvIFBandwidth", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + rcv_freq_start: Optional[float] = field( + default=None, + metadata={ + "name": "RcvFreqStart", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + rcv_fmrate: Optional[float] = field( + default=None, + metadata={ + "name": "RcvFMRate", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + index: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + @dataclass + class TxSequence: + tx_step: List["RadarCollectionType.TxSequence.TxStep"] = field( + default_factory=list, + metadata={ + "name": "TxStep", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "min_occurs": 1, + }, + ) + size: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + @dataclass + class TxStep: + wfindex: Optional[int] = field( + default=None, + metadata={ + "name": "WFIndex", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + tx_polarization: Optional[Polarization2Type] = field( + default=None, + metadata={ + "name": "TxPolarization", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + index: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + @dataclass + class RcvChannels: + chan_parameters: List["RadarCollectionType.RcvChannels.ChanParameters"] = field( + default_factory=list, + metadata={ + "name": "ChanParameters", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "min_occurs": 1, + }, + ) + size: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + @dataclass + class ChanParameters: + tx_rcv_polarization: Optional[DualPolarizationType] = field( + default=None, + metadata={ + "name": "TxRcvPolarization", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + rcv_apcindex: Optional[int] = field( + default=None, + metadata={ + "name": "RcvAPCIndex", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + index: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + @dataclass + class Area: + corner: Optional["RadarCollectionType.Area.Corner"] = field( + default=None, + metadata={ + "name": "Corner", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + plane: Optional["RadarCollectionType.Area.Plane"] = field( + default=None, + metadata={ + "name": "Plane", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + + @dataclass + class Corner: + acp: List[LatLonHAECornerRestrictType] = field( + default_factory=list, + metadata={ + "name": "ACP", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "min_occurs": 4, + "max_occurs": 4, + }, + ) + + @dataclass + class Plane: + ref_pt: Optional["RadarCollectionType.Area.Plane.RefPt"] = field( + default=None, + metadata={ + "name": "RefPt", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + xdir: Optional["RadarCollectionType.Area.Plane.XDir"] = field( + default=None, + metadata={ + "name": "XDir", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + ydir: Optional["RadarCollectionType.Area.Plane.YDir"] = field( + default=None, + metadata={ + "name": "YDir", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + segment_list: Optional["RadarCollectionType.Area.Plane.SegmentList"] = field( + default=None, + metadata={ + "name": "SegmentList", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + orientation: Optional[OrientationType] = field( + default=None, + metadata={ + "name": "Orientation", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + + @dataclass + class RefPt: + ecf: Optional[XYZType] = field( + default=None, + metadata={ + "name": "ECF", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + line: Optional[float] = field( + default=None, + metadata={ + "name": "Line", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + sample: Optional[float] = field( + default=None, + metadata={ + "name": "Sample", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + name: Optional[str] = field( + default=None, + metadata={ + "type": "Attribute", + }, + ) + + @dataclass + class XDir: + uvect_ecf: Optional[XYZType] = field( + default=None, + metadata={ + "name": "UVectECF", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + line_spacing: Optional[float] = field( + default=None, + metadata={ + "name": "LineSpacing", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + num_lines: Optional[int] = field( + default=None, + metadata={ + "name": "NumLines", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + first_line: Optional[int] = field( + default=None, + metadata={ + "name": "FirstLine", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + + @dataclass + class YDir: + uvect_ecf: Optional[XYZType] = field( + default=None, + metadata={ + "name": "UVectECF", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + sample_spacing: Optional[float] = field( + default=None, + metadata={ + "name": "SampleSpacing", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + num_samples: Optional[int] = field( + default=None, + metadata={ + "name": "NumSamples", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + first_sample: Optional[int] = field( + default=None, + metadata={ + "name": "FirstSample", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + + @dataclass + class SegmentList: + segment: List["RadarCollectionType.Area.Plane.SegmentList.Segment"] = field( + default_factory=list, + metadata={ + "name": "Segment", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "min_occurs": 1, + }, + ) + size: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + @dataclass + class Segment: + start_line: Optional[int] = field( + default=None, + metadata={ + "name": "StartLine", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + start_sample: Optional[int] = field( + default=None, + metadata={ + "name": "StartSample", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + end_line: Optional[int] = field( + default=None, + metadata={ + "name": "EndLine", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + end_sample: Optional[int] = field( + default=None, + metadata={ + "name": "EndSample", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + identifier: Optional[str] = field( + default=None, + metadata={ + "name": "Identifier", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + index: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class RadiometricType: + noise_level: Optional["RadiometricType.NoiseLevel"] = field( + default=None, + metadata={ + "name": "NoiseLevel", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + rcssfpoly: Optional[Poly2DType] = field( + default=None, + metadata={ + "name": "RCSSFPoly", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + sigma_zero_sfpoly: Optional[Poly2DType] = field( + default=None, + metadata={ + "name": "SigmaZeroSFPoly", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + beta_zero_sfpoly: Optional[Poly2DType] = field( + default=None, + metadata={ + "name": "BetaZeroSFPoly", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + gamma_zero_sfpoly: Optional[Poly2DType] = field( + default=None, + metadata={ + "name": "GammaZeroSFPoly", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + + @dataclass + class NoiseLevel: + noise_level_type: Optional[NoiseLevelType] = field( + default=None, + metadata={ + "name": "NoiseLevelType", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + noise_poly: Optional[Poly2DType] = field( + default=None, + metadata={ + "name": "NoisePoly", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + + +@dataclass +class RgAzCompType: + az_sf: Optional[float] = field( + default=None, + metadata={ + "name": "AzSF", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + kaz_poly: Optional[Poly1DType] = field( + default=None, + metadata={ + "name": "KazPoly", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + + +@dataclass +class TimelineType: + collect_start: Optional[XmlDateTime] = field( + default=None, + metadata={ + "name": "CollectStart", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + collect_duration: Optional[float] = field( + default=None, + metadata={ + "name": "CollectDuration", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + ipp: Optional["TimelineType.IPP"] = field( + default=None, + metadata={ + "name": "IPP", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + + @dataclass + class IPP: + set: List["TimelineType.IPP.Set"] = field( + default_factory=list, + metadata={ + "name": "Set", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "min_occurs": 1, + }, + ) + size: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + @dataclass + class Set: + tstart: Optional[float] = field( + default=None, + metadata={ + "name": "TStart", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + tend: Optional[float] = field( + default=None, + metadata={ + "name": "TEnd", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + ippstart: Optional[int] = field( + default=None, + metadata={ + "name": "IPPStart", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + ippend: Optional[int] = field( + default=None, + metadata={ + "name": "IPPEnd", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + ipppoly: Optional[Poly1DType] = field( + default=None, + metadata={ + "name": "IPPPoly", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + index: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class XYZPolyType: + x: Optional[Poly1DType] = field( + default=None, + metadata={ + "name": "X", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + y: Optional[Poly1DType] = field( + default=None, + metadata={ + "name": "Y", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + z: Optional[Poly1DType] = field( + default=None, + metadata={ + "name": "Z", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + + +@dataclass +class AntParamType: + xaxis_poly: Optional[XYZPolyType] = field( + default=None, + metadata={ + "name": "XAxisPoly", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + yaxis_poly: Optional[XYZPolyType] = field( + default=None, + metadata={ + "name": "YAxisPoly", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + freq_zero: Optional[float] = field( + default=None, + metadata={ + "name": "FreqZero", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + eb: Optional["AntParamType.EB"] = field( + default=None, + metadata={ + "name": "EB", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + array: Optional[GainPhasePolyType] = field( + default=None, + metadata={ + "name": "Array", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + elem: Optional[GainPhasePolyType] = field( + default=None, + metadata={ + "name": "Elem", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + gain_bspoly: Optional[Poly1DType] = field( + default=None, + metadata={ + "name": "GainBSPoly", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + ebfreq_shift: Optional[bool] = field( + default=None, + metadata={ + "name": "EBFreqShift", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + mlfreq_dilation: Optional[bool] = field( + default=None, + metadata={ + "name": "MLFreqDilation", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + + @dataclass + class EB: + dcxpoly: Optional[Poly1DType] = field( + default=None, + metadata={ + "name": "DCXPoly", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + dcypoly: Optional[Poly1DType] = field( + default=None, + metadata={ + "name": "DCYPoly", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + + +@dataclass +class GeoDataType: + earth_model: Optional[EarthModel] = field( + default=None, + metadata={ + "name": "EarthModel", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + scp: Optional["GeoDataType.SCP"] = field( + default=None, + metadata={ + "name": "SCP", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + image_corners: Optional["GeoDataType.ImageCorners"] = field( + default=None, + metadata={ + "name": "ImageCorners", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + valid_data: Optional[PolygonType] = field( + default=None, + metadata={ + "name": "ValidData", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + geo_info: List[GeoInfoType] = field( + default_factory=list, + metadata={ + "name": "GeoInfo", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + + @dataclass + class SCP: + ecf: Optional[XYZType] = field( + default=None, + metadata={ + "name": "ECF", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + llh: Optional[LatLonHAERestrictionType] = field( + default=None, + metadata={ + "name": "LLH", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + + @dataclass + class ImageCorners: + icp: List[LatLonCornerStringType] = field( + default_factory=list, + metadata={ + "name": "ICP", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "min_occurs": 4, + "max_occurs": 4, + }, + ) + + +@dataclass +class GridType: + image_plane: Optional[ImagePlane] = field( + default=None, + metadata={ + "name": "ImagePlane", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + type_value: Optional[ImageGridType] = field( + default=None, + metadata={ + "name": "Type", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + time_coapoly: Optional[Poly2DType] = field( + default=None, + metadata={ + "name": "TimeCOAPoly", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + row: Optional[DirParamType] = field( + default=None, + metadata={ + "name": "Row", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + col: Optional[DirParamType] = field( + default=None, + metadata={ + "name": "Col", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + + +@dataclass +class XYZPolyAttributeType(XYZPolyType): + index: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class AntennaType: + tx: Optional[AntParamType] = field( + default=None, + metadata={ + "name": "Tx", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + rcv: Optional[AntParamType] = field( + default=None, + metadata={ + "name": "Rcv", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + two_way: Optional[AntParamType] = field( + default=None, + metadata={ + "name": "TwoWay", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + + +@dataclass +class PositionType: + arppoly: Optional[XYZPolyType] = field( + default=None, + metadata={ + "name": "ARPPoly", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "required": True, + }, + ) + grppoly: Optional[XYZPolyType] = field( + default=None, + metadata={ + "name": "GRPPoly", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + tx_apcpoly: Optional[XYZPolyType] = field( + default=None, + metadata={ + "name": "TxAPCPoly", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + rcv_apc: Optional["PositionType.RcvAPC"] = field( + default=None, + metadata={ + "name": "RcvAPC", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + }, + ) + + @dataclass + class RcvAPC: + rcv_apcpoly: List[XYZPolyAttributeType] = field( + default_factory=list, + metadata={ + "name": "RcvAPCPoly", + "type": "Element", + "namespace": "urn:SICD:1.2.1", + "min_occurs": 1, + }, + ) + size: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class SICD: + class Meta: + namespace = "urn:SICD:1.2.1" + + collection_info: Optional[CollectionInfoType] = field( + default=None, + metadata={ + "name": "CollectionInfo", + "type": "Element", + "required": True, + }, + ) + image_creation: Optional[ImageCreationType] = field( + default=None, + metadata={ + "name": "ImageCreation", + "type": "Element", + }, + ) + image_data: Optional[ImageDataType] = field( + default=None, + metadata={ + "name": "ImageData", + "type": "Element", + "required": True, + }, + ) + geo_data: Optional[GeoDataType] = field( + default=None, + metadata={ + "name": "GeoData", + "type": "Element", + "required": True, + }, + ) + grid: Optional[GridType] = field( + default=None, + metadata={ + "name": "Grid", + "type": "Element", + "required": True, + }, + ) + timeline: Optional[TimelineType] = field( + default=None, + metadata={ + "name": "Timeline", + "type": "Element", + "required": True, + }, + ) + position: Optional[PositionType] = field( + default=None, + metadata={ + "name": "Position", + "type": "Element", + "required": True, + }, + ) + radar_collection: Optional[RadarCollectionType] = field( + default=None, + metadata={ + "name": "RadarCollection", + "type": "Element", + "required": True, + }, + ) + image_formation: Optional[ImageFormationType] = field( + default=None, + metadata={ + "name": "ImageFormation", + "type": "Element", + "required": True, + }, + ) + scpcoa: Optional[SCPCOAType] = field( + default=None, + metadata={ + "name": "SCPCOA", + "type": "Element", + "required": True, + }, + ) + radiometric: Optional[RadiometricType] = field( + default=None, + metadata={ + "name": "Radiometric", + "type": "Element", + }, + ) + antenna: Optional[AntennaType] = field( + default=None, + metadata={ + "name": "Antenna", + "type": "Element", + }, + ) + error_statistics: Optional[ErrorStatisticsType] = field( + default=None, + metadata={ + "name": "ErrorStatistics", + "type": "Element", + }, + ) + match_info: Optional[MatchInfoType] = field( + default=None, + metadata={ + "name": "MatchInfo", + "type": "Element", + }, + ) + rg_az_comp: Optional[RgAzCompType] = field( + default=None, + metadata={ + "name": "RgAzComp", + "type": "Element", + }, + ) + pfa: Optional[PFAType] = field( + default=None, + metadata={ + "name": "PFA", + "type": "Element", + }, + ) + rma: Optional[RMAType] = field( + default=None, + metadata={ + "name": "RMA", + "type": "Element", + }, + ) diff --git a/src/aws/osml/formats/sicd/models/sicd_v1_3_0.py b/src/aws/osml/formats/sicd/models/sicd_v1_3_0.py new file mode 100644 index 0000000..f98f745 --- /dev/null +++ b/src/aws/osml/formats/sicd/models/sicd_v1_3_0.py @@ -0,0 +1,3654 @@ +"""This file was generated by xsdata, v23.8, on 2023-08-30 17:21:13 + +Generator: DataclassGenerator +See: https://xsdata.readthedocs.io/ +""" +from dataclasses import dataclass, field +from enum import Enum +from typing import List, Optional + +from xsdata.models.datatype import XmlDateTime + +__NAMESPACE__ = "urn:SICD:1.3.0" + + +@dataclass +class ArrayDouble: + value: Optional[float] = field( + default=None, + metadata={ + "required": True, + }, + ) + index: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +class CollectType(Enum): + MONOSTATIC = "MONOSTATIC" + BISTATIC = "BISTATIC" + + +@dataclass +class ComplexType: + real: Optional[float] = field( + default=None, + metadata={ + "name": "Real", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + imag: Optional[float] = field( + default=None, + metadata={ + "name": "Imag", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + + +class CornerStringType(Enum): + FRFC_1 = "1:FRFC" + FRLC_2 = "2:FRLC" + LRLC_3 = "3:LRLC" + LRFC_4 = "4:LRFC" + + +class DirParamTypeSgn(Enum): + PLUS_ONE = 1 + MINUS_ONE = -1 + + +@dataclass +class ErrorDecorrFuncType: + corr_coef_zero: Optional[float] = field( + default=None, + metadata={ + "name": "CorrCoefZero", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + decorr_rate: Optional[float] = field( + default=None, + metadata={ + "name": "DecorrRate", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + + +class EarthModel(Enum): + WGS_84 = "WGS_84" + + +class ImagePlane(Enum): + SLANT = "SLANT" + GROUND = "GROUND" + OTHER = "OTHER" + + +class ImageGridType(Enum): + RGAZIM = "RGAZIM" + RGZERO = "RGZERO" + XRGYCR = "XRGYCR" + XCTYAT = "XCTYAT" + PLANE = "PLANE" + + +@dataclass +class ImageCreationType: + application: Optional[str] = field( + default=None, + metadata={ + "name": "Application", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + date_time: Optional[XmlDateTime] = field( + default=None, + metadata={ + "name": "DateTime", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + site: Optional[str] = field( + default=None, + metadata={ + "name": "Site", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + profile: Optional[str] = field( + default=None, + metadata={ + "name": "Profile", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + + +class ImagePixelType(Enum): + RE32F_IM32F = "RE32F_IM32F" + RE16I_IM16I = "RE16I_IM16I" + AMP8I_PHS8I = "AMP8I_PHS8I" + + +class AzAutofocus(Enum): + NO = "NO" + GLOBAL = "GLOBAL" + SV = "SV" + + +class ImageBeamComp(Enum): + NO = "NO" + SV = "SV" + + +class ImageFormAlgo(Enum): + PFA = "PFA" + RMA = "RMA" + RGAZCOMP = "RGAZCOMP" + OTHER = "OTHER" + + +class RgAutofocus(Enum): + NO = "NO" + GLOBAL = "GLOBAL" + SV = "SV" + + +class STBeamComp(Enum): + NO = "NO" + GLOBAL = "GLOBAL" + SV = "SV" + + +@dataclass +class LatLonHAERestrictionType: + lat: Optional[float] = field( + default=None, + metadata={ + "name": "Lat", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + "min_inclusive": -90.0, + "max_inclusive": 90.0, + }, + ) + lon: Optional[float] = field( + default=None, + metadata={ + "name": "Lon", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + "min_inclusive": -180.0, + "max_inclusive": 180.0, + }, + ) + hae: Optional[float] = field( + default=None, + metadata={ + "name": "HAE", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + + +@dataclass +class LatLonHAEType: + lat: Optional[float] = field( + default=None, + metadata={ + "name": "Lat", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + lon: Optional[float] = field( + default=None, + metadata={ + "name": "Lon", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + hae: Optional[float] = field( + default=None, + metadata={ + "name": "HAE", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + + +@dataclass +class LatLonRestrictionType: + lat: Optional[float] = field( + default=None, + metadata={ + "name": "Lat", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + "min_inclusive": -90.0, + "max_inclusive": 90.0, + }, + ) + lon: Optional[float] = field( + default=None, + metadata={ + "name": "Lon", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + "min_inclusive": -180.0, + "max_inclusive": 180.0, + }, + ) + + +@dataclass +class LatLonType: + lat: Optional[float] = field( + default=None, + metadata={ + "name": "Lat", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + lon: Optional[float] = field( + default=None, + metadata={ + "name": "Lon", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + + +class NoiseLevelType(Enum): + ABSOLUTE = "ABSOLUTE" + RELATIVE = "RELATIVE" + + +class OrientationType(Enum): + UP = "UP" + DOWN = "DOWN" + LEFT = "LEFT" + RIGHT = "RIGHT" + ARBITRARY = "ARBITRARY" + + +@dataclass +class ParameterType: + value: str = field( + default="", + metadata={ + "required": True, + }, + ) + name: Optional[str] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class PolyCoef1DType: + value: Optional[float] = field( + default=None, + metadata={ + "required": True, + }, + ) + exponent1: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class PolyCoef2DType: + value: Optional[float] = field( + default=None, + metadata={ + "required": True, + }, + ) + exponent1: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + exponent2: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +class PosVelErrFrame(Enum): + ECF = "ECF" + RIC_ECF = "RIC_ECF" + RIC_ECI = "RIC_ECI" + + +class RMAImageType(Enum): + RMAT = "RMAT" + RMCR = "RMCR" + INCA = "INCA" + + +class RMAlgorithm(Enum): + OMEGA_K = "OMEGA_K" + CSA = "CSA" + RG_DOP = "RG_DOP" + + +class RadarModeType(Enum): + SPOTLIGHT = "SPOTLIGHT" + STRIPMAP = "STRIPMAP" + DYNAMIC_STRIPMAP = "DYNAMIC STRIPMAP" + + +@dataclass +class RowColType: + row: Optional[int] = field( + default=None, + metadata={ + "name": "Row", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + col: Optional[int] = field( + default=None, + metadata={ + "name": "Col", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + + +class SideOfTrack(Enum): + L = "L" + R = "R" + + +class WFParametersRcvDemodType(Enum): + STRETCH = "STRETCH" + CHIRP = "CHIRP" + + +@dataclass +class XYZType: + x: Optional[float] = field( + default=None, + metadata={ + "name": "X", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + y: Optional[float] = field( + default=None, + metadata={ + "name": "Y", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + z: Optional[float] = field( + default=None, + metadata={ + "name": "Z", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + + +@dataclass +class CollectionInfoType: + collector_name: Optional[str] = field( + default=None, + metadata={ + "name": "CollectorName", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + illuminator_name: Optional[str] = field( + default=None, + metadata={ + "name": "IlluminatorName", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + core_name: Optional[str] = field( + default=None, + metadata={ + "name": "CoreName", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + collect_type: Optional[CollectType] = field( + default=None, + metadata={ + "name": "CollectType", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + radar_mode: Optional["CollectionInfoType.RadarMode"] = field( + default=None, + metadata={ + "name": "RadarMode", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + classification: Optional[str] = field( + default=None, + metadata={ + "name": "Classification", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + country_code: List[str] = field( + default_factory=list, + metadata={ + "name": "CountryCode", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + parameter: List[ParameterType] = field( + default_factory=list, + metadata={ + "name": "Parameter", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + + @dataclass + class RadarMode: + mode_type: Optional[RadarModeType] = field( + default=None, + metadata={ + "name": "ModeType", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + mode_id: Optional[str] = field( + default=None, + metadata={ + "name": "ModeID", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + + +@dataclass +class ErrorStatisticsType: + composite_scp: Optional["ErrorStatisticsType.CompositeSCP"] = field( + default=None, + metadata={ + "name": "CompositeSCP", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + components: Optional["ErrorStatisticsType.Components"] = field( + default=None, + metadata={ + "name": "Components", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + unmodeled: Optional["ErrorStatisticsType.Unmodeled"] = field( + default=None, + metadata={ + "name": "Unmodeled", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + additional_parms: Optional["ErrorStatisticsType.AdditionalParms"] = field( + default=None, + metadata={ + "name": "AdditionalParms", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + + @dataclass + class CompositeSCP: + rg: Optional[float] = field( + default=None, + metadata={ + "name": "Rg", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + az: Optional[float] = field( + default=None, + metadata={ + "name": "Az", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + rg_az: Optional[float] = field( + default=None, + metadata={ + "name": "RgAz", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + + @dataclass + class Components: + pos_vel_err: Optional["ErrorStatisticsType.Components.PosVelErr"] = field( + default=None, + metadata={ + "name": "PosVelErr", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + radar_sensor: Optional["ErrorStatisticsType.Components.RadarSensor"] = field( + default=None, + metadata={ + "name": "RadarSensor", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + tropo_error: Optional["ErrorStatisticsType.Components.TropoError"] = field( + default=None, + metadata={ + "name": "TropoError", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + iono_error: Optional["ErrorStatisticsType.Components.IonoError"] = field( + default=None, + metadata={ + "name": "IonoError", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + + @dataclass + class PosVelErr: + frame: Optional[PosVelErrFrame] = field( + default=None, + metadata={ + "name": "Frame", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + p1: Optional[float] = field( + default=None, + metadata={ + "name": "P1", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + p2: Optional[float] = field( + default=None, + metadata={ + "name": "P2", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + p3: Optional[float] = field( + default=None, + metadata={ + "name": "P3", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + v1: Optional[float] = field( + default=None, + metadata={ + "name": "V1", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + v2: Optional[float] = field( + default=None, + metadata={ + "name": "V2", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + v3: Optional[float] = field( + default=None, + metadata={ + "name": "V3", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + corr_coefs: Optional["ErrorStatisticsType.Components.PosVelErr.CorrCoefs"] = field( + default=None, + metadata={ + "name": "CorrCoefs", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + position_decorr: Optional[ErrorDecorrFuncType] = field( + default=None, + metadata={ + "name": "PositionDecorr", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + + @dataclass + class CorrCoefs: + p1_p2: Optional[float] = field( + default=None, + metadata={ + "name": "P1P2", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + p1_p3: Optional[float] = field( + default=None, + metadata={ + "name": "P1P3", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + p1_v1: Optional[float] = field( + default=None, + metadata={ + "name": "P1V1", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + p1_v2: Optional[float] = field( + default=None, + metadata={ + "name": "P1V2", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + p1_v3: Optional[float] = field( + default=None, + metadata={ + "name": "P1V3", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + p2_p3: Optional[float] = field( + default=None, + metadata={ + "name": "P2P3", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + p2_v1: Optional[float] = field( + default=None, + metadata={ + "name": "P2V1", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + p2_v2: Optional[float] = field( + default=None, + metadata={ + "name": "P2V2", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + p2_v3: Optional[float] = field( + default=None, + metadata={ + "name": "P2V3", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + p3_v1: Optional[float] = field( + default=None, + metadata={ + "name": "P3V1", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + p3_v2: Optional[float] = field( + default=None, + metadata={ + "name": "P3V2", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + p3_v3: Optional[float] = field( + default=None, + metadata={ + "name": "P3V3", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + v1_v2: Optional[float] = field( + default=None, + metadata={ + "name": "V1V2", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + v1_v3: Optional[float] = field( + default=None, + metadata={ + "name": "V1V3", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + v2_v3: Optional[float] = field( + default=None, + metadata={ + "name": "V2V3", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + + @dataclass + class RadarSensor: + range_bias: Optional[float] = field( + default=None, + metadata={ + "name": "RangeBias", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + clock_freq_sf: Optional[float] = field( + default=None, + metadata={ + "name": "ClockFreqSF", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + transmit_freq_sf: Optional[float] = field( + default=None, + metadata={ + "name": "TransmitFreqSF", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + range_bias_decorr: Optional[ErrorDecorrFuncType] = field( + default=None, + metadata={ + "name": "RangeBiasDecorr", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + + @dataclass + class TropoError: + tropo_range_vertical: Optional[float] = field( + default=None, + metadata={ + "name": "TropoRangeVertical", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + tropo_range_slant: Optional[float] = field( + default=None, + metadata={ + "name": "TropoRangeSlant", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + tropo_range_decorr: Optional[ErrorDecorrFuncType] = field( + default=None, + metadata={ + "name": "TropoRangeDecorr", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + + @dataclass + class IonoError: + iono_range_vertical: Optional[float] = field( + default=None, + metadata={ + "name": "IonoRangeVertical", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + iono_range_rate_vertical: Optional[float] = field( + default=None, + metadata={ + "name": "IonoRangeRateVertical", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + iono_rg_rg_rate_cc: Optional[float] = field( + default=None, + metadata={ + "name": "IonoRgRgRateCC", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + iono_range_vert_decorr: Optional[ErrorDecorrFuncType] = field( + default=None, + metadata={ + "name": "IonoRangeVertDecorr", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + + @dataclass + class Unmodeled: + xrow: Optional[float] = field( + default=None, + metadata={ + "name": "Xrow", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + ycol: Optional[float] = field( + default=None, + metadata={ + "name": "Ycol", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + xrow_ycol: Optional[float] = field( + default=None, + metadata={ + "name": "XrowYcol", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + unmodeled_decorr: Optional["ErrorStatisticsType.Unmodeled.UnmodeledDecorr"] = field( + default=None, + metadata={ + "name": "UnmodeledDecorr", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + + @dataclass + class UnmodeledDecorr: + xrow: Optional[ErrorDecorrFuncType] = field( + default=None, + metadata={ + "name": "Xrow", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + ycol: Optional[ErrorDecorrFuncType] = field( + default=None, + metadata={ + "name": "Ycol", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + + @dataclass + class AdditionalParms: + parameter: List[ParameterType] = field( + default_factory=list, + metadata={ + "name": "Parameter", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "min_occurs": 1, + }, + ) + + +@dataclass +class ImageFormationType: + rcv_chan_proc: Optional["ImageFormationType.RcvChanProc"] = field( + default=None, + metadata={ + "name": "RcvChanProc", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + tx_rcv_polarization_proc: Optional[str] = field( + default=None, + metadata={ + "name": "TxRcvPolarizationProc", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + "pattern": r"(([VHXYSE]|RHC|LHC|OTHER[^:]*):([VHXYSE]|RHC|LHC|OTHER[^:]*))|OTHER|UNKNOWN", + }, + ) + tstart_proc: Optional[float] = field( + default=None, + metadata={ + "name": "TStartProc", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + tend_proc: Optional[float] = field( + default=None, + metadata={ + "name": "TEndProc", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + tx_frequency_proc: Optional["ImageFormationType.TxFrequencyProc"] = field( + default=None, + metadata={ + "name": "TxFrequencyProc", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + segment_identifier: Optional[str] = field( + default=None, + metadata={ + "name": "SegmentIdentifier", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + image_form_algo: Optional[ImageFormAlgo] = field( + default=None, + metadata={ + "name": "ImageFormAlgo", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + stbeam_comp: Optional[STBeamComp] = field( + default=None, + metadata={ + "name": "STBeamComp", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + image_beam_comp: Optional[ImageBeamComp] = field( + default=None, + metadata={ + "name": "ImageBeamComp", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + az_autofocus: Optional[AzAutofocus] = field( + default=None, + metadata={ + "name": "AzAutofocus", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + rg_autofocus: Optional[RgAutofocus] = field( + default=None, + metadata={ + "name": "RgAutofocus", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + processing: List["ImageFormationType.Processing"] = field( + default_factory=list, + metadata={ + "name": "Processing", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + polarization_calibration: Optional["ImageFormationType.PolarizationCalibration"] = field( + default=None, + metadata={ + "name": "PolarizationCalibration", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + + @dataclass + class RcvChanProc: + num_chan_proc: Optional[int] = field( + default=None, + metadata={ + "name": "NumChanProc", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + prfscale_factor: Optional[float] = field( + default=None, + metadata={ + "name": "PRFScaleFactor", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + chan_index: List[int] = field( + default_factory=list, + metadata={ + "name": "ChanIndex", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "min_occurs": 1, + }, + ) + + @dataclass + class TxFrequencyProc: + min_proc: Optional[float] = field( + default=None, + metadata={ + "name": "MinProc", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + max_proc: Optional[float] = field( + default=None, + metadata={ + "name": "MaxProc", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + + @dataclass + class Processing: + type_value: Optional[str] = field( + default=None, + metadata={ + "name": "Type", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + applied: Optional[bool] = field( + default=None, + metadata={ + "name": "Applied", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + parameter: List[ParameterType] = field( + default_factory=list, + metadata={ + "name": "Parameter", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + + @dataclass + class PolarizationCalibration: + distort_correction_applied: Optional[bool] = field( + default=None, + metadata={ + "name": "DistortCorrectionApplied", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + distortion: Optional["ImageFormationType.PolarizationCalibration.Distortion"] = field( + default=None, + metadata={ + "name": "Distortion", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + + @dataclass + class Distortion: + calibration_date: Optional[XmlDateTime] = field( + default=None, + metadata={ + "name": "CalibrationDate", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + a: Optional[float] = field( + default=None, + metadata={ + "name": "A", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + f1: Optional[ComplexType] = field( + default=None, + metadata={ + "name": "F1", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + q1: Optional[ComplexType] = field( + default=None, + metadata={ + "name": "Q1", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + q2: Optional[ComplexType] = field( + default=None, + metadata={ + "name": "Q2", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + f2: Optional[ComplexType] = field( + default=None, + metadata={ + "name": "F2", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + q3: Optional[ComplexType] = field( + default=None, + metadata={ + "name": "Q3", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + q4: Optional[ComplexType] = field( + default=None, + metadata={ + "name": "Q4", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + gain_error_a: Optional[float] = field( + default=None, + metadata={ + "name": "GainErrorA", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + gain_error_f1: Optional[float] = field( + default=None, + metadata={ + "name": "GainErrorF1", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + gain_error_f2: Optional[float] = field( + default=None, + metadata={ + "name": "GainErrorF2", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + phase_error_f1: Optional[float] = field( + default=None, + metadata={ + "name": "PhaseErrorF1", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + phase_error_f2: Optional[float] = field( + default=None, + metadata={ + "name": "PhaseErrorF2", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + + +@dataclass +class LatLonCornerStringType(LatLonType): + index: Optional[CornerStringType] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class LatLonCornerType(LatLonType): + index: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + "min_inclusive": 1, + "max_inclusive": 4, + }, + ) + + +@dataclass +class LatLonHAECornerRestrictType(LatLonHAERestrictionType): + index: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + "min_inclusive": 1, + "max_inclusive": 4, + }, + ) + + +@dataclass +class LatLonHAECornerStringType(LatLonHAEType): + index: Optional[CornerStringType] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class LineType: + endpoint: List["LineType.Endpoint"] = field( + default_factory=list, + metadata={ + "name": "Endpoint", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "min_occurs": 2, + }, + ) + size: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + @dataclass + class Endpoint(LatLonType): + index: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class MatchInfoType: + num_match_types: Optional[int] = field( + default=None, + metadata={ + "name": "NumMatchTypes", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + match_type: List["MatchInfoType.MatchType"] = field( + default_factory=list, + metadata={ + "name": "MatchType", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "min_occurs": 1, + }, + ) + + @dataclass + class MatchType: + type_id: Optional[str] = field( + default=None, + metadata={ + "name": "TypeID", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + current_index: Optional[int] = field( + default=None, + metadata={ + "name": "CurrentIndex", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + num_match_collections: Optional[int] = field( + default=None, + metadata={ + "name": "NumMatchCollections", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + match_collection: List["MatchInfoType.MatchType.MatchCollection"] = field( + default_factory=list, + metadata={ + "name": "MatchCollection", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + index: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + @dataclass + class MatchCollection: + core_name: Optional[str] = field( + default=None, + metadata={ + "name": "CoreName", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + match_index: Optional[int] = field( + default=None, + metadata={ + "name": "MatchIndex", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + parameter: List[ParameterType] = field( + default_factory=list, + metadata={ + "name": "Parameter", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + index: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class Poly1DType: + coef: List[PolyCoef1DType] = field( + default_factory=list, + metadata={ + "name": "Coef", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "min_occurs": 1, + }, + ) + order1: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class Poly2DType: + coef: List[PolyCoef2DType] = field( + default_factory=list, + metadata={ + "name": "Coef", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "min_occurs": 1, + }, + ) + order1: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + order2: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class PolygonType: + vertex: List["PolygonType.Vertex"] = field( + default_factory=list, + metadata={ + "name": "Vertex", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "min_occurs": 3, + }, + ) + size: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + @dataclass + class Vertex(LatLonRestrictionType): + index: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class RowColvertexType(RowColType): + index: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class SCPCOAType: + scptime: Optional[float] = field( + default=None, + metadata={ + "name": "SCPTime", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + arppos: Optional[XYZType] = field( + default=None, + metadata={ + "name": "ARPPos", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + arpvel: Optional[XYZType] = field( + default=None, + metadata={ + "name": "ARPVel", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + arpacc: Optional[XYZType] = field( + default=None, + metadata={ + "name": "ARPAcc", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + side_of_track: Optional[SideOfTrack] = field( + default=None, + metadata={ + "name": "SideOfTrack", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + slant_range: Optional[float] = field( + default=None, + metadata={ + "name": "SlantRange", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + ground_range: Optional[float] = field( + default=None, + metadata={ + "name": "GroundRange", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + doppler_cone_ang: Optional[float] = field( + default=None, + metadata={ + "name": "DopplerConeAng", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + graze_ang: Optional[float] = field( + default=None, + metadata={ + "name": "GrazeAng", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + "min_inclusive": 0.0, + "max_inclusive": 90.0, + }, + ) + incidence_ang: Optional[float] = field( + default=None, + metadata={ + "name": "IncidenceAng", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + "min_inclusive": 0.0, + "max_inclusive": 90.0, + }, + ) + twist_ang: Optional[float] = field( + default=None, + metadata={ + "name": "TwistAng", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + "min_inclusive": -90.0, + "max_inclusive": 90.0, + }, + ) + slope_ang: Optional[float] = field( + default=None, + metadata={ + "name": "SlopeAng", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + "min_inclusive": 0.0, + "max_inclusive": 90.0, + }, + ) + azim_ang: Optional[float] = field( + default=None, + metadata={ + "name": "AzimAng", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + "min_inclusive": 0.0, + "max_inclusive": 360.0, + }, + ) + layover_ang: Optional[float] = field( + default=None, + metadata={ + "name": "LayoverAng", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + "min_inclusive": 0.0, + "max_inclusive": 360.0, + }, + ) + + +@dataclass +class DirParamType: + uvect_ecf: Optional[XYZType] = field( + default=None, + metadata={ + "name": "UVectECF", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + ss: Optional[float] = field( + default=None, + metadata={ + "name": "SS", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + imp_resp_wid: Optional[float] = field( + default=None, + metadata={ + "name": "ImpRespWid", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + sgn: Optional[DirParamTypeSgn] = field( + default=None, + metadata={ + "name": "Sgn", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + imp_resp_bw: Optional[float] = field( + default=None, + metadata={ + "name": "ImpRespBW", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + kctr: Optional[float] = field( + default=None, + metadata={ + "name": "KCtr", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + delta_k1: Optional[float] = field( + default=None, + metadata={ + "name": "DeltaK1", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + delta_k2: Optional[float] = field( + default=None, + metadata={ + "name": "DeltaK2", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + delta_kcoapoly: Optional[Poly2DType] = field( + default=None, + metadata={ + "name": "DeltaKCOAPoly", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + wgt_type: Optional["DirParamType.WgtType"] = field( + default=None, + metadata={ + "name": "WgtType", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + wgt_funct: Optional["DirParamType.WgtFunct"] = field( + default=None, + metadata={ + "name": "WgtFunct", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + + @dataclass + class WgtType: + window_name: Optional[str] = field( + default=None, + metadata={ + "name": "WindowName", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + parameter: List[ParameterType] = field( + default_factory=list, + metadata={ + "name": "Parameter", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + + @dataclass + class WgtFunct: + wgt: List[ArrayDouble] = field( + default_factory=list, + metadata={ + "name": "Wgt", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "min_occurs": 2, + }, + ) + size: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class GainPhasePolyType: + gain_poly: Optional[Poly2DType] = field( + default=None, + metadata={ + "name": "GainPoly", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + phase_poly: Optional[Poly2DType] = field( + default=None, + metadata={ + "name": "PhasePoly", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + + +@dataclass +class GeoInfoType: + desc: List[ParameterType] = field( + default_factory=list, + metadata={ + "name": "Desc", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + point: Optional[LatLonRestrictionType] = field( + default=None, + metadata={ + "name": "Point", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + line: Optional[LineType] = field( + default=None, + metadata={ + "name": "Line", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + polygon: Optional[PolygonType] = field( + default=None, + metadata={ + "name": "Polygon", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + geo_info: List["GeoInfoType"] = field( + default_factory=list, + metadata={ + "name": "GeoInfo", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + name: Optional[str] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class ImageDataType: + pixel_type: Optional[ImagePixelType] = field( + default=None, + metadata={ + "name": "PixelType", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + amp_table: Optional["ImageDataType.AmpTable"] = field( + default=None, + metadata={ + "name": "AmpTable", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + num_rows: Optional[int] = field( + default=None, + metadata={ + "name": "NumRows", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + num_cols: Optional[int] = field( + default=None, + metadata={ + "name": "NumCols", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + first_row: Optional[int] = field( + default=None, + metadata={ + "name": "FirstRow", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + first_col: Optional[int] = field( + default=None, + metadata={ + "name": "FirstCol", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + full_image: Optional["ImageDataType.FullImage"] = field( + default=None, + metadata={ + "name": "FullImage", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + scppixel: Optional[RowColType] = field( + default=None, + metadata={ + "name": "SCPPixel", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + valid_data: Optional["ImageDataType.ValidData"] = field( + default=None, + metadata={ + "name": "ValidData", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + + @dataclass + class AmpTable: + amplitude: List[ArrayDouble] = field( + default_factory=list, + metadata={ + "name": "Amplitude", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "min_occurs": 256, + "max_occurs": 256, + }, + ) + size: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + @dataclass + class FullImage: + num_rows: Optional[int] = field( + default=None, + metadata={ + "name": "NumRows", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + num_cols: Optional[int] = field( + default=None, + metadata={ + "name": "NumCols", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + + @dataclass + class ValidData: + vertex: List[RowColvertexType] = field( + default_factory=list, + metadata={ + "name": "Vertex", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "min_occurs": 3, + }, + ) + size: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class PFAType: + fpn: Optional[XYZType] = field( + default=None, + metadata={ + "name": "FPN", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + ipn: Optional[XYZType] = field( + default=None, + metadata={ + "name": "IPN", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + polar_ang_ref_time: Optional[float] = field( + default=None, + metadata={ + "name": "PolarAngRefTime", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + polar_ang_poly: Optional[Poly1DType] = field( + default=None, + metadata={ + "name": "PolarAngPoly", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + spatial_freq_sfpoly: Optional[Poly1DType] = field( + default=None, + metadata={ + "name": "SpatialFreqSFPoly", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + krg1: Optional[float] = field( + default=None, + metadata={ + "name": "Krg1", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + krg2: Optional[float] = field( + default=None, + metadata={ + "name": "Krg2", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + kaz1: Optional[float] = field( + default=None, + metadata={ + "name": "Kaz1", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + kaz2: Optional[float] = field( + default=None, + metadata={ + "name": "Kaz2", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + stdeskew: Optional["PFAType.STDeskew"] = field( + default=None, + metadata={ + "name": "STDeskew", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + + @dataclass + class STDeskew: + applied: Optional[bool] = field( + default=None, + metadata={ + "name": "Applied", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + stdsphase_poly: Optional[Poly2DType] = field( + default=None, + metadata={ + "name": "STDSPhasePoly", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + + +@dataclass +class RMAType: + rmalgo_type: Optional[RMAlgorithm] = field( + default=None, + metadata={ + "name": "RMAlgoType", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + image_type: Optional[RMAImageType] = field( + default=None, + metadata={ + "name": "ImageType", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + rmat: Optional["RMAType.RMAT"] = field( + default=None, + metadata={ + "name": "RMAT", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + rmcr: Optional["RMAType.RMCR"] = field( + default=None, + metadata={ + "name": "RMCR", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + inca: Optional["RMAType.INCA"] = field( + default=None, + metadata={ + "name": "INCA", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + + @dataclass + class RMAT: + pos_ref: Optional[XYZType] = field( + default=None, + metadata={ + "name": "PosRef", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + vel_ref: Optional[XYZType] = field( + default=None, + metadata={ + "name": "VelRef", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + dop_cone_ang_ref: Optional[float] = field( + default=None, + metadata={ + "name": "DopConeAngRef", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + + @dataclass + class RMCR: + pos_ref: Optional[XYZType] = field( + default=None, + metadata={ + "name": "PosRef", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + vel_ref: Optional[XYZType] = field( + default=None, + metadata={ + "name": "VelRef", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + dop_cone_ang_ref: Optional[float] = field( + default=None, + metadata={ + "name": "DopConeAngRef", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + + @dataclass + class INCA: + time_capoly: Optional[Poly1DType] = field( + default=None, + metadata={ + "name": "TimeCAPoly", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + r_ca_scp: Optional[float] = field( + default=None, + metadata={ + "name": "R_CA_SCP", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + freq_zero: Optional[float] = field( + default=None, + metadata={ + "name": "FreqZero", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + drate_sfpoly: Optional[Poly2DType] = field( + default=None, + metadata={ + "name": "DRateSFPoly", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + dop_centroid_poly: Optional[Poly2DType] = field( + default=None, + metadata={ + "name": "DopCentroidPoly", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + dop_centroid_coa: Optional[bool] = field( + default=None, + metadata={ + "name": "DopCentroidCOA", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + + +@dataclass +class RadarCollectionType: + tx_frequency: Optional["RadarCollectionType.TxFrequency"] = field( + default=None, + metadata={ + "name": "TxFrequency", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + ref_freq_index: Optional[int] = field( + default=None, + metadata={ + "name": "RefFreqIndex", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + waveform: Optional["RadarCollectionType.Waveform"] = field( + default=None, + metadata={ + "name": "Waveform", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + tx_polarization: Optional[str] = field( + default=None, + metadata={ + "name": "TxPolarization", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + "pattern": r"[VHXYSE]|RHC|LHC|UNKNOWN|SEQUENCE|OTHER[^:]*", + }, + ) + tx_sequence: Optional["RadarCollectionType.TxSequence"] = field( + default=None, + metadata={ + "name": "TxSequence", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + rcv_channels: Optional["RadarCollectionType.RcvChannels"] = field( + default=None, + metadata={ + "name": "RcvChannels", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + area: Optional["RadarCollectionType.Area"] = field( + default=None, + metadata={ + "name": "Area", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + parameter: List[ParameterType] = field( + default_factory=list, + metadata={ + "name": "Parameter", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + + @dataclass + class TxFrequency: + min: Optional[float] = field( + default=None, + metadata={ + "name": "Min", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + max: Optional[float] = field( + default=None, + metadata={ + "name": "Max", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + + @dataclass + class Waveform: + wfparameters: List["RadarCollectionType.Waveform.WFParameters"] = field( + default_factory=list, + metadata={ + "name": "WFParameters", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "min_occurs": 1, + }, + ) + size: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + @dataclass + class WFParameters: + tx_pulse_length: Optional[float] = field( + default=None, + metadata={ + "name": "TxPulseLength", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + tx_rfbandwidth: Optional[float] = field( + default=None, + metadata={ + "name": "TxRFBandwidth", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + tx_freq_start: Optional[float] = field( + default=None, + metadata={ + "name": "TxFreqStart", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + tx_fmrate: Optional[float] = field( + default=None, + metadata={ + "name": "TxFMRate", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + rcv_demod_type: Optional[WFParametersRcvDemodType] = field( + default=None, + metadata={ + "name": "RcvDemodType", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + rcv_window_length: Optional[float] = field( + default=None, + metadata={ + "name": "RcvWindowLength", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + adcsample_rate: Optional[float] = field( + default=None, + metadata={ + "name": "ADCSampleRate", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + rcv_ifbandwidth: Optional[float] = field( + default=None, + metadata={ + "name": "RcvIFBandwidth", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + rcv_freq_start: Optional[float] = field( + default=None, + metadata={ + "name": "RcvFreqStart", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + rcv_fmrate: Optional[float] = field( + default=None, + metadata={ + "name": "RcvFMRate", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + index: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + @dataclass + class TxSequence: + tx_step: List["RadarCollectionType.TxSequence.TxStep"] = field( + default_factory=list, + metadata={ + "name": "TxStep", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "min_occurs": 1, + }, + ) + size: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + @dataclass + class TxStep: + wfindex: Optional[int] = field( + default=None, + metadata={ + "name": "WFIndex", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + tx_polarization: Optional[str] = field( + default=None, + metadata={ + "name": "TxPolarization", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "pattern": r"[VHXYSE]|RHC|LHC|UNKNOWN|OTHER[^:]*", + }, + ) + index: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + @dataclass + class RcvChannels: + chan_parameters: List["RadarCollectionType.RcvChannels.ChanParameters"] = field( + default_factory=list, + metadata={ + "name": "ChanParameters", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "min_occurs": 1, + }, + ) + size: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + @dataclass + class ChanParameters: + tx_rcv_polarization: Optional[str] = field( + default=None, + metadata={ + "name": "TxRcvPolarization", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + "pattern": r"(([VHXYSE]|RHC|LHC|OTHER[^:]*):([VHXYSE]|RHC|LHC|OTHER[^:]*))|OTHER|UNKNOWN", + }, + ) + rcv_apcindex: Optional[int] = field( + default=None, + metadata={ + "name": "RcvAPCIndex", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + index: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + @dataclass + class Area: + corner: Optional["RadarCollectionType.Area.Corner"] = field( + default=None, + metadata={ + "name": "Corner", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + plane: Optional["RadarCollectionType.Area.Plane"] = field( + default=None, + metadata={ + "name": "Plane", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + + @dataclass + class Corner: + acp: List[LatLonHAECornerRestrictType] = field( + default_factory=list, + metadata={ + "name": "ACP", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "min_occurs": 4, + "max_occurs": 4, + }, + ) + + @dataclass + class Plane: + ref_pt: Optional["RadarCollectionType.Area.Plane.RefPt"] = field( + default=None, + metadata={ + "name": "RefPt", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + xdir: Optional["RadarCollectionType.Area.Plane.XDir"] = field( + default=None, + metadata={ + "name": "XDir", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + ydir: Optional["RadarCollectionType.Area.Plane.YDir"] = field( + default=None, + metadata={ + "name": "YDir", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + segment_list: Optional["RadarCollectionType.Area.Plane.SegmentList"] = field( + default=None, + metadata={ + "name": "SegmentList", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + orientation: Optional[OrientationType] = field( + default=None, + metadata={ + "name": "Orientation", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + + @dataclass + class RefPt: + ecf: Optional[XYZType] = field( + default=None, + metadata={ + "name": "ECF", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + line: Optional[float] = field( + default=None, + metadata={ + "name": "Line", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + sample: Optional[float] = field( + default=None, + metadata={ + "name": "Sample", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + name: Optional[str] = field( + default=None, + metadata={ + "type": "Attribute", + }, + ) + + @dataclass + class XDir: + uvect_ecf: Optional[XYZType] = field( + default=None, + metadata={ + "name": "UVectECF", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + line_spacing: Optional[float] = field( + default=None, + metadata={ + "name": "LineSpacing", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + num_lines: Optional[int] = field( + default=None, + metadata={ + "name": "NumLines", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + first_line: Optional[int] = field( + default=None, + metadata={ + "name": "FirstLine", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + + @dataclass + class YDir: + uvect_ecf: Optional[XYZType] = field( + default=None, + metadata={ + "name": "UVectECF", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + sample_spacing: Optional[float] = field( + default=None, + metadata={ + "name": "SampleSpacing", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + num_samples: Optional[int] = field( + default=None, + metadata={ + "name": "NumSamples", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + first_sample: Optional[int] = field( + default=None, + metadata={ + "name": "FirstSample", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + + @dataclass + class SegmentList: + segment: List["RadarCollectionType.Area.Plane.SegmentList.Segment"] = field( + default_factory=list, + metadata={ + "name": "Segment", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "min_occurs": 1, + }, + ) + size: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + @dataclass + class Segment: + start_line: Optional[int] = field( + default=None, + metadata={ + "name": "StartLine", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + start_sample: Optional[int] = field( + default=None, + metadata={ + "name": "StartSample", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + end_line: Optional[int] = field( + default=None, + metadata={ + "name": "EndLine", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + end_sample: Optional[int] = field( + default=None, + metadata={ + "name": "EndSample", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + identifier: Optional[str] = field( + default=None, + metadata={ + "name": "Identifier", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + index: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class RadiometricType: + noise_level: Optional["RadiometricType.NoiseLevel"] = field( + default=None, + metadata={ + "name": "NoiseLevel", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + rcssfpoly: Optional[Poly2DType] = field( + default=None, + metadata={ + "name": "RCSSFPoly", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + sigma_zero_sfpoly: Optional[Poly2DType] = field( + default=None, + metadata={ + "name": "SigmaZeroSFPoly", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + beta_zero_sfpoly: Optional[Poly2DType] = field( + default=None, + metadata={ + "name": "BetaZeroSFPoly", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + gamma_zero_sfpoly: Optional[Poly2DType] = field( + default=None, + metadata={ + "name": "GammaZeroSFPoly", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + + @dataclass + class NoiseLevel: + noise_level_type: Optional[NoiseLevelType] = field( + default=None, + metadata={ + "name": "NoiseLevelType", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + noise_poly: Optional[Poly2DType] = field( + default=None, + metadata={ + "name": "NoisePoly", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + + +@dataclass +class RgAzCompType: + az_sf: Optional[float] = field( + default=None, + metadata={ + "name": "AzSF", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + kaz_poly: Optional[Poly1DType] = field( + default=None, + metadata={ + "name": "KazPoly", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + + +@dataclass +class TimelineType: + collect_start: Optional[XmlDateTime] = field( + default=None, + metadata={ + "name": "CollectStart", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + collect_duration: Optional[float] = field( + default=None, + metadata={ + "name": "CollectDuration", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + ipp: Optional["TimelineType.IPP"] = field( + default=None, + metadata={ + "name": "IPP", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + + @dataclass + class IPP: + set: List["TimelineType.IPP.Set"] = field( + default_factory=list, + metadata={ + "name": "Set", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "min_occurs": 1, + }, + ) + size: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + @dataclass + class Set: + tstart: Optional[float] = field( + default=None, + metadata={ + "name": "TStart", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + tend: Optional[float] = field( + default=None, + metadata={ + "name": "TEnd", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + ippstart: Optional[int] = field( + default=None, + metadata={ + "name": "IPPStart", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + ippend: Optional[int] = field( + default=None, + metadata={ + "name": "IPPEnd", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + ipppoly: Optional[Poly1DType] = field( + default=None, + metadata={ + "name": "IPPPoly", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + index: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class XYZPolyType: + x: Optional[Poly1DType] = field( + default=None, + metadata={ + "name": "X", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + y: Optional[Poly1DType] = field( + default=None, + metadata={ + "name": "Y", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + z: Optional[Poly1DType] = field( + default=None, + metadata={ + "name": "Z", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + + +@dataclass +class AntParamType: + xaxis_poly: Optional[XYZPolyType] = field( + default=None, + metadata={ + "name": "XAxisPoly", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + yaxis_poly: Optional[XYZPolyType] = field( + default=None, + metadata={ + "name": "YAxisPoly", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + freq_zero: Optional[float] = field( + default=None, + metadata={ + "name": "FreqZero", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + eb: Optional["AntParamType.EB"] = field( + default=None, + metadata={ + "name": "EB", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + array: Optional[GainPhasePolyType] = field( + default=None, + metadata={ + "name": "Array", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + elem: Optional[GainPhasePolyType] = field( + default=None, + metadata={ + "name": "Elem", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + gain_bspoly: Optional[Poly1DType] = field( + default=None, + metadata={ + "name": "GainBSPoly", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + ebfreq_shift: Optional[bool] = field( + default=None, + metadata={ + "name": "EBFreqShift", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + mlfreq_dilation: Optional[bool] = field( + default=None, + metadata={ + "name": "MLFreqDilation", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + + @dataclass + class EB: + dcxpoly: Optional[Poly1DType] = field( + default=None, + metadata={ + "name": "DCXPoly", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + dcypoly: Optional[Poly1DType] = field( + default=None, + metadata={ + "name": "DCYPoly", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + + +@dataclass +class GeoDataType: + earth_model: Optional[EarthModel] = field( + default=None, + metadata={ + "name": "EarthModel", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + scp: Optional["GeoDataType.SCP"] = field( + default=None, + metadata={ + "name": "SCP", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + image_corners: Optional["GeoDataType.ImageCorners"] = field( + default=None, + metadata={ + "name": "ImageCorners", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + valid_data: Optional[PolygonType] = field( + default=None, + metadata={ + "name": "ValidData", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + geo_info: List[GeoInfoType] = field( + default_factory=list, + metadata={ + "name": "GeoInfo", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + + @dataclass + class SCP: + ecf: Optional[XYZType] = field( + default=None, + metadata={ + "name": "ECF", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + llh: Optional[LatLonHAERestrictionType] = field( + default=None, + metadata={ + "name": "LLH", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + + @dataclass + class ImageCorners: + icp: List[LatLonCornerStringType] = field( + default_factory=list, + metadata={ + "name": "ICP", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "min_occurs": 4, + "max_occurs": 4, + }, + ) + + +@dataclass +class GridType: + image_plane: Optional[ImagePlane] = field( + default=None, + metadata={ + "name": "ImagePlane", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + type_value: Optional[ImageGridType] = field( + default=None, + metadata={ + "name": "Type", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + time_coapoly: Optional[Poly2DType] = field( + default=None, + metadata={ + "name": "TimeCOAPoly", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + row: Optional[DirParamType] = field( + default=None, + metadata={ + "name": "Row", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + col: Optional[DirParamType] = field( + default=None, + metadata={ + "name": "Col", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + + +@dataclass +class XYZPolyAttributeType(XYZPolyType): + index: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class AntennaType: + tx: Optional[AntParamType] = field( + default=None, + metadata={ + "name": "Tx", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + rcv: Optional[AntParamType] = field( + default=None, + metadata={ + "name": "Rcv", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + two_way: Optional[AntParamType] = field( + default=None, + metadata={ + "name": "TwoWay", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + + +@dataclass +class PositionType: + arppoly: Optional[XYZPolyType] = field( + default=None, + metadata={ + "name": "ARPPoly", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "required": True, + }, + ) + grppoly: Optional[XYZPolyType] = field( + default=None, + metadata={ + "name": "GRPPoly", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + tx_apcpoly: Optional[XYZPolyType] = field( + default=None, + metadata={ + "name": "TxAPCPoly", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + rcv_apc: Optional["PositionType.RcvAPC"] = field( + default=None, + metadata={ + "name": "RcvAPC", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + }, + ) + + @dataclass + class RcvAPC: + rcv_apcpoly: List[XYZPolyAttributeType] = field( + default_factory=list, + metadata={ + "name": "RcvAPCPoly", + "type": "Element", + "namespace": "urn:SICD:1.3.0", + "min_occurs": 1, + }, + ) + size: Optional[int] = field( + default=None, + metadata={ + "type": "Attribute", + "required": True, + }, + ) + + +@dataclass +class SICD: + class Meta: + namespace = "urn:SICD:1.3.0" + + collection_info: Optional[CollectionInfoType] = field( + default=None, + metadata={ + "name": "CollectionInfo", + "type": "Element", + "required": True, + }, + ) + image_creation: Optional[ImageCreationType] = field( + default=None, + metadata={ + "name": "ImageCreation", + "type": "Element", + }, + ) + image_data: Optional[ImageDataType] = field( + default=None, + metadata={ + "name": "ImageData", + "type": "Element", + "required": True, + }, + ) + geo_data: Optional[GeoDataType] = field( + default=None, + metadata={ + "name": "GeoData", + "type": "Element", + "required": True, + }, + ) + grid: Optional[GridType] = field( + default=None, + metadata={ + "name": "Grid", + "type": "Element", + "required": True, + }, + ) + timeline: Optional[TimelineType] = field( + default=None, + metadata={ + "name": "Timeline", + "type": "Element", + "required": True, + }, + ) + position: Optional[PositionType] = field( + default=None, + metadata={ + "name": "Position", + "type": "Element", + "required": True, + }, + ) + radar_collection: Optional[RadarCollectionType] = field( + default=None, + metadata={ + "name": "RadarCollection", + "type": "Element", + "required": True, + }, + ) + image_formation: Optional[ImageFormationType] = field( + default=None, + metadata={ + "name": "ImageFormation", + "type": "Element", + "required": True, + }, + ) + scpcoa: Optional[SCPCOAType] = field( + default=None, + metadata={ + "name": "SCPCOA", + "type": "Element", + "required": True, + }, + ) + radiometric: Optional[RadiometricType] = field( + default=None, + metadata={ + "name": "Radiometric", + "type": "Element", + }, + ) + antenna: Optional[AntennaType] = field( + default=None, + metadata={ + "name": "Antenna", + "type": "Element", + }, + ) + error_statistics: Optional[ErrorStatisticsType] = field( + default=None, + metadata={ + "name": "ErrorStatistics", + "type": "Element", + }, + ) + match_info: Optional[MatchInfoType] = field( + default=None, + metadata={ + "name": "MatchInfo", + "type": "Element", + }, + ) + rg_az_comp: Optional[RgAzCompType] = field( + default=None, + metadata={ + "name": "RgAzComp", + "type": "Element", + }, + ) + pfa: Optional[PFAType] = field( + default=None, + metadata={ + "name": "PFA", + "type": "Element", + }, + ) + rma: Optional[RMAType] = field( + default=None, + metadata={ + "name": "RMA", + "type": "Element", + }, + ) diff --git a/src/aws/osml/gdal/gdal_utils.py b/src/aws/osml/gdal/gdal_utils.py index 90e2696..8e86f5c 100644 --- a/src/aws/osml/gdal/gdal_utils.py +++ b/src/aws/osml/gdal/gdal_utils.py @@ -40,16 +40,14 @@ def load_gdal_dataset(image_path: str) -> Tuple[gdal.Dataset, Optional[SensorMod if xml_tres is not None and len(xml_tres) > 0: parsed_tres = ElementTree.fromstring(xml_tres[0]) - # If this image has SICD Metadata parse it - parsed_dess = None + # If this image has NITF DES segments read them xml_dess = ds.GetMetadata("xml:DES") - if xml_dess is not None and len(xml_dess) > 0: - parsed_dess = ElementTree.fromstring(xml_dess[0]) selected_sensor_model_types = [ SensorModelTypes.AFFINE, SensorModelTypes.PROJECTIVE, SensorModelTypes.RPC, + SensorModelTypes.SICD, # TODO: Enable RSM model once testing complete # SensorModelTypes.RSM, ] @@ -58,7 +56,7 @@ def load_gdal_dataset(image_path: str) -> Tuple[gdal.Dataset, Optional[SensorMod ds.RasterXSize, ds.RasterYSize, xml_tres=parsed_tres, - xml_dess=parsed_dess, + xml_dess=xml_dess, geo_transform=geo_transform, ground_control_points=ground_control_points, selected_sensor_model_types=selected_sensor_model_types, diff --git a/src/aws/osml/gdal/sensor_model_factory.py b/src/aws/osml/gdal/sensor_model_factory.py index bca8282..b969aa3 100644 --- a/src/aws/osml/gdal/sensor_model_factory.py +++ b/src/aws/osml/gdal/sensor_model_factory.py @@ -1,3 +1,4 @@ +import base64 import logging from enum import Enum from typing import List, Optional @@ -8,9 +9,11 @@ from aws.osml.photogrammetry import ChippedImageSensorModel, CompositeSensorModel, ImageCoordinate, SensorModel from .gdal_sensor_model_builder import GDALAffineSensorModelBuilder, GDALGCPSensorModelBuilder +from .nitf_des_accessor import NITFDESAccessor from .projective_sensor_model_builder import ProjectiveSensorModelBuilder from .rpc_sensor_model_builder import RPCSensorModelBuilder from .rsm_sensor_model_builder import RSMSensorModelBuilder +from .sicd_sensor_model_builder import SICDSensorModelBuilder from .xmltre_utils import get_tre_field_value @@ -57,6 +60,7 @@ class SensorModelTypes(Enum): PROJECTIVE = "PROJECTIVE" RPC = "RPC" RSM = "RSM" + SICD = "SICD" ALL_SENSOR_MODEL_TYPES = [item for item in SensorModelTypes] @@ -74,7 +78,7 @@ def __init__( actual_image_width: int, actual_image_height: int, xml_tres: Optional[ET.Element] = None, - xml_dess: Optional[ET.Element] = None, + xml_dess: Optional[List[str]] = None, geo_transform: Optional[List[float]] = None, ground_control_points: Optional[List[gdal.GCP]] = None, selected_sensor_model_types: Optional[List[SensorModelTypes]] = None, @@ -172,6 +176,22 @@ def build(self) -> Optional[SensorModel]: # TODO: Maybe create a projective sensor model from corner locations derived from the precision model # TODO: Consider using the rough corners from IGEOLO + if self.xml_dess is not None and len(self.xml_dess) > 0: + des_accessor = NITFDESAccessor(self.xml_dess) + + xml_data_content_segments = des_accessor.get_segments_by_name("XML_DATA_CONTENT") + if xml_data_content_segments is not None: + for xml_data_segment in xml_data_content_segments: + xml_bytes = des_accessor.parse_field_value(xml_data_segment, "DESDATA", base64.b64decode) + xml_str = xml_bytes.decode("utf-8") + if "SIDD" in xml_str: + # This looks like a SIDD file. Skip for now + # SIDD images will contain SICD extensions but the SIDD should come first + break + elif "SICD" in xml_str and SensorModelTypes.SICD in self.selected_sensor_model_types: + precision_sensor_model = SICDSensorModelBuilder(sicd_xml=xml_str).build() + break + # If we have both an approximate and a precision sensor model return them as a composite so applications # can choose which model best meets their needs. If we were only able to construct one or the other then # return what we were able to build. diff --git a/src/aws/osml/gdal/sicd_sensor_model_builder.py b/src/aws/osml/gdal/sicd_sensor_model_builder.py new file mode 100644 index 0000000..37aba27 --- /dev/null +++ b/src/aws/osml/gdal/sicd_sensor_model_builder.py @@ -0,0 +1,186 @@ +import logging +from typing import Optional, Union + +import numpy as np +from xsdata.formats.dataclass.parsers import XmlParser + +import aws.osml.formats.sicd.models.sicd_v1_2_1 as sicd121 +import aws.osml.formats.sicd.models.sicd_v1_3_0 as sicd130 + +from ..photogrammetry import ( + ImageCoordinate, + INCAProjectionSet, + PFAProjectionSet, + PlaneProjectionSet, + Polynomial2D, + PolynomialXYZ, + RGAZCOMPProjectionSet, + SARImageCoordConverter, + SensorModel, + SICDSensorModel, + WorldCoordinate, +) +from .sensor_model_builder import SensorModelBuilder + +logger = logging.getLogger(__name__) + + +def xyztype_to_ndarray(xyztype: Union[sicd121.XYZType, sicd130.XYZType]) -> np.ndarray: + """ + Convert the XYZType to a 1-d NumPy array. + + :param xyztype: the XYZType dataclass + :return: the NumPy array + """ + return np.array([xyztype.x, xyztype.y, xyztype.z]) + + +def poly1d_to_native(poly1d: Union[sicd121.Poly1DType, sicd130.XYZType]) -> np.polynomial.Polynomial: + """ + Convert the Poly1DType to a NumPy Polynomial. + + :param poly1d: the Poly1D dataclass + :return: the NumPy polynomial with matching coefficients + """ + coefficients = [0] * (poly1d.order1 + 1) + for coef in poly1d.coef: + coefficients[coef.exponent1] = coef.value + return np.polynomial.Polynomial(coefficients) + + +def poly2d_to_native(poly2d: Union[sicd121.Poly2DType, sicd130.Poly2DType]) -> Polynomial2D: + """ + Convert the Poly2D dataclass to a Polynomial2D. + + :param poly2d: the Poly2D dataclass + :return: the Polynomial2D with matching coefficients + """ + coefficients = [0] * (poly2d.order1 + 1) + for row in range(0, len(coefficients)): + coefficients[row] = [0] * (poly2d.order2 + 1) + for coef in poly2d.coef: + coefficients[coef.exponent1][coef.exponent2] = coef.value + return Polynomial2D(np.array(coefficients)) + + +def xyzpoly_to_native(xyzpoly: Union[sicd121.XYZPolyType, sicd130.XYZPolyType]) -> PolynomialXYZ: + """ + Convert the XYZPoly dataclass into a PolynomialXYZ. + + :param xyzpoly: the XYZPoly dataclass + :return: the PolynomialXYZ with matching coefficients + """ + return PolynomialXYZ( + x_polynomial=poly1d_to_native(xyzpoly.x), + y_polynomial=poly1d_to_native(xyzpoly.y), + z_polynomial=poly1d_to_native(xyzpoly.z), + ) + + +class SICDSensorModelBuilder(SensorModelBuilder): + """ + This builder is used to create sensor models for images that have SICD metadata. The metadata is provided + as XML that conforms to the SICD specifications. We intend to support multiple SICD versions but the current + software was implemented using the v1.2.1 and v1.3.0 specifications. + """ + + def __init__(self, sicd_xml: str): + """ + Construct the builder given the SICD XML. + + :param sicd_xml: the XML string + """ + super().__init__() + self.sicd_xml = sicd_xml + + def build(self) -> Optional[SensorModel]: + """ + Attempt to build a precise SAR sensor model. This sensor model handles chipped images natively. + + :return: the sensor model; if available + """ + try: + if self.sicd_xml is None or len(self.sicd_xml) == 0: + return None + + parser = XmlParser() + sicd = parser.from_string(self.sicd_xml) + return SICDSensorModelBuilder.from_dataclass(sicd) + except Exception as e: + logging.error("Exception caught attempting to build SICD sensor model.", e) + return None + + @staticmethod + def from_dataclass(sicd: Union[sicd121.SICD, sicd130.SICD]) -> Optional[SensorModel]: + """ + This method constructs a SICD sensor model from the python dataclasses generated when parsing the XML. + + :param sicd: the dataclass object constructed from the XML + :return: the sensor model; if available + """ + + scp_ecf = WorldCoordinate(xyztype_to_ndarray(sicd.geo_data.scp.ecf)) + scp_pixel = ImageCoordinate([sicd.image_data.scppixel.col, sicd.image_data.scppixel.row]) + time_coa_poly = poly2d_to_native(sicd.grid.time_coapoly) + arp_poly = xyzpoly_to_native(sicd.position.arppoly) + + coord_converter = SARImageCoordConverter( + scp_pixel=scp_pixel, + scp_ecf=scp_ecf, + u_row=xyztype_to_ndarray(sicd.grid.row.uvect_ecf), + u_col=xyztype_to_ndarray(sicd.grid.col.uvect_ecf), + row_ss=sicd.grid.row.ss, + col_ss=sicd.grid.col.ss, + first_pixel=ImageCoordinate([sicd.image_data.first_col, sicd.image_data.first_row]), + ) + + projection_set = None + ugpn = None + if sicd.grid.type_value == sicd121.ImageGridType.RGAZIM: + if sicd.image_formation.image_form_algo == sicd121.ImageFormAlgo.PFA: + ugpn = xyztype_to_ndarray(sicd.pfa.fpn) + projection_set = PFAProjectionSet( + scp_ecf=scp_ecf, + polar_ang_poly=poly1d_to_native(sicd.pfa.polar_ang_poly), + spatial_freq_sf_poly=poly1d_to_native(sicd.pfa.spatial_freq_sfpoly), + coa_time_poly=time_coa_poly, + arp_poly=arp_poly, + ) + elif sicd.image_formation.image_form_algo == sicd121.ImageFormAlgo.RGAZCOMP: + projection_set = RGAZCOMPProjectionSet( + scp_ecf=scp_ecf, az_scale_factor=sicd.rg_az_comp.az_sf, coa_time_poly=time_coa_poly, arp_poly=arp_poly + ) + elif sicd.grid.type_value == sicd121.ImageGridType.RGZERO: + projection_set = INCAProjectionSet( + r_ca_scp=sicd.rma.inca.r_ca_scp, + inca_time_coa_poly=poly1d_to_native(sicd.rma.inca.time_capoly), + drate_sf_poly=poly2d_to_native(sicd.rma.inca.drate_sfpoly), + coa_time_poly=time_coa_poly, + arp_poly=arp_poly, + ) + elif sicd.grid.type_value in [ + sicd121.ImageGridType.PLANE, + sicd121.ImageGridType.XCTYAT, + sicd121.ImageGridType.XRGYCR, + ]: + projection_set = PlaneProjectionSet( + scp_ecf=scp_ecf, + image_plane_urow=xyztype_to_ndarray(sicd.grid.row.uvect_ecf), + image_plane_ucol=xyztype_to_ndarray(sicd.grid.col.uvect_ecf), + coa_time_poly=time_coa_poly, + arp_poly=arp_poly, + ) + else: + logger.warning(f"SICD image with unknown grid type {sicd.grid.type_value}. No sensor model created.") + return None + + sicd_sensor_model = SICDSensorModel( + coord_converter=coord_converter, + coa_projection_set=projection_set, + scp_arp=xyztype_to_ndarray(sicd.scpcoa.arppos), + scp_varp=xyztype_to_ndarray(sicd.scpcoa.arpvel), + side_of_track=str(sicd.scpcoa.side_of_track.value), + u_gpn=ugpn, + ) + + return sicd_sensor_model diff --git a/src/aws/osml/photogrammetry/__init__.py b/src/aws/osml/photogrammetry/__init__.py index 20a746f..f62e01e 100644 --- a/src/aws/osml/photogrammetry/__init__.py +++ b/src/aws/osml/photogrammetry/__init__.py @@ -31,6 +31,17 @@ ) from .rpc_sensor_model import RPCPolynomial, RPCSensorModel from .sensor_model import SensorModel, SensorModelOptions +from .sicd_sensor_model import ( + COAProjectionSet, + INCAProjectionSet, + PFAProjectionSet, + PlaneProjectionSet, + Polynomial2D, + PolynomialXYZ, + RGAZCOMPProjectionSet, + SARImageCoordConverter, + SICDSensorModel, +) from .srtm_dem_tile_set import SRTMTileSet __all__ = [ @@ -47,7 +58,14 @@ "ConstantElevationModel", "ElevationModel", "GDALAffineSensorModel", + "SARImageCoordConverter", + "INCAProjectionSet", + "PlaneProjectionSet", + "PFAProjectionSet", + "PolynomialXYZ", + "Polynomial2D", "ProjectiveSensorModel", + "RGAZCOMPProjectionSet", "RSMContext", "RSMGroundDomain", "RSMGroundDomainForm", @@ -60,5 +78,6 @@ "RPCSensorModel", "SensorModel", "SensorModelOptions", + "SICDSensorModel", "SRTMTileSet", ] diff --git a/src/aws/osml/photogrammetry/sicd_sensor_model.py b/src/aws/osml/photogrammetry/sicd_sensor_model.py new file mode 100644 index 0000000..29f8ef6 --- /dev/null +++ b/src/aws/osml/photogrammetry/sicd_sensor_model.py @@ -0,0 +1,813 @@ +import logging +from abc import abstractmethod +from enum import Enum +from typing import Any, Dict, Optional, Tuple, Union + +import numpy as np +import numpy.typing as npt + +from . import ElevationModel +from .coordinates import ( + GeodeticWorldCoordinate, + ImageCoordinate, + WorldCoordinate, + geocentric_to_geodetic, + geodetic_to_geocentric, +) +from .sensor_model import SensorModel + +logger = logging.getLogger(__name__) + + +class Polynomial2D: + """ + This class contains coefficients for a two-dimensional polynomial. + """ + + def __init__(self, coef: npt.ArrayLike): + """ + Constructor that takes the coefficients of the polynomial. The coefficients should be ordered so that the + coefficient of the term of multi-degree i,j is contained in coef[i,j]. + + :param coef: array-like structure of coefficients + """ + self.coef = np.array(coef) + if len(self.coef.shape) != 2: + raise ValueError( + f"Coefficients for class Polynomial2D must be two-dimensional. " + f"Received numpy.ndarray of shape {self.coef.shape}" + ) + + def __call__(self, x: Union[float, np.ndarray], y: Union[float, np.ndarray]) -> np.ndarray: + """ + Invoke NumPy's polyval2d given the inputs and the coefficients of the polynomial. + + :param x: the first input parameter + :param y: the second input parameter + :return: the values of the 2-d polynomial at points formed with pairs of corresponding values from x and y. + """ + return np.polynomial.polynomial.polyval2d(x, y, self.coef) + + +class PolynomialXYZ: + """ + This class is an aggregation 3 one-dimensional polynomials all with the same input variable. The result of + evaluating this class on the input variable is an [x, y, z] vector. + """ + + def __init__( + self, + x_polynomial: np.polynomial.Polynomial, + y_polynomial: np.polynomial.Polynomial, + z_polynomial: np.polynomial.Polynomial, + ): + """ + Constructor that accepts the 3 NumPy 1-d polynomials one for each component. + + :param x_polynomial: polynomial for the x component + :param y_polynomial: polynomial for the y component + :param z_polynomial: polynomial for the z component + """ + self.x_polynomial = x_polynomial + self.y_polynomial = y_polynomial + self.z_polynomial = z_polynomial + + def __call__(self, t: float) -> np.ndarray: + """ + Evaluate the x, y, and z polynomials at t and return the result as a vector. + + :param t: the value + :return: the polynomial result + """ + x = self.x_polynomial(t) + y = self.y_polynomial(t) + z = self.z_polynomial(t) + + return np.array([x, y, z], dtype=x.dtype) + + def deriv(self, m: int = 1): + """ + Create a new PolynomialXYZ that is the derivative of the current PolynomialXYZ. + + :param m: find the derivative of order m + :return: the new polynomial derivative + """ + x_derivative = self.x_polynomial.deriv(m=m) + y_derivative = self.y_polynomial.deriv(m=m) + z_derivative = self.z_polynomial.deriv(m=m) + + return PolynomialXYZ(x_polynomial=x_derivative, y_polynomial=y_derivative, z_polynomial=z_derivative) + + +class SARImageCoordConverter: + """ + This class contains image grid and image plane coordinate conversions for a provided set of SICD parameters. The + equations are mostly defined in Section 2 of the SICD Standard Volume 3. + """ + + def __init__( + self, + scp_pixel: ImageCoordinate, + scp_ecf: WorldCoordinate, + u_row: np.ndarray, + u_col: np.ndarray, + row_ss: float, + col_ss: float, + first_pixel: ImageCoordinate = ImageCoordinate([0.0, 0.0]), + ): + """ + Construct the coordinate converter given parameters from the metadata. The names of these parameters have been + chosen to align with the names in the specification. + + :param scp_pixel: location of the scene center point (SCP) in the global pixel grid + :param scp_ecf: location of the scene center point (SCP) in earth centered fixed (ECF) coordinates + :param u_row: unit vector in the increasing row direction in ECF coordinates. + :param u_col: unit vector in the increasing column direction in ECF coordinates. + :param row_ss: sample spacing in the row direction + :param col_ss: sample spacing in the column direction + :param first_pixel: location of the first row/column of the pixel array. For a full image array [0, 0] + """ + self.scp_pixel = scp_pixel + self.scp_ecf = scp_ecf + self.row_ss = row_ss + self.col_ss = col_ss + self.u_row = u_row + self.u_col = u_col + self.first_pixel = first_pixel + # Section 2.4 calculation of the image plane unit normal vector + ipn = np.cross(self.u_row, self.u_col) + self.uvect_ipn = ipn / np.linalg.norm(ipn) + + # Section 2.4 calculation of transform from ipp to xrow, ycol + cos_theta = np.dot(self.u_row, self.u_col) + sin_theta = np.sqrt(1 - cos_theta * cos_theta) + ipp_transform = np.array([[1, -cos_theta], [-cos_theta, 1]], dtype="float64") / (sin_theta * sin_theta) + row_col_transform = np.zeros((3, 2), dtype="float64") + row_col_transform[:, 0] = self.u_row + row_col_transform[:, 1] = self.u_col + self.matrix_transform = np.dot(row_col_transform, ipp_transform) + + def rowcol_to_xrowycol(self, row_col: np.ndarray) -> np.ndarray: + """ + This function converts the row and column indexes (row, col) in the global image grid to SCP centered + image coordinates (xrow, ycol) using equations (2) (3) in Section 2.2 of the SICD Specification + Volume 3. + + :param row_col: the [row, col] location as an array + :return: the [xrow, ycol] location as an array + """ + xrow_ycol = np.zeros(2, dtype="float64") + xrow_ycol[0] = (row_col[0] - self.scp_pixel.r) * self.row_ss + xrow_ycol[1] = (row_col[1] - self.scp_pixel.c) * self.col_ss + return xrow_ycol + + def xrowycol_to_rowcol(self, xrow_ycol: np.ndarray) -> np.ndarray: + """ + This function converts the SCP centered image coordinates (xrow, ycol) to row and column indexes (row, col) + in the global image grid using equations (2) (3) in Section 2.2 of the SICD Specification Volume 3. + + :param xrow_ycol: the [xrow, ycol] location as an array + :return: the [row, col] location as an array + """ + row_col = np.zeros(2, dtype="float64") + row_col[0] = xrow_ycol[0] / self.row_ss + self.scp_pixel.r + row_col[1] = xrow_ycol[1] / self.col_ss + self.scp_pixel.c + return row_col + + def xrowycol_to_ipp(self, xrow_ycol: np.ndarray) -> np.ndarray: + """ + This function converts SCP centered image coordinates (xrow, ycol) to a ECF coordinate, image plane point (IPP), + on the image plane using equations in Section 2.4 of the SICD Specification Volume 3. + + :param xrow_ycol: the [xrow, ycol] location as an array + :return: the image plane point [x, y, z] ECF location on the image plane + """ + delta_ipp = xrow_ycol[0] * self.u_row + xrow_ycol[1] * self.u_col + return self.scp_ecf.coordinate + delta_ipp + + def ipp_to_xrowycol(self, ipp: np.ndarray) -> np.ndarray: + """ + This function converts an ECF location on the image plane into SCP centered image coordinates (xrow, ycol) + using equations in Section 2.4 of the SICD Specification volume 3. + + :param ipp: the image plane point [x, y, z] ECF location on the image plane + :return: the [xrow, ycol] location as an array + """ + delta_ipp = ipp - self.scp_ecf.coordinate + xrow_ycol = np.dot(delta_ipp, self.matrix_transform) + return xrow_ycol + + +class COAProjectionSet: + """ + This is an abstract base class for R/Rdot projection contour computations described in Section 4 of the SICD + Standard Volume 3. + """ + + def __init__( + self, + coa_time_poly: Polynomial2D, + arp_poly: PolynomialXYZ, + delta_arp: np.ndarray = np.array([0.0, 0.0, 0.0], dtype="float64"), + delta_varp: np.ndarray = np.array([0.0, 0.0, 0.0], dtype="float64"), + range_bias: float = 0.0, + ): + """ + Constructor with parameters supporting the calculations common to all R/Rdot projections (i.e. the + calculations that do not depend on grid type and image formation algorithm). + + :param coa_time_poly: Center Of Aperture (COA) time polynomial. + :param arp_poly: Aperture Reference Point (ARP) position polynomial coefficients. + :param delta_arp: the ARP position offset + :param delta_varp: the ARP velocity offset + :param range_bias: the range bias offset + """ + self.coa_time_poly = coa_time_poly + self.arp_poly = arp_poly + self.varp_poly = self.arp_poly.deriv(m=1) + + self.delta_arp = delta_arp + self.delta_varp = delta_varp + self.range_bias = float(range_bias) + + def precise_rrdot_computation( + self, xrow_ycol: np.ndarray + ) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]: + """ + This executes the precise image pixel grid location to R/Rdot projection. This function invokes + the _grid_specific_projection() function implemented by subclasses which should handle the portions + of the calculation that are dependent on the image grid and image formation algorithm. + + :param xrow_ycol: the [xrow, ycol] location as an array + :return: the COA projection set { Rcoa, Rdotcoa, tcoa, arpcoa, varpcoa } + """ + # These are the common calculations for image COA time (coa_time), COA ARP position and velocity + # (arp_position and arp_velocity) as described in Section 2 of the SICD specification Volume 3. + coa_time = self.coa_time_poly(xrow_ycol[0], xrow_ycol[1]) + arp_position = self.arp_poly(coa_time) + arp_velocity = self.varp_poly(coa_time) + + # These are the image grid and image formation algorithm dependent calculations for the precise + # computation of the R/Rdot contour. Each subclass should implement an approach as described in + # sections 4.1 through 4.6 of the SICD specification Volume 3. + r_tgt_coa, r_dot_tgt_coa = self._grid_specific_projection(xrow_ycol, coa_time, arp_position, arp_velocity) + + # If provided the Adjustable Parameter Offsets are incorporated into the computation from + # image pixel location to COA projection parameters. See Section 8.1 of the SICD specification Volume 3. + # TODO: Check this. This is the same approach as SarPy but I'm not 100% sure it is correct + arp_position += self.delta_arp + arp_velocity += self.delta_varp + r_tgt_coa += self.range_bias + + return r_tgt_coa, r_dot_tgt_coa, coa_time, arp_position, arp_velocity + + @abstractmethod + def _grid_specific_projection(self, xrow_ycol, coa_time, arp_position, arp_velocity) -> Tuple[np.ndarray, np.ndarray]: + """ + The precise computation of the R/Rdot contour is dependent upon the image grid type and the image + formation algorithm that produced the image but the computation of the COA time, ARP position, and + velocity is the same for all image products. + + This abstract method should be overriden by subclasses to perform the R/Rdot calculations for a + specific image grid and formation algorithm. + + :param xrow_ycol: the [xrow, ycol] location as an array + :param coa_time: Center Of Aperture (COA) time + :param arp_position: Aperture Reference Point (ARP) position + :param arp_velocity: Aperture Reference Point (ARP) velocity + :return: the tuple containing range and range rate relative to the ARP at COA time + """ + + +class PFAProjectionSet(COAProjectionSet): + """ + This Center Of Aperture (COA) Projection set is to be used with a range azimuth image grid (RGAZIM) and polar + formatted (PFA) phase history data. See section 4.1 of the SICD Specification Volume 3. + """ + + def __init__( + self, + scp_ecf: WorldCoordinate, + polar_ang_poly, + spatial_freq_sf_poly, + coa_time_poly: Polynomial2D, + arp_poly: PolynomialXYZ, + delta_arp: np.ndarray = np.array([0.0, 0.0, 0.0], dtype="float64"), + delta_varp: np.ndarray = np.array([0.0, 0.0, 0.0], dtype="float64"), + range_bias: float = 0.0, + ): + """ + Constructor for this projection set. + + :param scp_ecf: Scene Center Point position in ECF coordinates + :param polar_ang_poly: Polar Angle polynomial coefficients + :param spatial_freq_sf_poly: Spatial Frequency Scale Factor polynomial coefficients + :param coa_time_poly: Center Of Aperture (COA) time polynomial + :param arp_poly: Aperture Reference Point (ARP) position polynomial coefficients + :param delta_arp: the ARP position offset + :param delta_varp: the ARP velocity offset + :param range_bias: the range bias offset + """ + super().__init__(coa_time_poly, arp_poly, delta_arp, delta_varp, range_bias) + self.scp_ecf = scp_ecf + self.polar_ang_poly = polar_ang_poly + self.spatial_freq_sf_poly = spatial_freq_sf_poly + self.polar_ang_poly_der = polar_ang_poly.deriv(m=1) + self.spatial_freq_sf_poly_der = spatial_freq_sf_poly.deriv(m=1) + + def _grid_specific_projection(self, xrow_ycol, coa_time, arp_position, arp_velocity) -> Tuple[np.ndarray, np.ndarray]: + """ + These are the calculations for the precise computation of the R/Rdot contour unique to these grid and + image formation algorithm types. See SICD Volume 3 Section 4.1 + + :param xrow_ycol: the [xrow, ycol] location as an array + :param coa_time: Center Of Aperture (COA) time + :param arp_position: Aperture Reference Point (ARP) position + :param arp_velocity: Aperture Reference Point (ARP) velocity + :return: the tuple containing range and range rate relative to the ARP at COA time + """ + # For the RGAZIM grid, the image coordinates are range and azimuth. The row coordinate is the range + # coordinate, xrow = rg. The column coordinate is the azimuth coordinate, ycol = az. + rg = xrow_ycol[0] + az = xrow_ycol[1] + + # (2) Compute the range and range rate to the SCP at the pixel COA time + arp_minus_scp = arp_position - self.scp_ecf.coordinate + range_to_scp = np.linalg.norm(arp_minus_scp, axis=-1) + rdot_to_scp = np.sum(arp_velocity * arp_minus_scp, axis=-1) / range_to_scp + + # (3) Compute the polar angle (theta) and its derivative with respect to time (d_theta_d_time) + # at the pixel COA time. + theta = self.polar_ang_poly(coa_time) + d_theta_d_time = self.polar_ang_poly_der(coa_time) + + # (4) Compute the polar aperture scale factor (KSF) and its derivative with respect to polar angle + # (d_ksf_d_theta) at the pixel COA time + ksf = self.spatial_freq_sf_poly(theta) + d_ksf_d_theta = self.spatial_freq_sf_poly_der(theta) + + # (5) Compute the spatial frequency domain phase slopes in the radial (ka) and cross radial + # (kc) directions (d_phi_d_ka and d_phi_d_kc) for the radial direction at theta. Note: The sign COA + # parameter (SGN) for the phase may be ignored as it is cancelled in a subsequent computation. + d_phi_d_ka = rg * np.cos(theta) + az * np.sin(theta) + d_phi_d_kc = -rg * np.sin(theta) + az * np.cos(theta) + + # (6) Compute range relative to the SCP (delta_range) at the COA. + delta_range = ksf * d_phi_d_ka + + # (7) Compute the derivative of the range relative to the SCP with respect to polar angle + # (d_delta_range_d_theta) at the COA. Scale by the derivative of the polar angle with respect + # to time to yield the derivative with respect to time (delta_r_dot_tgt_coa). + d_delta_range_d_theta = d_ksf_d_theta * d_phi_d_ka + ksf * d_phi_d_kc + delta_r_dot_tgt_coa = d_delta_range_d_theta * d_theta_d_time + + # (8) Compute the range and range rate relative to the ARP at COA ( r_tgt_coa and rdot_tgt_coa). + # The projection to three-dimensional scene point for grid location (rgTGT, azTGT) is along this + # R/Rdot contour. + r_tgt_coa = range_to_scp + delta_range + rdot_tgt_coa = rdot_to_scp + delta_r_dot_tgt_coa + + return r_tgt_coa, rdot_tgt_coa + + +class RGAZCOMPProjectionSet(COAProjectionSet): + def __init__( + self, + scp_ecf: WorldCoordinate, + az_scale_factor: float, + coa_time_poly: Polynomial2D, + arp_poly: PolynomialXYZ, + delta_arp: np.ndarray = np.array([0.0, 0.0, 0.0], dtype="float64"), + delta_varp: np.ndarray = np.array([0.0, 0.0, 0.0], dtype="float64"), + range_bias: float = 0.0, + ): + """ + Constructor for this projection set. + + :param scp_ecf: Scene Center Point position in ECF coordinates + :param az_scale_factor: Scale factor that converts azimuth coordinate to an increment in cosine of the DCA at COA + :param coa_time_poly: Center Of Aperture (COA) time polynomial + :param arp_poly: Aperture Reference Point (ARP) position polynomial coefficients + :param delta_arp: the ARP position offset + :param delta_varp: the ARP velocity offset + :param range_bias: the range bias offset + """ + super().__init__(coa_time_poly, arp_poly, delta_arp, delta_varp, range_bias) + self.scp_ecf = scp_ecf + self.az_scale_factor = az_scale_factor + + def _grid_specific_projection(self, xrow_ycol, coa_time, arp_position, arp_velocity) -> Tuple[np.ndarray, np.ndarray]: + """ + These are the calculations for the precise computation of the R/Rdot contour unique to these grid and + image formation algorithm types. See SICD Volume 3 Section 4.2 + + :param xrow_ycol: the [xrow, ycol] location as an array + :param coa_time: Center Of Aperture (COA) time + :param arp_position: Aperture Reference Point (ARP) position + :param arp_velocity: Aperture Reference Point (ARP) velocity + :return: the tuple containing range and range rate relative to the ARP at COA time + """ + # For the RGAZIM grid, the image coordinates are range and azimuth. The row coordinate is the range + # coordinate, xrow = rg. The column coordinate is the azimuth coordinate, ycol = az. + rg = xrow_ycol[0] + az = xrow_ycol[1] + + # (2) Compute the range and range rate to the SCP at COA. + arp_minus_scp = arp_position - self.scp_ecf.coordinate + range_to_scp = np.linalg.norm(arp_minus_scp, axis=-1) + rdot_to_scp = np.sum(arp_velocity * arp_minus_scp, axis=-1) / range_to_scp + + # (3) Compute the increment in cosine of the DCA at COA of the target (delta_cos_dca_tgt_coa) by + # scaling the azimuth coordinate by the azimuth to DCA scale factor. Compute the increment + # in range rate (delta_rdot_tgt_coa) by scaling by the magnitude of the velocity vector at COA. + delta_cos_dca_tgt_coa = self.az_scale_factor * az + delta_r_dot_tgt_coa = -np.linalg.norm(arp_velocity, axis=-1) * delta_cos_dca_tgt_coa + + # (4) Compute the range and range rate to the target at COA as follows. + r_tgt_coa = range_to_scp + rg + rdot_tgt_coa = rdot_to_scp + delta_r_dot_tgt_coa + + return r_tgt_coa, rdot_tgt_coa + + +class INCAProjectionSet(COAProjectionSet): + def __init__( + self, + r_ca_scp: float, + inca_time_coa_poly: np.polynomial.Polynomial, + drate_sf_poly: Polynomial2D, + coa_time_poly: Polynomial2D, + arp_poly: PolynomialXYZ, + delta_arp: np.ndarray = np.array([0.0, 0.0, 0.0], dtype="float64"), + delta_varp: np.ndarray = np.array([0.0, 0.0, 0.0], dtype="float64"), + range_bias: float = 0.0, + ): + """ + Constructor for this projection set. + + :param r_ca_scp: Range at Closest Approach for the SCP (m) + :param inca_time_coa_poly: Time of Closest Approach polynomial coefficients + :param drate_sf_poly: Doppler Rate Scale Factor polynomial coefficients + :param coa_time_poly: Center Of Aperture (COA) time polynomial + :param arp_poly: Aperture Reference Point (ARP) position polynomial coefficients + :param delta_arp: the ARP position offset + :param delta_varp: the ARP velocity offset + :param range_bias: the range bias offset + """ + super().__init__(coa_time_poly, arp_poly, delta_arp, delta_varp, range_bias) + self.r_ca_scp = r_ca_scp + self.inca_time_coa_poly = inca_time_coa_poly + self.drate_sf_poly = drate_sf_poly + + def _grid_specific_projection(self, xrow_ycol, coa_time, arp_position, arp_velocity) -> Tuple[np.ndarray, np.ndarray]: + """ + These are the calculations for the precise computation of the R/Rdot contour unique to these grid and + image formation algorithm types. See SICD Volume 3 Section 4.3 + + :param xrow_ycol: the [xrow, ycol] location as an array + :param coa_time: Center Of Aperture (COA) time + :param arp_position: Aperture Reference Point (ARP) position + :param arp_velocity: Aperture Reference Point (ARP) velocity + :return: the tuple containing range and range rate relative to the ARP at COA time + """ + # For the RGZERO grid, the image coordinates are range and azimuth. The row coordinate is the range + # coordinate, xrow = rg. The column coordinates is the azimuth coordinate, ycol = az. + rg = xrow_ycol[0] + az = xrow_ycol[1] + + # (2) Compute the range at closest approach and the time of closest approach for the image + # grid location. The range at closest approach, R TGT , is computed from the range coordinate. + # The time of closest approach, tTGT , is computed from the azimuth coordinate. CA + range_ca_tgt = self.r_ca_scp + rg + time_ca_tgt = self.inca_time_coa_poly(az) + + # (2 repeated in v1.3.0 of the spec) Compute the ARP velocity at the time of closest approach + # and the magnitude of the vector. + arp_velocity_ca_tgt = self.varp_poly(time_ca_tgt) + mag_arp_velocity_ca_tgt = np.sum(arp_velocity_ca_tgt, axis=-1) + + # (3) Compute the Doppler Rate Scale Factor (drsf_tgt) for image grid location (rg, az). + drsf_tgt = self.drate_sf_poly(rg, az) + + # (4) Compute the time difference between the COA time and the CA time (delta_coa_tgt). + delta_coa_tgt = coa_time - time_ca_tgt + + # (5) Compute the range and range rate relative to the ARP at COA ( RTGT and RdotTGT ). + r_tgt_coa = np.sqrt(range_ca_tgt**2 + drsf_tgt * mag_arp_velocity_ca_tgt**2 * delta_coa_tgt**2) + r_dot_tgt_coa = (drsf_tgt / r_tgt_coa) * mag_arp_velocity_ca_tgt**2 * delta_coa_tgt + + return r_tgt_coa, r_dot_tgt_coa + + +class PlaneProjectionSet(COAProjectionSet): + def __init__( + self, + scp_ecf: WorldCoordinate, + image_plane_urow: np.ndarray, + image_plane_ucol: np.ndarray, + coa_time_poly: Polynomial2D, + arp_poly: PolynomialXYZ, + delta_arp: np.ndarray = np.array([0.0, 0.0, 0.0], dtype="float64"), + delta_varp: np.ndarray = np.array([0.0, 0.0, 0.0], dtype="float64"), + range_bias: float = 0.0, + ): + """ + Constructor for this projection set. + + :param scp_ecf: Scene Center Point position in ECF coordinates + :param image_plane_urow: Unit vector in the increasing row direction in ECF coordinates (uRow) + :param image_plane_ucol: Unit vector in the increasing column direction in ECF coordinates (uCol) + :param coa_time_poly: Center Of Aperture (COA) time polynomial + :param arp_poly: Aperture Reference Point (ARP) position polynomial coefficients + :param delta_arp: the ARP position offset + :param delta_varp: the ARP velocity offset + :param range_bias: the range bias offset + """ + super().__init__(coa_time_poly, arp_poly, delta_arp, delta_varp, range_bias) + self.scp_ecf = scp_ecf + self.image_plane_urow = image_plane_urow + self.image_plane_ucol = image_plane_ucol + + def _grid_specific_projection(self, xrow_ycol, coa_time, arp_position, arp_velocity) -> Tuple[np.ndarray, np.ndarray]: + """ + These are the calculations for the precise computation of the R/Rdot contour unique to these grid and + image formation algorithm types. See SICD Volume 3 Sections 4.4, 4.5, and 4.6. + + Note that the calculations in sections 4.4, 4.5, and 4.6 are the same with the only difference being the + interpretation of the xrow and ycol gird positions. To share this one implementation for all three grid + planes assume: xrow = xrg = xct and ycol = ycr = yat + + :param xrow_ycol: the [xrow, ycol] location as an array + :param coa_time: Center Of Aperture (COA) time + :param arp_position: Aperture Reference Point (ARP) position + :param arp_velocity: Aperture Reference Point (ARP) velocity + :return: the tuple containing range and range rate relative to the ARP at COA time + """ + + # xrow = xrg = xct and ycol = ycr = yat + + # (2) The samples of an XRGYCR, XCTYAT, or PLANE grid are uniformly spaced locations in the image plane + # formed by the SCP, and image plane vectors uRow and uCol. Vectors uRow and uCol are orthogonal. Compute + # the point the image plane point for image grid location (xrgTGT, ycrTGT). + image_plane_point = ( + self.scp_ecf.coordinate + xrow_ycol[0] * self.image_plane_urow + xrow_ycol[1] * self.image_plane_ucol + ) + + # (3) Compute the range and range rate relative to the ARP at COA (r_tgt_coa and rdot_tgt_coa) for image plane + # point (image_plane_point). + arp_minus_ipp = arp_position - image_plane_point + r_tgt_coa = np.linalg.norm(arp_minus_ipp, axis=-1) + rdot_tgt_coa = np.sum(arp_velocity * arp_minus_ipp, axis=-1) / r_tgt_coa + + return r_tgt_coa, rdot_tgt_coa + + +class RRDotSurfaceProjection: + """ + This is the base class for calculations that project the R/RDot contour onto a surface model. The SICD specification + defines a way to do this for planes, a surface of constant height above an ellipsoid, or a digital elevation model. + """ + + @abstractmethod + def rrdot_to_ground(self, r_tgt_coa, r_dot_tgt_coa, arp_position, arp_velocity) -> np.ndarray: + """ + Subclasses should implement this method to compute the R/RDot Contour Ground Plane intersection with a + specific surface type (e.g. planar, HAE, DEM) + + :param r_tgt_coa: target COA range + :param r_dot_tgt_coa: target COA range rate + :param arp_position: ARP position + :param arp_velocity: ARP velocity + :return: the intersection between the R/Rdot Contour and the ground plane + """ + + +class GroundPlaneRRDotSurfaceProjection(RRDotSurfaceProjection): + """ + This class implements the Precise R/RDot Ground Plane Projection described in Section 5 of the SICD Specification + Volume 3 (v1.3.0). + """ + + class GroundPlaneNormalType(Enum): + SPHERICAL_EARTH = "SPHERICAL_EARTH" + GEODETIC_EARTH = "GEODETIC_EARTH" + + def __init__( + self, + ref_ecf: WorldCoordinate, + gpn: Optional[np.ndarray], + gpn_type: GroundPlaneNormalType = GroundPlaneNormalType.GEODETIC_EARTH, + ): + """ + The ground plane is defined by a reference point in the plane (ref_ect) and the vector normal to the plane + (gpn). The reference point and plane orientation may be based upon specific terrain height and slope + information for the imaged area. When only a reference point is specified, a ground plane normal may be + derived assuming the plane is tangent to a spherical earth model or a surface of constant geodetic height + above the WGS-84 reference ellipsoid passing through (ref_ect). + + :param ref_ecf: reference point in the plane, GREF in the specification + :param gpn: optional vector normal to the ground plane; if missing it will be computed using gpn_type + :param gpn_type: method to derive the ground plan normal + """ + self.ref_ecf = ref_ecf + + if gpn is not None: + self.u_gpn = gpn / np.linalg.norm(gpn) + elif gpn_type == self.GroundPlaneNormalType.SPHERICAL_EARTH: + self.u_gpn = ref_ecf.coordinate / np.linalg.norm(ref_ecf.coordinate) + elif gpn_type == self.GroundPlaneNormalType.GEODETIC_EARTH: + ref_lle = geocentric_to_geodetic(ref_ecf) + self.u_gpn = np.array( + [ + np.cos(ref_lle.latitude) * np.cos(ref_lle.longitude), + np.cos(ref_lle.latitude) * np.sin(ref_lle.longitude), + np.sin(ref_lle.latitude), + ] + ) + else: + raise ValueError(f"Provided gpn_type, {gpn_type}, is invalid.") + + def rrdot_to_ground(self, r_tgt_coa, r_dot_tgt_coa, arp_position, arp_velocity) -> np.ndarray: + """ + This method implements the R/RDot Contour Ground Plane Intersection described in section 5.2 + + :param r_tgt_coa: target COA range + :param r_dot_tgt_coa: target COA range rate + :param arp_position: ARP position + :param arp_velocity: ARP velocity + :return: the intersection between the R/Rdot Contour and the ground plane + """ + # (1) Compute the unit vector in the +Z direction (normal to the ground plane). + uvect_z = self.u_gpn / np.linalg.norm(self.u_gpn) + + # (2) Compute the ARP distance from the plane (arp_z). Also compute the ARP ground plane nadir (agpn). + arp_z = np.sum((arp_position - self.ref_ecf.coordinate) * uvect_z, axis=-1) + if arp_z > r_tgt_coa: + raise ValueError("No solution exists. Distance between ARP and the plane is greater than range.") + + agpn = arp_position - np.outer(arp_z, uvect_z) + + # (3) Compute the ground plane distance (gp_distance) from the ARP nadir to the circle of constant range. Also + # compute the sine and cosine of the grazing angle (sin_graz and cos_graz). + gp_distance = np.sqrt(r_tgt_coa * r_tgt_coa - arp_z * arp_z) + sin_graz = arp_z / r_tgt_coa + cos_graz = gp_distance / r_tgt_coa + + # (4) Compute velocity components normal to the ground plane (v_z) and parallel to the ground plane (v_x). + v_z = np.dot(arp_velocity, uvect_z) + v_mag = np.linalg.norm(arp_velocity, axis=-1) + v_x = np.sqrt(v_mag * v_mag - v_z * v_z) + + # (5) Orient the +X direction in the ground plane such that the v_x > 0. Compute unit vectors uvect_x + # and uvect_y. + uvect_x = (arp_velocity - (v_z * uvect_z)) / v_x + uvect_y = np.cross(uvect_z, uvect_x) + + # (6) Compute the cosine of the azimuth angle to the ground plane point. + cos_az = (-r_dot_tgt_coa + v_z * sin_graz) / (v_x * cos_graz) + if np.abs(cos_az) > 1: + raise ValueError("No solution exists. cos_az < -1 or cos_az > 1.") + + # (7) Compute the sine of the azimuth angle. Use parameter LOOK to establish the correct sign corresponding + # to the correct Side of Track. + look = np.sign(np.dot(np.cross(arp_position - self.ref_ecf.coordinate, arp_velocity), uvect_z)) + sin_az = look * np.sqrt(1 - cos_az * cos_az) + + # (8) Compute GPPTGT at distance G from the AGPN and at the correct azimuth angle. + return agpn + uvect_x * (gp_distance * cos_az) + uvect_y * (gp_distance * sin_az) + + +class SICDSensorModel(SensorModel): + """ + This is an implementation of the SICD sensor model as described by SICD Volume 3 Image Projections Description + NGA.STND.0024-3_1.3.0 (2021-11-30) + """ + + def __init__( + self, + coord_converter: SARImageCoordConverter, + coa_projection_set: COAProjectionSet, + scp_arp: np.ndarray, + scp_varp: np.ndarray, + side_of_track: str, + u_gpn: Optional[np.ndarray] = None, + ): + """ + Constructs a SICD sensor model from the information derived from the XML metadata. + + :param coord_converter: converts coordinates between image grid and image plane + :param coa_projection_set: projects image locations to the r/rdot contour + :param scp_arp: aperture reference point position + :param scp_varp: aperture reference point velocity + :param side_of_track: side of track imaged + :param u_gpn: optional unit normal for ground plane + """ + super().__init__() + self.coa_projection_set = coa_projection_set + self.image_plane = coord_converter + self.uvect_gpn = u_gpn + self.scp_arp = scp_arp + self.scp_varp = scp_varp + self.side_of_track = side_of_track + + self.uvect_spn = np.cross(scp_varp, coord_converter.scp_ecf.coordinate - scp_arp) + if side_of_track == "R": + self.uvect_spn *= -1.0 + self.uvect_spn /= np.linalg.norm(self.uvect_spn) + + # TODO: Add option for HAE ground assumption, does world_to_image always need a GroundPlaneProjection? + self.default_surface_projection = GroundPlaneRRDotSurfaceProjection(self.image_plane.scp_ecf, self.uvect_gpn) + + def image_to_world( + self, + image_coordinate: ImageCoordinate, + elevation_model: Optional[ElevationModel] = None, + options: Optional[Dict[str, Any]] = None, + ) -> GeodeticWorldCoordinate: + """ + This is an implementation of an Image Grid to Scene point projection that first projects the image + location to the R/RDot contour and then intersects the R/RDot contour with the elevation model. + + :param image_coordinate: the x,y image coordinate + :param elevation_model: the optional elevation model, if none supplied a plane tangent to SCP is assumed + :param options: no additional options are supported at this time + :return: the lon, lat, elev geodetic coordinate of the surface matching the image coordinate + """ + row_col = np.array( + [image_coordinate.r + self.image_plane.first_pixel.r, image_coordinate.c + self.image_plane.first_pixel.c] + ) + xrow_ycol = self.image_plane.rowcol_to_xrowycol(row_col=row_col) + r_tgt_coa, r_dot_tgt_coa, time_coa, arp_coa, varp_coa = self.coa_projection_set.precise_rrdot_computation(xrow_ycol) + + if elevation_model is not None: + raise NotImplementedError("SICD sensor model with DEM not yet implemented") + else: + surface_projection = self.default_surface_projection + + # Note that for a DEM the r/rdot contour may intersect the surface at multiple locations + # resulting in an ambiguous location. Here we are arbitrarily selecting the first result. + # TODO: Is there a better way to handle multiple DEM intersections? + coords_ecf = surface_projection.rrdot_to_ground(r_tgt_coa, r_dot_tgt_coa, arp_coa, varp_coa) + + return geocentric_to_geodetic(WorldCoordinate(coords_ecf[0])) + + def world_to_image(self, world_coordinate: GeodeticWorldCoordinate) -> ImageCoordinate: + """ + This is an implementation of Section 6.1 Scene To Image Grid Projection for a single point. + + :param world_coordinate: lon, lat, elevation coordinate of the scene point + :return: the x,y pixel location in this image + """ + ecf_world_coordinate = geodetic_to_geocentric(world_coordinate) + + # TODO: Consider making these options like we have for image_to_world + tolerance = 1e-2 + max_iterations = 10 + + # (2) Ground plane points are projected along straight lines to the image plane to establish points. + # The GP to IP projection direction is along the SCP COA slant plane normal. Also, compute the image + # plane unit normal, uIPN. Compute projection scale factor SF as shown. + uvect_proj = self.uvect_spn + scale_factor = float(np.dot(uvect_proj, self.image_plane.uvect_ipn)) + + # (3) Set initial ground plane position G1 to the scene point position S. + scene_point = np.array([ecf_world_coordinate.x, ecf_world_coordinate.y, ecf_world_coordinate.z]) + g_n = scene_point.copy() + + xrow_ycol_n = None + cont = True + iteration = 0 + while cont: + iteration += 1 + + # (4) Project ground plane point g_n to image plane point i_n. The projection distance is dist_n. Compute + # image coordinates xrown and ycoln. + dist_n = np.dot(self.image_plane.scp_ecf.coordinate - g_n, self.image_plane.uvect_ipn) / scale_factor + i_n = g_n + dist_n * uvect_proj + xrow_ycol_n = self.image_plane.ipp_to_xrowycol(i_n) + + # (5) Compute the precise projection for image grid location (xrown, ycoln) to the ground plane containing + # the scene point S. The result is point p_n. For image grid location (xrown, ycoln), compute COA + # parameters per Section 2. Compute the precise R/Rdot projection contour per Section 4. Compute the + # R/Rdot intersection with the ground plane per Section 5. + r_tgt_coa, r_dot_tgt_coa, time_coa, arp_coa, varp_coa = self.coa_projection_set.precise_rrdot_computation( + xrow_ycol_n + ) + p_n = self.default_surface_projection.rrdot_to_ground(r_tgt_coa, r_dot_tgt_coa, arp_coa, varp_coa) + + # (6) Compute the displacement between ground plane point Pn and the scene point S. + diff_n = scene_point - p_n[0] + delta_gpn = np.linalg.norm(diff_n) + g_n += diff_n + + # If the displacement is greater than the threshold (GP_MAX), compute point Gn+1 and repeat the + # projections in steps (4) and (5) above. If the displacement is less than the threshold, accept image + # grid location (xrown, ycoln) as the precise image grid location for scene point S. + cont = delta_gpn > tolerance and (iteration < max_iterations) + + row_col = self.image_plane.xrowycol_to_rowcol(xrow_ycol_n) + + # Convert the row_col image grid location to an x,y image coordinate. Note that row_col is in reference + # to the full image, so we subtract off the first_pixel offset to make the image coordinate correct if this + # is a chip. + return ImageCoordinate([row_col[1] - self.image_plane.first_pixel.x, row_col[0] - self.image_plane.first_pixel.y]) diff --git a/test/aws/osml/gdal/test_sensor_model_factory.py b/test/aws/osml/gdal/test_sensor_model_factory.py index c3f3e83..f4d0f67 100644 --- a/test/aws/osml/gdal/test_sensor_model_factory.py +++ b/test/aws/osml/gdal/test_sensor_model_factory.py @@ -166,6 +166,35 @@ def test_sensor_model_builder_gcps(self): np.array([radians(121.7518378), radians(13.89145147), 0.0]), ) + def test_sicd_sensor_models(self): + from aws.osml.gdal.sensor_model_factory import SensorModelFactory + from aws.osml.photogrammetry import ImageCoordinate, SICDSensorModel, geocentric_to_geodetic + + test_examples = [ + "./test/data/sicd/capella-sicd121-chip1.ntf", + "./test/data/sicd/capella-sicd121-chip2.ntf", + "./test/data/sicd/umbra-sicd121-chip1.ntf", + ] + for image_path in test_examples: + ds = gdal.Open(image_path) + xml_dess = ds.GetMetadata("xml:DES") + factory = SensorModelFactory(ds.RasterXSize, ds.RasterYSize, xml_dess=xml_dess) + sm = factory.build() + assert sm is not None + assert isinstance(sm, SICDSensorModel) + + scp_image_coord = ImageCoordinate( + [ + sm.image_plane.scp_pixel.x - sm.image_plane.first_pixel.x, + sm.image_plane.scp_pixel.y - sm.image_plane.first_pixel.y, + ] + ) + scp_world_coord = geocentric_to_geodetic(sm.image_plane.scp_ecf) + + assert np.allclose(scp_image_coord.coordinate, sm.world_to_image(scp_world_coord).coordinate, atol=1.0) + + assert np.allclose(scp_world_coord.coordinate, sm.image_to_world(scp_image_coord).coordinate) + if __name__ == "__main__": unittest.main() diff --git a/test/aws/osml/photogrammetry/test_sicd_sensor_model.py b/test/aws/osml/photogrammetry/test_sicd_sensor_model.py new file mode 100644 index 0000000..357e1b0 --- /dev/null +++ b/test/aws/osml/photogrammetry/test_sicd_sensor_model.py @@ -0,0 +1,197 @@ +import unittest +from math import radians +from pathlib import Path + +import numpy as np +from xsdata.formats.dataclass.parsers import XmlParser + +import aws.osml.formats.sicd.models.sicd_v1_2_1 as sicd121 +from aws.osml.gdal.sicd_sensor_model_builder import poly1d_to_native, poly2d_to_native, xyzpoly_to_native, xyztype_to_ndarray +from aws.osml.photogrammetry import ( + GeodeticWorldCoordinate, + ImageCoordinate, + INCAProjectionSet, + PFAProjectionSet, + PlaneProjectionSet, + SARImageCoordConverter, + SICDSensorModel, + WorldCoordinate, + geodetic_to_geocentric, +) + + +class TestSICDSensorModel(unittest.TestCase): + def setUp(self): + pass + + def tearDown(self): + pass + + def test_xrgycr(self): + sicd: sicd121.SICD = XmlParser().from_path(Path("./test/data/sicd/example.sicd121.rma.xml")) + + scp_ecf = WorldCoordinate(xyztype_to_ndarray(sicd.geo_data.scp.ecf)) + scp_pixel = ImageCoordinate([sicd.image_data.scppixel.col, sicd.image_data.scppixel.row]) + + time_coa_poly = poly2d_to_native(sicd.grid.time_coapoly) + arp_poly = xyzpoly_to_native(sicd.position.arppoly) + + first_pixel = ImageCoordinate([sicd.image_data.first_col, sicd.image_data.first_row]) + + coord_converter = SARImageCoordConverter( + scp_pixel=scp_pixel, + scp_ecf=scp_ecf, + u_row=xyztype_to_ndarray(sicd.grid.row.uvect_ecf), + u_col=xyztype_to_ndarray(sicd.grid.col.uvect_ecf), + row_ss=sicd.grid.row.ss, + col_ss=sicd.grid.col.ss, + first_pixel=first_pixel, + ) + + projection_set = PlaneProjectionSet( + scp_ecf=scp_ecf, + image_plane_urow=xyztype_to_ndarray(sicd.grid.row.uvect_ecf), + image_plane_ucol=xyztype_to_ndarray(sicd.grid.col.uvect_ecf), + coa_time_poly=time_coa_poly, + arp_poly=arp_poly, + ) + + sicd_sensor_model = SICDSensorModel( + coord_converter=coord_converter, + coa_projection_set=projection_set, + scp_arp=xyztype_to_ndarray(sicd.scpcoa.arppos), + scp_varp=xyztype_to_ndarray(sicd.scpcoa.arpvel), + side_of_track=str(sicd.scpcoa.side_of_track.value), + ) + + geodetic_world_coordinate = sicd_sensor_model.image_to_world( + ImageCoordinate([sicd.image_data.scppixel.col, sicd.image_data.scppixel.row]) + ) + ecf_world_coordinate = geodetic_to_geocentric(geodetic_world_coordinate) + + assert np.allclose(ecf_world_coordinate.coordinate, scp_ecf.coordinate) + + geo_scp_world_coordinate = GeodeticWorldCoordinate( + [radians(sicd.geo_data.scp.llh.lon), radians(sicd.geo_data.scp.llh.lat), sicd.geo_data.scp.llh.hae] + ) + + assert np.allclose(geo_scp_world_coordinate.coordinate, geodetic_world_coordinate.coordinate) + + calculated_image_scp = sicd_sensor_model.world_to_image(geo_scp_world_coordinate) + + assert np.allclose(calculated_image_scp.coordinate, scp_pixel.coordinate) + + def test_rgzero_inca(self): + sicd: sicd121.SICD = XmlParser().from_path(Path("./test/data/sicd/example.sicd121.capella.xml")) + + scp_ecf = WorldCoordinate(xyztype_to_ndarray(sicd.geo_data.scp.ecf)) + scp_pixel = ImageCoordinate([sicd.image_data.scppixel.col, sicd.image_data.scppixel.row]) + + time_coa_poly = poly2d_to_native(sicd.grid.time_coapoly) + arp_poly = xyzpoly_to_native(sicd.position.arppoly) + + first_pixel = ImageCoordinate([sicd.image_data.first_col, sicd.image_data.first_row]) + + image_plane = SARImageCoordConverter( + scp_pixel=scp_pixel, + scp_ecf=scp_ecf, + u_row=xyztype_to_ndarray(sicd.grid.row.uvect_ecf), + u_col=xyztype_to_ndarray(sicd.grid.col.uvect_ecf), + row_ss=sicd.grid.row.ss, + col_ss=sicd.grid.col.ss, + first_pixel=first_pixel, + ) + + projection_set = INCAProjectionSet( + r_ca_scp=sicd.rma.inca.r_ca_scp, + inca_time_coa_poly=poly1d_to_native(sicd.rma.inca.time_capoly), + drate_sf_poly=poly2d_to_native(sicd.rma.inca.drate_sfpoly), + coa_time_poly=time_coa_poly, + arp_poly=arp_poly, + ) + + sicd_sensor_model = SICDSensorModel( + coord_converter=image_plane, + coa_projection_set=projection_set, + scp_arp=xyztype_to_ndarray(sicd.scpcoa.arppos), + scp_varp=xyztype_to_ndarray(sicd.scpcoa.arpvel), + side_of_track=str(sicd.scpcoa.side_of_track.value), + ) + + geodetic_world_coordinate = sicd_sensor_model.image_to_world( + ImageCoordinate([sicd.image_data.scppixel.col, sicd.image_data.scppixel.row]) + ) + ecf_world_coordinate = geodetic_to_geocentric(geodetic_world_coordinate) + + assert np.allclose(ecf_world_coordinate.coordinate, scp_ecf.coordinate) + + geo_scp_world_coordinate = GeodeticWorldCoordinate( + [radians(sicd.geo_data.scp.llh.lon), radians(sicd.geo_data.scp.llh.lat), sicd.geo_data.scp.llh.hae] + ) + + assert np.allclose(geo_scp_world_coordinate.coordinate, geodetic_world_coordinate.coordinate) + + calculated_image_scp = sicd_sensor_model.world_to_image(geo_scp_world_coordinate) + + assert np.allclose(calculated_image_scp.coordinate, scp_pixel.coordinate) + + def test_rgazim_pfa(self): + sicd: sicd121.SICD = XmlParser().from_path(Path("./test/data/sicd/example.sicd121.pfa.xml")) + + scp_ecf = WorldCoordinate(xyztype_to_ndarray(sicd.geo_data.scp.ecf)) + scp_pixel = ImageCoordinate([sicd.image_data.scppixel.col, sicd.image_data.scppixel.row]) + + polar_ang_poly = poly1d_to_native(sicd.pfa.polar_ang_poly) + spatial_freq_sf_poly = poly1d_to_native(sicd.pfa.spatial_freq_sfpoly) + time_coa_poly = poly2d_to_native(sicd.grid.time_coapoly) + arp_poly = xyzpoly_to_native(sicd.position.arppoly) + + first_pixel = ImageCoordinate([sicd.image_data.first_col, sicd.image_data.first_row]) + + image_plane = SARImageCoordConverter( + scp_pixel=scp_pixel, + scp_ecf=scp_ecf, + u_row=xyztype_to_ndarray(sicd.grid.row.uvect_ecf), + u_col=xyztype_to_ndarray(sicd.grid.col.uvect_ecf), + row_ss=sicd.grid.row.ss, + col_ss=sicd.grid.col.ss, + first_pixel=first_pixel, + ) + + projection_set = PFAProjectionSet( + scp_ecf=scp_ecf, + polar_ang_poly=polar_ang_poly, + spatial_freq_sf_poly=spatial_freq_sf_poly, + coa_time_poly=time_coa_poly, + arp_poly=arp_poly, + ) + + # FPN is the default ground plane normal for a PFA projection otherwise we calculate it as a normal + # from WGS84 ellipsoid + ugpn = xyztype_to_ndarray(sicd.pfa.fpn) + + sicd_sensor_model = SICDSensorModel( + coord_converter=image_plane, + coa_projection_set=projection_set, + scp_arp=xyztype_to_ndarray(sicd.scpcoa.arppos), + scp_varp=xyztype_to_ndarray(sicd.scpcoa.arpvel), + side_of_track=str(sicd.scpcoa.side_of_track.value), + u_gpn=ugpn, + ) + + geodetic_world_coordinate = sicd_sensor_model.image_to_world( + ImageCoordinate([sicd.image_data.scppixel.col, sicd.image_data.scppixel.row]) + ) + ecf_world_coordinate = geodetic_to_geocentric(geodetic_world_coordinate) + + assert np.allclose(ecf_world_coordinate.coordinate, scp_ecf.coordinate) + + geo_scp_world_coordinate = GeodeticWorldCoordinate( + [radians(sicd.geo_data.scp.llh.lon), radians(sicd.geo_data.scp.llh.lat), sicd.geo_data.scp.llh.hae] + ) + + assert np.allclose(geo_scp_world_coordinate.coordinate, geodetic_world_coordinate.coordinate) + + calculated_image_scp = sicd_sensor_model.world_to_image(geo_scp_world_coordinate) + + assert np.allclose(calculated_image_scp.coordinate, scp_pixel.coordinate) diff --git a/test/data/sicd/capella-sicd121-chip1.ntf b/test/data/sicd/capella-sicd121-chip1.ntf new file mode 100644 index 0000000..1516370 --- /dev/null +++ b/test/data/sicd/capella-sicd121-chip1.ntf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f11dbe73eff3a8b55c50a523d634a63eb98966fa0d4cf32a33b7339f725b59f0 +size 1094244 diff --git a/test/data/sicd/capella-sicd121-chip2.ntf b/test/data/sicd/capella-sicd121-chip2.ntf new file mode 100644 index 0000000..478f79a --- /dev/null +++ b/test/data/sicd/capella-sicd121-chip2.ntf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9c6950dad897c1ff4c99e1a1256b99c4b18261e20c43fccca03ac75c17e8b6bf +size 1064611 diff --git a/test/data/sicd/example.sicd121.capella.xml b/test/data/sicd/example.sicd121.capella.xml new file mode 100644 index 0000000..fd24b36 --- /dev/null +++ b/test/data/sicd/example.sicd121.capella.xml @@ -0,0 +1,402 @@ + + + capella-2 + 15JAN21capella-2173921 + MONOSTATIC + + STRIPMAP + + UNCLASSIFIED + + + Capella SAR Processor (2.5.3, 37a73cc02e5a357756980ce065e2b53adeae4e9d) + 2021-09-01T22:48:40.000000Z + Unknown + sarpy 1.2.5 + + + RE16I_IM16I + 5388 + 19083 + 0 + 0 + + 5388 + 19083 + + + 2694 + 9541 + + + + 0 + 0 + + + 0 + 19083 + + + 5388 + 19083 + + + 5388 + 0 + + + 0 + 0 + + + + + WGS_84 + + + 5271232.528561848 + -703918.7036014228 + 3509547.755004264 + + + 33.59934615859317 + -7.606259320191953 + 54.63396231038757 + + + + + 33.57557419233318 + -7.715737959893586 + + + 33.66247968391572 + -7.521776845384764 + + + 33.62304102343928 + -7.496736772717166 + + + 33.53576476994573 + -7.690344834684546 + + + + + 33.57557419233318 + -7.715737959893586 + + + 33.66247968391572 + -7.521776845384764 + + + 33.62304102343928 + -7.496736772717166 + + + 33.53576476994573 + -7.690344834684546 + + + 33.57557419233318 + -7.715737959893586 + + + + + SLANT + RGZERO + + 2.173685700333333 + 0.000150937408161923 + + + + -0.2313169281599637 + 0.3556237170721815 + -0.9055519038699015 + + 0.6245676208333334 + 0.9511988838080884 + -1 + 1.334256380792608 + 64.37786951931926 + -0.667128190396304 + 0.667128190396304 + + 0 + + + AVCI-NACAROGLU-CAPELLA + 1.25 + + + 0.01970287298661711 + 0.07726950936010718 + 0.1318824131826273 + 0.1944252372795882 + 0.2646014515889159 + 0.3412051275652817 + 0.4225343249454487 + 0.5065331681561823 + 0.590894960579544 + 0.6731625984868158 + 0.7508323427242285 + 0.8214596188067258 + 0.8827631847744818 + 0.9327232538826931 + 0.9696690963016896 + 0.9923519872809892 + 1 + 0.9923519872809892 + 0.9696690963016896 + 0.9327232538826931 + 0.8827631847744818 + 0.8214596188067258 + 0.7508323427242285 + 0.6731625984868158 + 0.590894960579544 + 0.5065331681561823 + 0.4225343249454487 + 0.3412051275652817 + 0.2646014515889159 + 0.1944252372795882 + 0.1318824131826273 + 0.07726950936010718 + + + + + -0.1421942494206885 + 0.9084236131422561 + 0.3931250876213012 + + 1.069856275523818 + 1.122553243782685 + -1 + 0.872855015564871 + 0 + -0.4394266826531045 + 0.4334283329117665 + + -0.002999174870669003 + + + ANTENNA-TAPER-CAPELLA + 0.01205268875689288 + + + 0.5038276985319642 + 0.5453410700968516 + 0.5893418917596951 + 0.6347317532132404 + 0.6805044117717426 + 0.7257502175755721 + 0.7696333109610092 + 0.8113906511990557 + 0.8503308500159068 + 0.8857978645240657 + 0.9171536249574779 + 0.9440325772587769 + 0.9660381523818794 + 0.9828498329217592 + 0.9942249153188277 + 0.9999999853762968 + 1 + 0.9942249589755801 + 0.9828499049796148 + 0.966038251797382 + 0.9440327025834828 + 0.917153774346285 + 0.8857980357445312 + 0.8503310404079479 + 0.8113908577480362 + 0.7696335302970161 + 0.7257504459595931 + 0.6805046450904531 + 0.6347319869708252 + 0.5893421210698754 + 0.5453412895181906 + 0.5038279024619354 + + + + + 2021-01-15T17:39:21.684235Z + 4.356605759000001 + + + 0 + 4.356605759000001 + 0 + 26979 + + 0.0 + 6192.6662318038825 + + + + + + + + 5438120.473202774 + -997.4095925011363 + -2.788989728553322 + 0.00023730335867582828 + 2.3545281075582228e-07 + -1.492656957489703e-11 + -3.0570170411356174e-14 + + + -971609.8647421665 + 6583.684605741273 + 0.6568128008751011 + -0.0011846374886725296 + -7.185209123184431e-08 + 5.783598485367062e-11 + 9.744111832411611e-14 + + + 4148423.8430116437 + 2855.80663274287 + -2.5117319050417883 + -0.0005721514871533472 + 2.545856535909912e-07 + 3.20193106149182e-11 + -1.1676099341312116e-14 + + + + + + 9549999872 + 9749999872 + + + + 1.96E-05 + 200000000 + 9549999872 + 10204081632653.06 + CHIRP + 750000000 + 0 + + + H + + + H:H + + + + + + 1 + 1 + 1 + + H:H + 0 + 4.356605759000001 + + 9549999872 + 9749999872 + + OTHER + NO + NO + NO + NO + + Backprojected to DEM + true + + + + 2.173685700333333 + + 5435939.242952521 + -957295.9124464868 + 4154619.595475576 + + + -1009.530993301936 + 6586.523220067197 + 2844.879101663144 + + + -5.574871172770072 + 1.298171362344716 + -5.030911433820477 + + R + 712352.4346112193 + 449850.7034007939 + 89.99999995062271 + 46.9889303602956 + 43.0110696397044 + -0.1700935896001955 + 46.98916589027822 + 331.8370896248286 + 332.0697045793157 + + + + ABSOLUTE + + 39.24201011267091 + 2.27866933042562E-05 + 3.471658456505701E-07 + -6.629346283575849E-17 + + + + 2.598451147663572E-06 + -1.942915018981167E-24 + 1.368407884430401E-27 + 1.378877395375437E-30 + + + 1.499571638005783E-06 + 9.009699530034262E-13 + -8.811557887570633E-19 + 2.225021054564836E-23 + + + 2.203222548581317E-06 + -2.320064743720347E-26 + 9.951539490477308E-28 + 1.305902461190002E-32 + + + 2.046831876543034E-06 + 2.291158712901786E-12 + -4.58441401396291E-19 + 5.497848200436365E-23 + + + + RG_DOP + INCA + + + 2.1736857003333334 + 0.000150937408161923 + + 712352.4341635579 + 9649999872 + + 0.9144184293995976 + + + -19.8703217922726 + + true + + + diff --git a/test/data/sicd/example.sicd121.pfa.xml b/test/data/sicd/example.sicd121.pfa.xml new file mode 100644 index 0000000..fd50ac6 --- /dev/null +++ b/test/data/sicd/example.sicd121.pfa.xml @@ -0,0 +1,1156 @@ + + + Synthetic + SyntheticCore + MONOSTATIC + + SPOTLIGHT + + UNCLASSIFIED + + + Valkyrie Systems Sage | sar_common_kit 1.9.0.0 + 2022-12-05T18:43:30.208726Z + + + RE32F_IM32F + 1494 + 1723 + 0 + 0 + + 1494 + 1723 + + + 747 + 861 + + + + 256 + 343 + + + 256 + 484 + + + 256 + 624 + + + 256 + 765 + + + 256 + 905 + + + 256 + 1046 + + + 256 + 1186 + + + 256 + 1327 + + + 256 + 1468 + + + 379 + 1456 + + + 502 + 1445 + + + 624 + 1434 + + + 747 + 1423 + + + 870 + 1412 + + + 992 + 1401 + + + 1115 + 1390 + + + 1238 + 1378 + + + 1238 + 1238 + + + 1238 + 1097 + + + 1238 + 957 + + + 1238 + 817 + + + 1238 + 676 + + + 1238 + 536 + + + 1238 + 395 + + + 1238 + 255 + + + 1115 + 266 + + + 992 + 277 + + + 870 + 288 + + + 747 + 299 + + + 624 + 310 + + + 502 + 321 + + + 379 + 332 + + + + + WGS_84 + + + 6378137 + 0 + 0 + + + 0 + 0 + 0 + + + + + 0.0080807974971151136 + -0.0061258326901634519 + + + 0.0056733384282420182 + 0.0074368679872295704 + + + -0.008071597406306948 + 0.0061267096368526897 + + + -0.0056641383373949505 + -0.0074359910405444127 + + + + + 0.005238360895824838 + -0.0036433852169764111 + + + 0.0050420582635510016 + -0.0025375503997010318 + + + 0.004845755628902701 + -0.0014317155805202205 + + + 0.0046494529921086835 + -0.00032588076026081428 + + + 0.0044531503533953975 + 0.00077995406025051814 + + + 0.0042568477129873231 + 0.0018857888801836113 + + + 0.004060545071107439 + 0.0029916236987124986 + + + 0.0038642424279800454 + 0.0040974585150061532 + + + 0.003667939783831225 + 0.0052032933282371652 + + + 0.0025546522020080289 + 0.0050083048147242385 + + + 0.0014413646175311253 + 0.0048133163010931164 + + + 0.00032807703126864579 + 0.0046183277873471356 + + + -0.00078521055587245359 + 0.0044233392734964747 + + + -0.0018984981430081194 + 0.0042283507595426628 + + + -0.003011785729254395 + 0.0040333622454942607 + + + -0.0041250733137164937 + 0.0038383737313532821 + + + -0.0052383608955085424 + 0.003643385217128532 + + + -0.0050420582632763646 + 0.0025375503997804791 + + + -0.0048457556287156058 + 0.0014317155805582487 + + + -0.0046494529920602899 + 0.00032588076028108935 + + + -0.0044531503535271385 + -0.00077995406023062997 + + + -0.0042568477133461038 + -0.0018857888801565275 + + + -0.0040605450717476526 + -0.0029916236986777789 + + + -0.0038642424289528294 + -0.004097458514977414 + + + -0.0036679397851883686 + -0.0052032933282338163 + + + -0.0025546522030494749 + -0.0050083048147000261 + + + -0.0014413646182779216 + -0.0048133163010521838 + + + -0.00032807703177041223 + -0.0046183277872955016 + + + 0.00078521055559128701 + -0.0044233392734311441 + + + 0.0018984981429170419 + -0.0042283507594636036 + + + 0.003011785729324451 + -0.0040333622453953642 + + + 0.0041250733139214331 + -0.0038383737312319729 + + + + + [6378137.0, -405.5797876726389, 579.2279653395692] + + + [6378137.0, 86.82408883346518, 492.403876506104] + + + [6378137.0, 579.2279653395692, 405.5797876726388] + + + [6378137.0, -492.40387650610404, 86.8240888334652] + + + [6378137.0, 0.0, 0.0] + + + [6378137.0, 492.40387650610404, -86.8240888334652] + + + [6378137.0, -579.2279653395692, -405.5797876726388] + + + [6378137.0, -86.82408883346518, -492.403876506104] + + + [6378137.0, 405.5797876726389, -579.2279653395692] + + + + + SLANT + RGAZIM + + 1.6800674762530383 + + + + -0.50000122375786304 + -0.15037583977714006 + -0.8528692064148875 + + 0.88229809656554448 + 0.99747529707162585 + -1 + 0.88798408351600244 + 66.712157247222834 + -0.44399204175799412 + 0.44399204175800833 + + -0 + + + + + -0.13518643844872713 + 0.98628938466763816 + -0.094646059985916131 + + 0.8788669876603048 + 0.99666461341322399 + -1 + 0.88870636679539183 + 1.3449549529642717e-08 + -0.44435318339769592 + 0.44435318339769592 + + -0 + + + + + 2022-12-05T18:41:24.051402Z + 3.4668291025146964 + + + 0.0056816659534297907 + 1.734252544716481 + 0 + 1040 + + -3.4200358124461596 + 601.94243049797956 + -2.5957560931224679e-05 + -2.0559365748016132e-08 + 8.5465035653447778e-11 + 1.5613913034987107e-13 + + + + 1.734252544716481 + 3.4628234234795321 + 1041 + 2080 + + -3.4200358124461596 + 601.94243049797956 + -2.5957560931224679e-05 + -2.0559365748016132e-08 + 8.5465035653447778e-11 + 1.5613913034987107e-13 + + + + + + + + 7228127.9124448663 + 352.53242998756502 + -3.5891719134975157 + -5.7694198643316104e-05 + 2.7699968593303768e-07 + 2.1592636134572539e-09 + + + 268129.91744542622 + -7332.3823879634392 + -0.13313219332893028 + 0.0012135963117010783 + 1.0196690368353028e-08 + 1.3607911396273635e-11 + + + 1451527.4824241539 + -401.08990372640221 + -0.72076439428225647 + 6.6511087447480695e-05 + 5.6690990559856781e-08 + 3.2123861103114586e-10 + + + + + 6378136.9999999972 + -2.7603991862673482e-10 + -8.8046691538817019e-10 + 3.6552842678534889e-10 + -4.5501609492503518e-13 + + + 0 + 0 + 0 + 0 + 0 + + + 0 + 0 + 0 + 0 + 0 + + + + + 7228127.9122881154 + 352.53243031729494 + -3.5891718145261389 + -5.7760778794926364e-05 + 2.961407356089768e-07 + 1.5152746784999351e-10 + + + 268129.91754994984 + -7332.3823894408897 + -0.13313219336015877 + 0.0012135939299440443 + 1.0894413139775905e-08 + -6.1574812416329165e-11 + + + 1451527.4820069293 + -401.0899032701904 + -0.72076438617145899 + 6.6502336106517436e-05 + 5.9235211780521735e-08 + 4.8536679801755301e-11 + + + + + + 7228127.9122881237 + 352.53243031084878 + -3.5891718013492584 + -5.7769234785477141e-05 + 2.985788077956373e-07 + -1.4884336849092922e-10 + + + 268129.91754994984 + -7332.3823894398674 + -0.13313219479068839 + 0.0012135948482529309 + 1.0630300780216877e-08 + -3.4535962553841784e-11 + + + 1451527.4820069303 + -401.08990326673984 + -0.72076439093449318 + 6.6505493849440306e-05 + 5.8315612455559405e-08 + 1.3835573401835279e-10 + + + + + + + 9933296178.0950012 + 10066703821.904999 + + + + + V + + + V:V + 1 + + + + + + 0.0052383608956667301 + -0.0036433852170520572 + 0.039373653940856457 + + + 0.0036679397845088379 + 0.0052032933282355155 + 0.039283305406570435 + + + -0.0052383608956667301 + 0.0036433852170520572 + 0.039373653940856457 + + + -0.0036679397845088379 + -0.0052032933282355155 + 0.039283305406570435 + + + + + + 6378137 + 0 + 0 + + 650 + 750 + + + + 0 + -0.17364817766693033 + -0.98480775301220813 + + 0.76980043496519579 + 1301 + 0 + + + + 0 + 0.98480775301220813 + -0.17364817766693033 + + 0.66641108974957253 + 1501 + 0 + + + + + + + 1 + 1 + + V:V + 0.0056816659534327432 + 3.4611621345438324 + + 9933296178.0950298 + 10066703821.905016 + + PFA + NO + NO + NO + NO + + inscription + true + fixed + fixed + + + polar_deterministic_phase + true + true + none + none + one_dimensional + + + + 1.6800674762530383 + + 7228710.0595508879 + 255810.65024467336 + 1450851.5901888732 + + + 340.47184478328006 + -7332.8194533174392 + -403.5112050640754 + + + -7.1789158206813477 + -0.25403049783428427 + -1.4408563791978921 + + L + 1701141.9562064605 + 1282320.3392587577 + 80.000333057346595 + 30.000080950049 + 59.999919049951004 + 8.9805970546123763 + 31.195125856239255 + 9.9994779614198173 + 352.45909403041333 + + + + ABSOLUTE + + -47.698407849729996 + + + + 234.567891 + 0.0123456789 + 3.45678912e-05 + 1.23456789e-09 + 2.34567891e-12 + 1.23456789e-16 + 1.23456789e-19 + -0.023456789 + -5.67891234e-06 + -4.56789123e-09 + -8.91234567e-13 + -4.56789123e-16 + -9.12345678e-20 + -3.45678912e-23 + 5.67891234e-05 + 3.45678912e-09 + 7.89123456e-12 + 4.56789123e-16 + 5.67891234e-19 + 6.78912345e-23 + 5.67891234e-26 + -6.78912345e-09 + -1.23456789e-12 + -1.23456789e-15 + -7.89123456e-20 + -6.78912345e-23 + -5.67891234e-26 + -1.23456789e-29 + 7.89123456e-12 + 5.67891234e-16 + 1.23456789e-18 + 1.23456789e-22 + 1.23456789e-25 + 1.23456789e-29 + 6.78912345e-33 + -1.23456789e-15 + -1.23456789e-19 + -1.23456789e-22 + -8.91234567e-26 + -4.56789123e-29 + 9.12345678e-33 + 2.34567891e-36 + + + + + + + 0.13982986262005959 + -0.0027594341203054122 + -2.642141233211328e-06 + 1.4412928975132546e-08 + 4.9106827976192821e-11 + -1.1406011989724555e-13 + + + -0.985103968766331 + -0.00072022999981332595 + 8.6929909937280073e-06 + 1.7419413461433799e-08 + -7.0195266020811239e-11 + -3.1383419971071391e-13 + + + 0.10008886172036295 + -0.0032336279155612555 + -3.6150726160461875e-06 + 2.4227194540266762e-08 + 6.8535490141060762e-11 + -1.9455706113121084e-13 + + + + + -0.85523535607199153 + -0.00010473716447760117 + 1.0612386361416289e-06 + 1.2191988789775561e-10 + -6.5342561715559025e-13 + 3.2519400278010645e-16 + + + -0.06921308838843146 + 0.00073787526119144643 + 7.399077245060788e-08 + -1.0525875572605681e-09 + -3.3788216228145983e-14 + 7.6241787500235205e-16 + + + 0.51359715158881925 + -7.4969848184029868e-05 + 1.2309351838082699e-06 + 3.5096979204331667e-10 + -2.0820927565561895e-12 + -5.4913780628865013e-16 + + + 10000000000 + + + 0 + + + 0 + + + + + 0 + -4.1430777012935061e-12 + -25810177.686242383 + -0.00014382609695309318 + -13687592749336.885 + 292.19947867443852 + -4.2115648774405212e+18 + -1442527537.8580344 + -4.4559066869820204e+25 + -4.1430777012935061e-12 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + -25810177.686242383 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + -0.00014382609695309318 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + -13687592749336.885 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 292.19947867443852 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + -4.2115648774405212e+18 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + -1442527537.8580344 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + -4.4559066869820204e+25 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + + + 0 + + + + + 0 + + + 0 + + + + 0 + + false + + + + + 0.13982986262005959 + -0.0027594341203054122 + -2.642141233211328e-06 + 1.4412928975132546e-08 + 4.9106827976192821e-11 + -1.1406011989724555e-13 + + + -0.985103968766331 + -0.00072022999981332595 + 8.6929909937280073e-06 + 1.7419413461433799e-08 + -7.0195266020811239e-11 + -3.1383419971071391e-13 + + + 0.10008886172036295 + -0.0032336279155612555 + -3.6150726160461875e-06 + 2.4227194540266762e-08 + 6.8535490141060762e-11 + -1.9455706113121084e-13 + + + + + -0.85523535607199153 + -0.00010473716447760117 + 1.0612386361416289e-06 + 1.2191988789775561e-10 + -6.5342561715559025e-13 + 3.2519400278010645e-16 + + + -0.06921308838843146 + 0.00073787526119144643 + 7.399077245060788e-08 + -1.0525875572605681e-09 + -3.3788216228145983e-14 + 7.6241787500235205e-16 + + + 0.51359715158881925 + -7.4969848184029868e-05 + 1.2309351838082699e-06 + 3.5096979204331667e-10 + -2.0820927565561895e-12 + -5.4913780628865013e-16 + + + 10000000000 + + + 0 + + + 0 + + + + + 0 + -4.1430777012935061e-12 + -25810177.686242383 + -0.00014382609695309318 + -13687592749336.885 + 292.19947867443852 + -4.2115648774405212e+18 + -1442527537.8580344 + -4.4559066869820204e+25 + -4.1430777012935061e-12 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + -25810177.686242383 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + -0.00014382609695309318 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + -13687592749336.885 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 292.19947867443852 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + -4.2115648774405212e+18 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + -1442527537.8580344 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + -4.4559066869820204e+25 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + + + 0 + + + + + 0 + + + 0 + + + + 0 + + false + + + + + 0.13982986262005959 + -0.0027594341203054122 + -2.642141233211328e-06 + + + -0.985103968766331 + -0.00072022999981332595 + 8.6929909937280073e-06 + + + 0.10008886172036295 + -0.0032336279155612555 + -3.6150726160461875e-06 + + + + + -0.85523535607199153 + -0.00010473716447760117 + 1.0612386361416289e-06 + + + -0.06921308838843146 + 0.00073787526119144643 + 7.399077245060788e-08 + + + 0.51359715158881925 + -7.4969848184029868e-05 + 1.2309351838082699e-06 + + + 10000000000 + + + 0 + + + 0 + + + + + 0 + -4.1430777012935061e-12 + -25810177.686242383 + -4.1430777012935061e-12 + 0 + 0 + -25810177.686242383 + 0 + 0 + + + 0 + + + + + 0 + + + 0 + + + + 0 + + false + + + + + 1 + 0 + 0 + + + 0.85540832579135895 + 0.067973204303252321 + -0.51347467325894713 + + 1.6800674762530463 + + -0.0071413060968959627 + 0.004245118151048095 + 3.303261280436797e-06 + -2.152475729568344e-08 + -5.5340201006749593e-11 + 1.6174039129702326e-13 + 1.2038148814517427e-15 + -1.0008410326624715e-16 + 1.6819824323114349e-17 + -1.1931290667377136e-18 + + + 0.99999143699142279 + 2.1389170906735031e-05 + 0.051388964320644458 + 0.0048322065140747042 + -0.020014437732713723 + 0.0023716850734943114 + 0.14153200650672734 + 4.5834686287659396 + -1395.0901983623608 + + 66.268165205464854 + 67.156149288980856 + -0.44524655663963569 + 0.44524655663963569 + + diff --git a/test/data/sicd/example.sicd121.rma.xml b/test/data/sicd/example.sicd121.rma.xml new file mode 100644 index 0000000..2786650 --- /dev/null +++ b/test/data/sicd/example.sicd121.rma.xml @@ -0,0 +1,1577 @@ + + + Synthetic + SyntheticCore + MONOSTATIC + + SPOTLIGHT + + UNCLASSIFIED + + + Valkyrie Systems Sage | sar_common_kit 1.10.0.0 + 2023-03-15T18:45:25.264560Z + + + RE32F_IM32F + 1491 + 1773 + 0 + 0 + + 1491 + 1773 + + + 745 + 886 + + + + 251 + 344 + + + 251 + 368 + + + 251 + 392 + + + 251 + 416 + + + 251 + 440 + + + 251 + 464 + + + 251 + 488 + + + 251 + 512 + + + 251 + 536 + + + 251 + 560 + + + 251 + 584 + + + 251 + 608 + + + 251 + 632 + + + 251 + 656 + + + 251 + 680 + + + 251 + 704 + + + 251 + 728 + + + 251 + 752 + + + 251 + 776 + + + 251 + 800 + + + 251 + 824 + + + 251 + 848 + + + 251 + 872 + + + 251 + 896 + + + 251 + 921 + + + 251 + 945 + + + 251 + 969 + + + 251 + 993 + + + 251 + 1017 + + + 251 + 1041 + + + 251 + 1065 + + + 251 + 1089 + + + 251 + 1113 + + + 251 + 1137 + + + 251 + 1161 + + + 251 + 1185 + + + 251 + 1209 + + + 251 + 1233 + + + 251 + 1257 + + + 251 + 1281 + + + 251 + 1305 + + + 251 + 1329 + + + 251 + 1353 + + + 251 + 1377 + + + 251 + 1401 + + + 251 + 1425 + + + 251 + 1449 + + + 251 + 1473 + + + 251 + 1498 + + + 251 + 1522 + + + 1239 + 1428 + + + 1239 + 250 + + + 1219 + 252 + + + 1199 + 254 + + + 1179 + 256 + + + 1158 + 258 + + + 1138 + 260 + + + 1118 + 262 + + + 1098 + 264 + + + 1078 + 266 + + + 1058 + 268 + + + 1037 + 269 + + + 1017 + 271 + + + 997 + 273 + + + 977 + 275 + + + 957 + 277 + + + 937 + 279 + + + 916 + 281 + + + 896 + 283 + + + 876 + 285 + + + 856 + 287 + + + 836 + 288 + + + 816 + 290 + + + 795 + 292 + + + 775 + 294 + + + 755 + 296 + + + 735 + 298 + + + 715 + 300 + + + 695 + 302 + + + 674 + 304 + + + 654 + 306 + + + 634 + 307 + + + 614 + 309 + + + 594 + 311 + + + 574 + 313 + + + 553 + 315 + + + 533 + 317 + + + 513 + 319 + + + 493 + 321 + + + 473 + 323 + + + 453 + 325 + + + 432 + 326 + + + 412 + 328 + + + 392 + 330 + + + 372 + 332 + + + 352 + 334 + + + 332 + 336 + + + 311 + 338 + + + 291 + 340 + + + 271 + 342 + + + + + WGS_84 + + + 6378137.0 + 0.0 + 0.0 + + + 0.0 + 0.0 + 0.0 + + + + + 0.00799513783337124 + -0.006008128184945283 + + + 0.005631615545468088 + 0.007307024777172993 + + + -0.00799513783337124 + 0.006008128184945283 + + + -0.005631615545468088 + -0.007307024777172993 + + + + + 0.00523836630846847 + -0.00364338148084923 + + + 0.005206316846277236 + -0.0034628370576756814 + + + 0.005174267384009941 + -0.0032822926344316466 + + + 0.005142217921672595 + -0.0031017482111219876 + + + 0.005110168459274313 + -0.002921203787750631 + + + 0.005078118996809532 + -0.002740659364320905 + + + 0.005046069534275856 + -0.0025601149408357384 + + + 0.005014020071677403 + -0.0023795705173000345 + + + 0.0049819706090178245 + -0.002199026093716339 + + + 0.004949921146299129 + -0.0020184816700884903 + + + 0.004917871683515614 + -0.00183793724642093 + + + 0.004885822220679328 + -0.0016573928227165578 + + + 0.004853772757772994 + -0.001476848398978006 + + + 0.004821723294815218 + -0.0012963039752099965 + + + 0.00478967383180142 + -0.0011157595514177244 + + + 0.004757624368719913 + -0.0009352151276003231 + + + 0.004725574905592831 + -0.0007546707037649141 + + + 0.0046935254424053975 + -0.0005741262799149227 + + + 0.004661475979172907 + -0.0003935818560524597 + + + 0.004629426515879852 + -0.00021303743218295168 + + + 0.004597377052536041 + -3.249300830870516e-05 + + + 0.004565327589146371 + 0.00014805141556674793 + + + 0.004533278125709861 + 0.00032859583943927877 + + + 0.004501228662217433 + 0.0005091402633053358 + + + 0.004469179198680784 + 0.0006896846871612296 + + + 0.004437129735094944 + 0.0008702291110032007 + + + 0.004405080271465758 + 0.00105077353482891 + + + 0.004373030807795506 + 0.0012313179586336562 + + + 0.00434098134408188 + 0.001411862382412968 + + + 0.004308931880317015 + 0.001592406806165302 + + + 0.004276882416512852 + 0.0017729512298854911 + + + 0.004244832952669874 + 0.0019534956535704378 + + + 0.004212783488787099 + 0.002134040077217569 + + + 0.004180734024872398 + 0.0023145845008214583 + + + 0.0041486845609137275 + 0.0024951289243789047 + + + 0.004116635096919947 + 0.002675673347886881 + + + 0.004084585632881881 + 0.0028562177713416415 + + + 0.004052536168819116 + 0.003036762194739138 + + + 0.0040204867047156576 + 0.0032173066180771126 + + + 0.003988437240590928 + 0.0033978510413510614 + + + 0.003956387776418934 + 0.0035783954645569166 + + + 0.003924338312221998 + 0.0037589398876915793 + + + 0.0038922888479996152 + 0.003939484310750592 + + + 0.003860239383746406 + 0.0041200287337321805 + + + 0.003828189919460814 + 0.004300573156630903 + + + 0.0037961404551599127 + 0.004481117579442749 + + + 0.00376409099081454 + 0.004661662002167499 + + + 0.0037320415264581962 + 0.0048422064247973 + + + 0.003699992062071886 + 0.005022750847332307 + + + 0.0036679425976707007 + 0.005203295269766109 + + + -0.005238366306873601 + 0.003643381480220648 + + + -0.0036679425961185777 + -0.005203295270113747 + + + -0.003486181190709507 + -0.005171460294868825 + + + -0.003304419785209955 + -0.005139625319622343 + + + -0.0031226583796152003 + -0.005107790344374426 + + + -0.002940896973933543 + -0.005075955369123011 + + + -0.002759135568177835 + -0.005044120393870122 + + + -0.0025773741623351127 + -0.005012285418614279 + + + -0.002395612756418211 + -0.004980450443357304 + + + -0.0022138513504335305 + -0.004948615468098705 + + + -0.0020320899443811236 + -0.004916780492837019 + + + -0.0018503285382714203 + -0.004884945517573347 + + + -0.0016685671320883786 + -0.004853110542308724 + + + -0.0014868057258590529 + -0.004821275567040518 + + + -0.0013050443195772694 + -0.004789440591770919 + + + -0.0011232829132420041 + -0.004757605616500143 + + + -0.0009415215068684879 + -0.004725770641225615 + + + -0.000759760100461352 + -0.004693935665949827 + + + -0.0005779986940034719 + -0.004662100690672747 + + + -0.00039623728752586734 + -0.004630265715393136 + + + -0.00021447588101285652 + -0.0045984307401120954 + + + -3.271447447357881e-05 + -0.004566595764828223 + + + 0.0001490469320847932 + -0.004534760789544004 + + + 0.00033080833866069544 + -0.004502925814256218 + + + 0.0005125697452407668 + -0.004471090838967827 + + + 0.0006943311518425009 + -0.004439255863676882 + + + 0.0008760925584408571 + -0.004407420888384009 + + + 0.001057853965047325 + -0.004375585913090328 + + + 0.0012396153716457189 + -0.004343750937793399 + + + 0.0014213767782441417 + -0.00431191596249584 + + + 0.0016031381848308614 + -0.00428008098719557 + + + 0.0017848995914006443 + -0.004248246011893965 + + + 0.001966660997951133 + -0.004216411036590668 + + + 0.00214842240448203 + -0.004184576061286293 + + + 0.0023301838109927755 + -0.0041527410859788895 + + + 0.0025119452174677966 + -0.004120906110671654 + + + 0.0026937066239156805 + -0.004089071135360193 + + + 0.0028754680303170878 + -0.004057236160048518 + + + 0.003057229436682084 + -0.0040254011847345545 + + + 0.0032389908430040214 + -0.003993566209419595 + + + 0.0034207522492769866 + -0.003961731234102589 + + + 0.0036025136554935326 + -0.00392989625878451 + + + 0.003784275061656479 + -0.003898061283464284 + + + 0.003966036467765342 + -0.0038662263081436234 + + + 0.004147797873798566 + -0.003834391332819248 + + + 0.004329559279767888 + -0.0038025563574956515 + + + 0.0045113206856681595 + -0.0037707213821690814 + + + 0.004693082091491189 + -0.003738886406840987 + + + 0.004874843497234658 + -0.003707051431511723 + + + 0.005056604902895187 + -0.0036752164561818976 + + + + + [6378137.0, -405.5797876726389, 579.2279653395692] + + + [6378137.0, 86.82408883346518, 492.403876506104] + + + [6378137.0, 579.2279653395692, 405.5797876726388] + + + [6378137.0, -492.40387650610404, 86.8240888334652] + + + [6378137.0, 0.0, 0.0] + + + [6378137.0, 492.40387650610404, -86.8240888334652] + + + [6378137.0, -579.2279653395692, -405.5797876726388] + + + [6378137.0, -86.82408883346518, -492.403876506104] + + + [6378137.0, 405.5797876726389, -579.2279653395692] + + + + + OTHER + XRGYCR + + 1.6800674834531517 + + + + -0.4999996777623892 + -0.15037599477345914 + -0.8528700852344376 + + 0.8764718936120963 + 0.9952159410112512 + -1 + 0.8899999999999864 + 66.71307055737934 + -0.4452524991440896 + 0.44474750085589676 + + -0.0002524991440964186 + + + + + -0.1351875076070428 + 0.9862892688060626 + -0.0946457389429345 + + 0.8384934792671196 + 0.9883832746537019 + -1 + 0.8961525454893355 + 0.0 + -0.46897366297262205 + 0.46897404568650136 + + 1.333944744637619e-07 + 3.9214832817582035e-05 + + + + + 2022-12-05T18:41:24.051402Z + 3.4668291025146964 + + + 0.0056816659534297907 + 3.4628234234795321 + 0 + 2080 + + -3.4200358124461596 + 601.94243049797956 + -2.5957560931224679e-05 + -2.0559365748016132e-08 + 8.5465035653447778e-11 + 1.5613913034987107e-13 + + + + + + + + 7228127.912172245 + 352.5324304976341 + -3.589171823565171 + -5.775512441827908e-05 + 2.9439757250673563e-07 + 3.347450914408521e-10 + + + 268129.91754565184 + -7332.382389316185 + -0.1331321942213263 + 0.0012135943340884567 + 1.0781763144046816e-08 + -4.9989135258628886e-11 + + + 1451527.4819836628 + -401.0899032323167 + -0.7207643788301279 + 6.649751510936382e-05 + 6.061667359346962e-08 + -1.0048502370615844e-10 + + + + + 6378136.999999997 + -2.760399186267348e-10 + -8.804669153881702e-10 + 3.655284267853489e-10 + -4.550160949250352e-13 + + + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + + + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + + + + + 7228127.912288115 + 352.53243031729494 + -3.589171814526139 + -5.7760778794926364e-05 + 2.961407356089768e-07 + 1.5152746784999351e-10 + + + 268129.91754994984 + -7332.38238944089 + -0.13313219336015877 + 0.0012135939299440443 + 1.0894413139775905e-08 + -6.157481241632917e-11 + + + 1451527.4820069293 + -401.0899032701904 + -0.720764386171459 + 6.650233610651744e-05 + 5.9235211780521735e-08 + 4.85366798017553e-11 + + + + + + 7228127.912288124 + 352.5324303108488 + -3.5891718013492584 + -5.776923478547714e-05 + 2.985788077956373e-07 + -1.4884336849092922e-10 + + + 268129.91754994984 + -7332.382389439867 + -0.13313219479068839 + 0.001213594848252931 + 1.0630300780216877e-08 + -3.4535962553841784e-11 + + + 1451527.4820069303 + -401.08990326673984 + -0.7207643909344932 + 6.65054938494403e-05 + 5.8315612455559405e-08 + 1.383557340183528e-10 + + + + + + + 9933296178.095001 + 10066703821.904999 + + + + + V + + + V:V + 1 + + + + + + 0.00523836089566673 + -0.003643385217052057 + 0.03937365394085646 + + + 0.003667939784508838 + 0.0052032933282355155 + 0.039283305406570435 + + + -0.00523836089566673 + 0.003643385217052057 + 0.03937365394085646 + + + -0.003667939784508838 + -0.0052032933282355155 + 0.039283305406570435 + + + + + + 6378137.0 + 0.0 + 0.0 + + 650.0 + 750.0 + + + + 0.0 + -0.17364817766693033 + -0.9848077530122081 + + 0.7698004349651958 + 1301 + 0 + + + + 0.0 + 0.9848077530122081 + -0.17364817766693033 + + 0.6664110897495725 + 1501 + 0 + + + + + + + 1 + 1 + + V:V + 0.0 + 3.4668291025146964 + + 9933296178.095 + 10066703821.904999 + + RMA + NO + NO + NO + NO + + + 1.6800674834531517 + + 7228710.059281655 + 255810.65028982106 + 1450851.5897463118 + + + 340.47184528526833 + -7332.819454683194 + -403.5112045857062 + + + -7.178915838727428 + -0.25403050571627894 + -1.4408563921345383 + + L + 1701141.9557011856 + 1282320.338942662 + 80.00033305866761 + 30.000080949403603 + 59.9999190505964 + 8.98059705769101 + 31.195125856427378 + 9.999477966137635 + 352.4590940290721 + + + + ABSOLUTE + + 1 + + + + 234.567891 + 0.0123456789 + 3.45678912e-05 + 1.23456789e-09 + 2.34567891e-12 + 1.23456789e-16 + 1.23456789e-19 + -0.023456789 + -5.67891234e-06 + -4.56789123e-09 + -8.91234567e-13 + -4.56789123e-16 + -9.12345678e-20 + -3.45678912e-23 + 5.67891234e-05 + 3.45678912e-09 + 7.89123456e-12 + 4.56789123e-16 + 5.67891234e-19 + 6.78912345e-23 + 5.67891234e-26 + -6.78912345e-09 + -1.23456789e-12 + -1.23456789e-15 + -7.89123456e-20 + -6.78912345e-23 + -5.67891234e-26 + -1.23456789e-29 + 7.89123456e-12 + 5.67891234e-16 + 1.23456789e-18 + 1.23456789e-22 + 1.23456789e-25 + 1.23456789e-29 + 6.78912345e-33 + -1.23456789e-15 + -1.23456789e-19 + -1.23456789e-22 + -8.91234567e-26 + -4.56789123e-29 + 9.12345678e-33 + 2.34567891e-36 + + + + + + + 0.1398298626200596 + -0.002759434120305412 + -2.642141233211328e-06 + 1.4412928975132546e-08 + 4.910682797619282e-11 + -1.1406011989724555e-13 + + + -0.985103968766331 + -0.000720229999813326 + 8.692990993728007e-06 + 1.74194134614338e-08 + -7.019526602081124e-11 + -3.138341997107139e-13 + + + 0.10008886172036295 + -0.0032336279155612555 + -3.6150726160461875e-06 + 2.4227194540266762e-08 + 6.853549014106076e-11 + -1.9455706113121084e-13 + + + + + -0.8552353560719915 + -0.00010473716447760117 + 1.0612386361416289e-06 + 1.219198878977556e-10 + -6.534256171555903e-13 + 3.2519400278010645e-16 + + + -0.06921308838843146 + 0.0007378752611914464 + 7.399077245060788e-08 + -1.0525875572605681e-09 + -3.378821622814598e-14 + 7.62417875002352e-16 + + + 0.5135971515888192 + -7.496984818402987e-05 + 1.23093518380827e-06 + 3.5096979204331667e-10 + -2.0820927565561895e-12 + -5.491378062886501e-16 + + + 10000000000.0 + + + 0.0 + + + 0.0 + + + + + 0.0 + -4.143077701293506e-12 + -25810177.686242383 + -0.00014382609695309318 + -13687592749336.885 + 292.1994786744385 + -4.211564877440521e+18 + -1442527537.8580344 + -4.4559066869820204e+25 + -4.143077701293506e-12 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + -25810177.686242383 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + -0.00014382609695309318 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + -13687592749336.885 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 292.1994786744385 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + -4.211564877440521e+18 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + -1442527537.8580344 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + -4.4559066869820204e+25 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + + + 0.0 + + + + + 0.0 + + + 0.0 + + + + 0.0 + + false + + + + + 0.1398298626200596 + -0.002759434120305412 + -2.642141233211328e-06 + 1.4412928975132546e-08 + 4.910682797619282e-11 + -1.1406011989724555e-13 + + + -0.985103968766331 + -0.000720229999813326 + 8.692990993728007e-06 + 1.74194134614338e-08 + -7.019526602081124e-11 + -3.138341997107139e-13 + + + 0.10008886172036295 + -0.0032336279155612555 + -3.6150726160461875e-06 + 2.4227194540266762e-08 + 6.853549014106076e-11 + -1.9455706113121084e-13 + + + + + -0.8552353560719915 + -0.00010473716447760117 + 1.0612386361416289e-06 + 1.219198878977556e-10 + -6.534256171555903e-13 + 3.2519400278010645e-16 + + + -0.06921308838843146 + 0.0007378752611914464 + 7.399077245060788e-08 + -1.0525875572605681e-09 + -3.378821622814598e-14 + 7.62417875002352e-16 + + + 0.5135971515888192 + -7.496984818402987e-05 + 1.23093518380827e-06 + 3.5096979204331667e-10 + -2.0820927565561895e-12 + -5.491378062886501e-16 + + + 10000000000.0 + + + 0.0 + + + 0.0 + + + + + 0.0 + -4.143077701293506e-12 + -25810177.686242383 + -0.00014382609695309318 + -13687592749336.885 + 292.1994786744385 + -4.211564877440521e+18 + -1442527537.8580344 + -4.4559066869820204e+25 + -4.143077701293506e-12 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + -25810177.686242383 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + -0.00014382609695309318 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + -13687592749336.885 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 292.1994786744385 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + -4.211564877440521e+18 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + -1442527537.8580344 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + -4.4559066869820204e+25 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + + + 0.0 + + + + + 0.0 + + + 0.0 + + + + 0.0 + + false + + + + OMEGA_K + RMCR + + + 7228706.085943103 + 255810.5096335333 + 1450850.7922670336 + + + 340.48515511874143 + -7332.816628850641 + -403.50838193334255 + + 80.00038893713646 + + + diff --git a/test/data/sicd/umbra-sicd121-chip1.ntf b/test/data/sicd/umbra-sicd121-chip1.ntf new file mode 100644 index 0000000..d628474 --- /dev/null +++ b/test/data/sicd/umbra-sicd121-chip1.ntf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:efc1eaa5e44a25a11ba3d963c9344574b35dac0622c3c349cbdbd2a4dadfb984 +size 2136682 From d894b0b3401b7438517e13b3824656e155515406 Mon Sep 17 00:00:00 2001 From: edparris Date: Thu, 7 Sep 2023 12:33:16 -0400 Subject: [PATCH 02/11] feat: add optional dynamic pixel range operations to tile factory (#9) The GDAL tile factory can now automatically rescale pixel values to match a desired output type. This is commonly used when converting panchromatic imagery which often has 11-16 bits per pixel into an 8-bits per pixel representation for visualization. These operations look at the histogram of the input image pixel values and then map them to the output range. --- src/aws/osml/gdal/__init__.py | 3 +- src/aws/osml/gdal/dynamic_range_adjustment.py | 86 +++++++++++++ src/aws/osml/gdal/gdal_utils.py | 120 ++++++++++++------ src/aws/osml/gdal/typing.py | 16 +++ .../image_processing/gdal_tile_factory.py | 21 ++- .../gdal/test_dynamic_range_adjustment.py | 18 +++ .../test_gdal_tile_factory.py | 29 ++++- 7 files changed, 248 insertions(+), 45 deletions(-) create mode 100644 src/aws/osml/gdal/dynamic_range_adjustment.py create mode 100644 test/aws/osml/gdal/test_dynamic_range_adjustment.py diff --git a/src/aws/osml/gdal/__init__.py b/src/aws/osml/gdal/__init__.py index 30adabf..a5c7e08 100644 --- a/src/aws/osml/gdal/__init__.py +++ b/src/aws/osml/gdal/__init__.py @@ -10,7 +10,7 @@ from .gdal_utils import get_image_extension, get_type_and_scales, load_gdal_dataset from .nitf_des_accessor import NITFDESAccessor from .sensor_model_factory import ChippedImageInfoFacade, SensorModelFactory, SensorModelTypes -from .typing import GDALCompressionOptions, GDALImageFormats +from .typing import GDALCompressionOptions, GDALImageFormats, RangeAdjustmentType __all__ = [ "set_gdal_default_configuration", @@ -21,6 +21,7 @@ "GDALConfigEnv", "GDALDigitalElevationModelTileFactory", "GDALImageFormats", + "RangeAdjustmentType", "NITFDESAccessor", "ChippedImageInfoFacade", "SensorModelFactory", diff --git a/src/aws/osml/gdal/dynamic_range_adjustment.py b/src/aws/osml/gdal/dynamic_range_adjustment.py new file mode 100644 index 0000000..97b0602 --- /dev/null +++ b/src/aws/osml/gdal/dynamic_range_adjustment.py @@ -0,0 +1,86 @@ +from typing import List + + +class DRAParameters: + """ + This class manages a set of parameters used to perform a Dynamic Range Adjustment that is applied when + converting imagery pixel values (e.g. 11-bit per pixel panchromatic imagery to an 8-bit per pixel grayscale). + """ + + def __init__( + self, suggested_min_value: float, suggested_max_value: float, actual_min_value: float, actual_max_value: float + ): + """ + Constructor for this class. + + :param suggested_min_value: suggested minimum value of the relevant pixel range + :param suggested_max_value: suggested maximum value of the relevant pixel range + :param actual_min_value: actual minimum value of pixels in the image + :param actual_max_value: actual maximum value of pixels in the image + """ + self.suggested_min_value = suggested_min_value + self.suggested_max_value = suggested_max_value + self.actual_min_value = actual_min_value + self.actual_max_value = actual_max_value + + @staticmethod + def from_counts( + counts: List[float], min_percentage: float = 0.02, max_percentage: float = 0.98, a: float = 0.2, b: float = 0.4 + ) -> "DRAParameters": + """ + This static factory method computes a new set of DRA parameters given a histogram of pixel values. + + :param counts: histogram of the pixel values + :param min_percentage: set point for low intensity pixels that may be outliers + :param max_percentage: set point for high intensity pixels that may be outliers + :param a: weighting factor for the low intensity range + :param b: weighting factor for the high intensity range + :return: a set of DRA parameters containing recommended and actual ranges of values + """ + num_histogram_bins = len(counts) + + # Find the first and last non-zero counts + actual_min_value = 0 + while actual_min_value < num_histogram_bins and counts[actual_min_value] == 0: + actual_min_value += 1 + + actual_max_value = num_histogram_bins - 1 + while actual_max_value > 0 and counts[actual_max_value] == 0: + actual_max_value -= 1 + + # Compute the cumulative distribution + cumulative_counts = counts.copy() + for i in range(1, len(cumulative_counts)): + cumulative_counts[i] = cumulative_counts[i] + cumulative_counts[i - 1] + + # Find the values that exclude the lowest and highest percentages of the counts. + # This identifies the range that contains most of the pixels while excluding outliers. + max_counts = cumulative_counts[-1] + low_threshold = min_percentage * max_counts + e_min = 0 + while cumulative_counts[e_min] < low_threshold: + e_min += 1 + + high_threshold = max_percentage * max_counts + e_max = num_histogram_bins - 1 + while cumulative_counts[e_max] > high_threshold: + e_max -= 1 + + min_value = max([actual_min_value, e_min - a * (e_max - e_min)]) + max_value = min([actual_max_value, e_max + b * (e_max - e_min)]) + + return DRAParameters( + suggested_min_value=min_value, + suggested_max_value=max_value, + actual_min_value=actual_min_value, + actual_max_value=actual_max_value, + ) + + def __repr__(self): + return ( + f"DRAParameters(min_value={self.suggested_min_value}, " + f"max_value={self.suggested_max_value}, " + f"e_first={self.actual_min_value}, " + f"e_last={self.actual_max_value}, " + f")" + ) diff --git a/src/aws/osml/gdal/gdal_utils.py b/src/aws/osml/gdal/gdal_utils.py index 8e86f5c..2633691 100644 --- a/src/aws/osml/gdal/gdal_utils.py +++ b/src/aws/osml/gdal/gdal_utils.py @@ -7,7 +7,9 @@ from aws.osml.photogrammetry import SensorModel +from .dynamic_range_adjustment import DRAParameters from .sensor_model_factory import SensorModelFactory, SensorModelTypes +from .typing import RangeAdjustmentType logger = logging.getLogger(__name__) @@ -65,54 +67,100 @@ def load_gdal_dataset(image_path: str) -> Tuple[gdal.Dataset, Optional[SensorMod return ds, sensor_model -def get_type_and_scales(raster_dataset: gdal.Dataset) -> Tuple[int, List[List[int]]]: +def get_minmax_for_type(gdal_type: int) -> Tuple[float, float]: + """ + This function computes the minimum and maximum values that can be stored in a given GDAL pixel type. + + :param gdal_type: the pixel type + :return: tuple of min, max values + """ + min_value = 0 + max_value = 255 + if gdal_type == gdalconst.GDT_Byte: + min_value = 0 + max_value = 2**8 - 1 + elif gdal_type == gdalconst.GDT_UInt16: + min_value = 0 + max_value = 2**16 - 1 + elif gdal_type == gdalconst.GDT_Int16: + min_value = -(2**15) + max_value = 2**15 - 1 + elif gdal_type == gdalconst.GDT_UInt32: + min_value = 0 + max_value = 2**32 - 1 + elif gdal_type == gdalconst.GDT_Int32: + min_value = -(2**31) + max_value = 2**31 - 1 + elif gdal_type == gdalconst.GDT_UInt64: + min_value = 0 + max_value = 2**64 - 1 + elif gdal_type == gdalconst.GDT_Int64: + min_value = -(2**63) + max_value = 2**63 - 1 + elif gdal_type == gdalconst.GDT_Float32: + min_value = -3.4028235e38 + max_value = 3.4028235e38 + elif gdal_type == gdalconst.GDT_Float64: + min_value = -1.7976931348623157e308 + max_value = 1.7976931348623157e308 + else: + logger.warning("Image uses unsupported GDAL datatype {}. Defaulting to [0,255] range".format(gdal_type)) + + return min_value, max_value + + +def get_type_and_scales( + raster_dataset: gdal.Dataset, + desired_output_type: Optional[int] = None, + range_adjustment: RangeAdjustmentType = RangeAdjustmentType.NONE, +) -> Tuple[int, List[List[int]]]: """ Get type and scales of a provided raster dataset :param raster_dataset: the raster dataset containing the region + :param desired_output_type: type to be output after dynamic range adjustments + :param range_adjustment: the type of pixel scaling effort to :return: a tuple containing type and scales """ scale_params = [] - num_bands = raster_dataset.RasterCount output_type = gdalconst.GDT_Byte - min = 0 - max = 255 + num_bands = raster_dataset.RasterCount for band_num in range(1, num_bands + 1): band = raster_dataset.GetRasterBand(band_num) - output_type = band.DataType - if output_type == gdalconst.GDT_Byte: - min = 0 - max = 2**8 - 1 - elif output_type == gdalconst.GDT_UInt16: - min = 0 - max = 2**16 - 1 - elif output_type == gdalconst.GDT_Int16: - min = -(2**15) - max = 2**15 - 1 - elif output_type == gdalconst.GDT_UInt32: - min = 0 - max = 2**32 - 1 - elif output_type == gdalconst.GDT_Int32: - min = -(2**31) - max = 2**31 - 1 - # TODO: Add these 64-bit cases in once GDAL is upgraded to a version that supports them - # elif output_type == gdalconst.GDT_UInt64: - # min = 0 - # max = 2**64-1 - # elif output_type == gdalconst.GDT_Int64: - # min = -2**63 - # max = 2**63-1 - elif output_type == gdalconst.GDT_Float32: - min = -3.4028235e38 - max = 3.4028235e38 - elif output_type == gdalconst.GDT_Float64: - min = -1.7976931348623157e308 - max = 1.7976931348623157e308 - else: - logger.warning("Image uses unsupported GDAL datatype {}. Defaulting to [0,255] range".format(output_type)) + band_type = band.DataType + min_value, max_value = get_minmax_for_type(band_type) - scale_params.append([min, max, min, max]) + if desired_output_type is None: + output_type = band_type + output_min = min_value + output_max = max_value + else: + output_type = desired_output_type + output_min, output_max = get_minmax_for_type(desired_output_type) + + # If a range adjustment is requested compute the range of source pixel values that will be mapped to the full + # output range. + selected_min = min_value + selected_max = max_value + if range_adjustment is not RangeAdjustmentType.NONE: + num_buckets = int(max_value - min_value) + if band_type == gdalconst.GDT_Float32 or band_type == gdalconst.GDT_Float64: + num_buckets = 255 + dra = DRAParameters.from_counts( + band.GetHistogram(buckets=num_buckets, max=max_value, min=min_value, include_out_of_range=1, approx_ok=1) + ) + if range_adjustment == RangeAdjustmentType.DRA: + selected_min = dra.suggested_min_value + selected_max = dra.suggested_max_value + elif range_adjustment == RangeAdjustmentType.MINMAX: + selected_min = dra.actual_min_value + selected_max = dra.actual_max_value + else: + logger.warning(f"Unknown range adjustment selected {range_adjustment}. Skipping.") + + band_scale_parameters = [selected_min, selected_max, output_min, output_max] + scale_params.append(band_scale_parameters) return output_type, scale_params diff --git a/src/aws/osml/gdal/typing.py b/src/aws/osml/gdal/typing.py index d37882d..ed68333 100644 --- a/src/aws/osml/gdal/typing.py +++ b/src/aws/osml/gdal/typing.py @@ -22,3 +22,19 @@ class GDALImageFormats(str, Enum): JPEG = "JPEG" PNG = "PNG" GTIFF = "GTiff" + + +class RangeAdjustmentType(str, Enum): + """ + Enumeration defining ways to scale raw image pixels to an output value range. + + - NONE indicates that the full range available to the input type will be used. + - MINMAX chooses the portion of the input range that actually contains values. + - DRA is a dynamic range adjustment that attempts to select the most important portion of the input range. + It differs from MINMAX in that it can exclude outliers to reduce the impact of unusually bright/dark + spots in an image. + """ + + NONE = "NONE" + MINMAX = "MINMAX" + DRA = "DRA" diff --git a/src/aws/osml/image_processing/gdal_tile_factory.py b/src/aws/osml/image_processing/gdal_tile_factory.py index 4818fcf..f80af71 100644 --- a/src/aws/osml/image_processing/gdal_tile_factory.py +++ b/src/aws/osml/image_processing/gdal_tile_factory.py @@ -6,7 +6,7 @@ from defusedxml import ElementTree from osgeo import gdal, gdalconst -from aws.osml.gdal import GDALCompressionOptions, GDALImageFormats, NITFDESAccessor, get_type_and_scales +from aws.osml.gdal import GDALCompressionOptions, GDALImageFormats, NITFDESAccessor, RangeAdjustmentType, get_type_and_scales from aws.osml.photogrammetry import ImageCoordinate, SensorModel from .sicd_updater import SICDUpdater @@ -26,6 +26,8 @@ def __init__( sensor_model: Optional[SensorModel] = None, tile_format: GDALImageFormats = GDALImageFormats.NITF, tile_compression: GDALCompressionOptions = GDALCompressionOptions.NONE, + output_type: Optional[int] = None, + range_adjustment: RangeAdjustmentType = RangeAdjustmentType.NONE, ): """ Constructs a new factory capable of producing tiles from a given GDAL raster dataset. @@ -34,6 +36,8 @@ def __init__( :param sensor_model: the sensor model providing mensuration support for this image :param tile_format: the output tile format :param tile_compression: the output tile compression + :param output_type: the GDAL pixel type in the output tile + :param range_adjustment: the type of scaling used to convert raw pixel values to the output range """ self.tile_format = tile_format self.tile_compression = tile_compression @@ -42,6 +46,8 @@ def __init__( self.des_accessor = None self.sicd_updater = None self.sicd_des_header = None + self.range_adjustment = range_adjustment + self.output_type = output_type if self.raster_dataset.GetDriver().ShortName == "NITF": xml_des = self.raster_dataset.GetMetadata("xml:DES") @@ -58,6 +64,8 @@ def __init__( self.sicd_des_header = self.des_accessor.extract_des_header(sicd_des) self.sicd_updater = SICDUpdater(sicd_metadata) + self.default_gdal_translate_kwargs = self._create_gdal_translate_kwargs() + def create_encoded_tile(self, src_window: List[int]) -> Optional[bytearray]: """ This method cuts a tile from the full image, updates the metadata as needed, and finally compresses/encodes @@ -71,10 +79,10 @@ def create_encoded_tile(self, src_window: List[int]) -> Optional[bytearray]: # Use the request and metadata from the raster dataset to create a set of keyword # arguments for the gdal.Translate() function. This will configure that function to # create image tiles using the format, compression, etc. requested by the client. - gdal_translate_kwargs = self._create_gdal_translate_kwargs() + gdal_translate_kwargs = self.default_gdal_translate_kwargs.copy() # Create a new IGEOLO value based on the corner points of this tile - if self.sensor_model is not None: + if self.sensor_model is not None and self.tile_format == GDALImageFormats.NITF: gdal_translate_kwargs["creationOptions"].append("ICORDS=G") gdal_translate_kwargs["creationOptions"].append("IGEOLO=" + self.create_new_igeolo(src_window)) @@ -148,9 +156,10 @@ def _create_gdal_translate_kwargs(self) -> Dict[str, Any]: :return: Dict[str, any] = the dictionary of translate keyword arguments """ - # Figure out what type of image this is and calculate a scale that does not force any range - # remapping - output_type, scale_params = get_type_and_scales(self.raster_dataset) + # Figure out what type of image this is and calculate a scale to map input pixels to the output type + output_type, scale_params = get_type_and_scales( + self.raster_dataset, desired_output_type=self.output_type, range_adjustment=self.range_adjustment + ) gdal_translate_kwargs = { "scaleParams": scale_params, diff --git a/test/aws/osml/gdal/test_dynamic_range_adjustment.py b/test/aws/osml/gdal/test_dynamic_range_adjustment.py new file mode 100644 index 0000000..eb24fff --- /dev/null +++ b/test/aws/osml/gdal/test_dynamic_range_adjustment.py @@ -0,0 +1,18 @@ +import unittest + + +class TestDRAParameters(unittest.TestCase): + def test_from_counts(self): + from aws.osml.gdal.dynamic_range_adjustment import DRAParameters + + counts = [0] * 1024 + counts[1:99] = [1] * (99 - 1) + counts[100:400] = [200] * (400 - 100) + counts[1022] = 1 + + dra_parameters = DRAParameters.from_counts(counts=counts) + + self.assertEquals(dra_parameters.actual_min_value, 1) + self.assertEquals(dra_parameters.actual_max_value, 1022) + self.assertAlmostEqual(dra_parameters.suggested_min_value, 47, delta=1) + self.assertAlmostEquals(dra_parameters.suggested_max_value, 506, delta=1) diff --git a/test/aws/osml/image_processing/test_gdal_tile_factory.py b/test/aws/osml/image_processing/test_gdal_tile_factory.py index 560996f..f6e3fc7 100644 --- a/test/aws/osml/image_processing/test_gdal_tile_factory.py +++ b/test/aws/osml/image_processing/test_gdal_tile_factory.py @@ -2,9 +2,9 @@ from secrets import token_hex from unittest import TestCase -from osgeo import gdal +from osgeo import gdal, gdalconst -from aws.osml.gdal import GDALCompressionOptions, GDALImageFormats, load_gdal_dataset +from aws.osml.gdal import GDALCompressionOptions, GDALImageFormats, RangeAdjustmentType, load_gdal_dataset from aws.osml.image_processing import GDALTileFactory @@ -35,6 +35,31 @@ def test_create_encoded_sicd_tile_png(self): assert tile_dataset.RasterYSize == 256 assert tile_dataset.GetDriver().ShortName == GDALImageFormats.PNG + def test_create_png_with_dra(self): + full_dataset, sensor_model = load_gdal_dataset("./test/data/small.ntf") + tile_factory = GDALTileFactory( + full_dataset, + sensor_model, + GDALImageFormats.PNG, + GDALCompressionOptions.NONE, + output_type=gdalconst.GDT_Byte, + range_adjustment=RangeAdjustmentType.DRA, + ) + + full_dataset.GetRasterBand(1).ComputeStatistics(approx_ok=0) + assert full_dataset.GetRasterBand(1).GetMinimum() == 0 + assert full_dataset.GetRasterBand(1).GetMaximum() == 255 + encoded_tile_data = tile_factory.create_encoded_tile([10, 10, 128, 256]) + temp_ds_name = "/vsimem/" + token_hex(16) + ".PNG" + gdal.FileFromMemBuffer(temp_ds_name, encoded_tile_data) + tile_dataset = gdal.Open(temp_ds_name) + assert tile_dataset.RasterXSize == 128 + assert tile_dataset.RasterYSize == 256 + assert tile_dataset.GetDriver().ShortName == GDALImageFormats.PNG + tile_dataset.GetRasterBand(1).ComputeStatistics(approx_ok=0) + assert tile_dataset.GetRasterBand(1).GetMinimum() == 0 + assert tile_dataset.GetRasterBand(1).GetMaximum() == 185 + # Test data here could be improved. We're reusing a nitf file for everything and just # testing a single raster scale def test_create_gdal_translate_kwargs(self): From 510d169453d9643100964ae9428588e6fbe18b98 Mon Sep 17 00:00:00 2001 From: edparris Date: Wed, 13 Sep 2023 11:39:29 -0400 Subject: [PATCH 03/11] feat: add HAE and DEM projections to SICD sensor model (#12) --- src/aws/osml/formats/sicd/__init__.py | 1 - src/aws/osml/gdal/gdal_dem_tile_factory.py | 39 ++- src/aws/osml/photogrammetry/__init__.py | 3 +- .../photogrammetry/digital_elevation_model.py | 35 +- .../osml/photogrammetry/elevation_model.py | 37 ++ .../osml/photogrammetry/sicd_sensor_model.py | 320 +++++++++++++++++- .../osml/gdal/test_gdal_dem_tile_factory.py | 8 +- .../osml/gdal/test_sensor_model_factory.py | 6 +- .../test_digital_elevation_model.py | 6 +- .../photogrammetry/test_sicd_sensor_model.py | 30 +- 10 files changed, 446 insertions(+), 39 deletions(-) diff --git a/src/aws/osml/formats/sicd/__init__.py b/src/aws/osml/formats/sicd/__init__.py index b2a4ba5..e69de29 100644 --- a/src/aws/osml/formats/sicd/__init__.py +++ b/src/aws/osml/formats/sicd/__init__.py @@ -1 +0,0 @@ -# nothing here diff --git a/src/aws/osml/gdal/gdal_dem_tile_factory.py b/src/aws/osml/gdal/gdal_dem_tile_factory.py index f89fb6a..6ec3b53 100644 --- a/src/aws/osml/gdal/gdal_dem_tile_factory.py +++ b/src/aws/osml/gdal/gdal_dem_tile_factory.py @@ -1,9 +1,16 @@ import logging from typing import Any, Optional, Tuple +import numpy as np from osgeo import gdal -from aws.osml.photogrammetry import DigitalElevationModelTileFactory, GDALAffineSensorModel +from aws.osml.photogrammetry import ( + DigitalElevationModelTileFactory, + ElevationRegionSummary, + GDALAffineSensorModel, + ImageCoordinate, + geodetic_to_geocentric, +) class GDALDigitalElevationModelTileFactory(DigitalElevationModelTileFactory): @@ -24,7 +31,9 @@ def __init__(self, tile_directory: str) -> None: super().__init__() self.tile_directory = tile_directory - def get_tile(self, tile_path: str) -> Tuple[Optional[Any], Optional[GDALAffineSensorModel]]: + def get_tile( + self, tile_path: str + ) -> Tuple[Optional[Any], Optional[GDALAffineSensorModel], Optional[ElevationRegionSummary]]: """ Retrieve a numpy array of elevation values and a sensor model. @@ -32,7 +41,7 @@ def get_tile(self, tile_path: str) -> Tuple[Optional[Any], Optional[GDALAffineSe :param tile_path: the location of the tile to load - :return: an array of elevation values and a sensor model or (None, None) + :return: an array of elevation values, a sensor model, and a summary or (None, None, None) """ tile_location = f"{self.tile_directory}/{tile_path}" tile_location = tile_location.replace("s3:/", "/vsis3", 1) @@ -43,15 +52,31 @@ def get_tile(self, tile_path: str) -> Tuple[Optional[Any], Optional[GDALAffineSe # information isn't available. if not ds: logging.debug(f"No DEM tile available for {tile_path}. Checked {tile_location}") - return None, None + return None, None, None # If the raster exists but doesn't have a geo transform then it is likely invalid input data. geo_transform = ds.GetGeoTransform(can_return_null=True) if not geo_transform: logging.warning(f"DEM tile does not have geo transform metadata and can't be used: {tile_location}") - return None, None + return None, None, None - band_as_array = ds.GetRasterBand(1).ReadAsArray(0, 0, ds.RasterXSize, ds.RasterYSize) + raster_band = ds.GetRasterBand(1) + band_as_array = raster_band.ReadAsArray(0, 0, ds.RasterXSize, ds.RasterYSize) + height, width = band_as_array.shape sensor_model = GDALAffineSensorModel(geo_transform) - return band_as_array, sensor_model + # Compute the distance in meters from the upper left to the lower right corner. Divide that by the distance + # in pixels to get an approximate pixel size in meters + ul_corner_ecf = geodetic_to_geocentric(sensor_model.image_to_world(ImageCoordinate([0, 0]))).coordinate + lr_corner_ecf = geodetic_to_geocentric(sensor_model.image_to_world(ImageCoordinate([width, height]))).coordinate + post_spacing = np.linalg.norm(ul_corner_ecf - lr_corner_ecf) / np.sqrt(width * width + height * height) + + stats = raster_band.GetStatistics(True, True) + summary = ElevationRegionSummary( + min_elevation=stats[0], + max_elevation=stats[1], + no_data_value=raster_band.GetNoDataValue(), + post_spacing=post_spacing, + ) + + return band_as_array, sensor_model, summary diff --git a/src/aws/osml/photogrammetry/__init__.py b/src/aws/osml/photogrammetry/__init__.py index f62e01e..2fc0fac 100644 --- a/src/aws/osml/photogrammetry/__init__.py +++ b/src/aws/osml/photogrammetry/__init__.py @@ -16,7 +16,7 @@ geodetic_to_geocentric, ) from .digital_elevation_model import DigitalElevationModel, DigitalElevationModelTileFactory, DigitalElevationModelTileSet -from .elevation_model import ConstantElevationModel, ElevationModel +from .elevation_model import ConstantElevationModel, ElevationModel, ElevationRegionSummary from .gdal_sensor_model import GDALAffineSensorModel from .projective_sensor_model import ProjectiveSensorModel from .replacement_sensor_model import ( @@ -57,6 +57,7 @@ "DigitalElevationModelTileSet", "ConstantElevationModel", "ElevationModel", + "ElevationRegionSummary", "GDALAffineSensorModel", "SARImageCoordConverter", "INCAProjectionSet", diff --git a/src/aws/osml/photogrammetry/digital_elevation_model.py b/src/aws/osml/photogrammetry/digital_elevation_model.py index b5577c3..a843cd9 100644 --- a/src/aws/osml/photogrammetry/digital_elevation_model.py +++ b/src/aws/osml/photogrammetry/digital_elevation_model.py @@ -9,7 +9,7 @@ from scipy.interpolate import RectBivariateSpline from .coordinates import GeodeticWorldCoordinate -from .elevation_model import ElevationModel +from .elevation_model import ElevationModel, ElevationRegionSummary from .sensor_model import SensorModel @@ -52,7 +52,7 @@ def __init__(self) -> None: pass @abstractmethod - def get_tile(self, tile_path: str) -> Tuple[Optional[Any], Optional[SensorModel]]: + def get_tile(self, tile_path: str) -> Tuple[Optional[Any], Optional[SensorModel], Optional[ElevationRegionSummary]]: """ Retrieve a numpy array of elevation values and a sensor model. @@ -60,7 +60,7 @@ def get_tile(self, tile_path: str) -> Tuple[Optional[Any], Optional[SensorModel] :param tile_path: the location of the tile to load - :return: an array of elevation values and a sensor model + :return: an array of elevation values, a sensor model, and a summary """ @@ -115,14 +115,31 @@ def set_elevation(self, geodetic_world_coordinate: GeodeticWorldCoordinate) -> N if not tile_id: return - interpolation_grid, sensor_model = self.get_interpolation_grid(tile_id) + interpolation_grid, sensor_model, summary = self.get_interpolation_grid(tile_id) if interpolation_grid is not None and sensor_model is not None: image_coordinate = sensor_model.world_to_image(geodetic_world_coordinate) geodetic_world_coordinate.elevation = interpolation_grid(image_coordinate.x, image_coordinate.y)[0][0] + def describe_region(self, geodetic_world_coordinate: GeodeticWorldCoordinate) -> Optional[ElevationRegionSummary]: + """ + Get a summary of the region near the provided world coordinate + + :param geodetic_world_coordinate: the coordinate at the center of the region of interest + :return: a summary of the elevation data in this tile + """ + + tile_id = self.tile_set.find_tile_id(geodetic_world_coordinate) + if not tile_id: + return + + interpolation_grid, sensor_model, summary = self.get_interpolation_grid(tile_id) + return summary + @cachedmethod(operator.attrgetter("raster_cache")) - def get_interpolation_grid(self, tile_path: str) -> Tuple[Optional[RectBivariateSpline], Optional[SensorModel]]: + def get_interpolation_grid( + self, tile_path: str + ) -> Tuple[Optional[RectBivariateSpline], Optional[SensorModel], Optional[ElevationRegionSummary]]: """ This method loads and converts an array of elevation values into a class that can interpolate values that lie between measured elevations. The sensor model is also @@ -135,13 +152,13 @@ def get_interpolation_grid(self, tile_path: str) -> Tuple[Optional[RectBivariate :param tile_path: the location of the tile to load - :return: the cached interpolation object and sensor model + :return: the cached interpolation object, sensor model, and summary """ - elevations_array, sensor_model = self.tile_factory.get_tile(tile_path) + elevations_array, sensor_model, summary = self.tile_factory.get_tile(tile_path) if elevations_array is not None and sensor_model is not None: height, width = elevations_array.shape x = range(0, width) y = range(0, height) - return RectBivariateSpline(x, y, elevations_array.T, kx=1, ky=1), sensor_model + return RectBivariateSpline(x, y, elevations_array.T, kx=1, ky=1), sensor_model, summary else: - return None, None + return None, None, None diff --git a/src/aws/osml/photogrammetry/elevation_model.py b/src/aws/osml/photogrammetry/elevation_model.py index 894dc0c..907cb1b 100644 --- a/src/aws/osml/photogrammetry/elevation_model.py +++ b/src/aws/osml/photogrammetry/elevation_model.py @@ -1,8 +1,22 @@ from abc import ABC, abstractmethod +from dataclasses import dataclass +from typing import Optional from .coordinates import GeodeticWorldCoordinate +@dataclass +class ElevationRegionSummary: + """ + This class contains a general summary of an elevation tile. + """ + + min_elevation: float + max_elevation: float + no_data_value: int + post_spacing: float + + class ElevationModel(ABC): """ An elevation model associates a height z for a given x, y of a world coordinate. It typically provides information @@ -24,6 +38,15 @@ def set_elevation(self, world_coordinate: GeodeticWorldCoordinate) -> None: :return: None """ + @abstractmethod + def describe_region(self, world_coordinate: GeodeticWorldCoordinate) -> Optional[ElevationRegionSummary]: + """ + Get a summary of the region near the provided world coordinate + + :param world_coordinate: the coordinate at the center of the region of interest + :return: the summary information + """ + class ConstantElevationModel(ElevationModel): """ @@ -50,3 +73,17 @@ def set_elevation(self, world_coordinate: GeodeticWorldCoordinate) -> None: :return: None """ world_coordinate.elevation = self.constant_elevation + + def describe_region(self, world_coordinate: GeodeticWorldCoordinate) -> Optional[ElevationRegionSummary]: + """ + Get a summary of the region near the provided world coordinate + + :param world_coordinate: the coordinate at the center of the region of interest + :return: [min elevation value, max elevation value, no elevation data value, post spacing] + """ + return ElevationRegionSummary( + min_elevation=self.constant_elevation, + max_elevation=self.constant_elevation, + no_data_value=-32767, + post_spacing=30.0, + ) diff --git a/src/aws/osml/photogrammetry/sicd_sensor_model.py b/src/aws/osml/photogrammetry/sicd_sensor_model.py index 29f8ef6..5edb348 100644 --- a/src/aws/osml/photogrammetry/sicd_sensor_model.py +++ b/src/aws/osml/photogrammetry/sicd_sensor_model.py @@ -677,6 +677,294 @@ def rrdot_to_ground(self, r_tgt_coa, r_dot_tgt_coa, arp_position, arp_velocity) return agpn + uvect_x * (gp_distance * cos_az) + uvect_y * (gp_distance * sin_az) +class HAERRDotSurfaceProjection(RRDotSurfaceProjection): + """ + This class implements the Precise R/RDot Height Above Ellipsioid (HAE) Projection described in Section 9 of the + SICD Specification Volume 3 (v1.3.0). + """ + + def __init__( + self, + scp_ecf: WorldCoordinate, + side_of_track: str, + hae: float, + height_threshold: float = 1.0, + max_number_iterations: int = 3, + ): + """ + Constructor for the projection that takes the image parameters and a height above theWGS-84 ellipsoid. + The parameter defaults are the recommended values for the user selectable parameters in section 9.2. + + :param scp_ecf: Scene Center Point position in ECF coordinates + :param side_of_track: side of track imaged + :param hae: the surface height (m) above the WGS-84 reference ellipsoid + :param height_threshold: the height threshold for convergence of iterative projection sequence + :param max_number_iterations: maximum number of iterations allowed + """ + self.scp_ecf = scp_ecf + self.scp_lle = geocentric_to_geodetic(scp_ecf) + self.side_of_track = side_of_track + self.hae = hae + + # Integer based on side of track parameter + self.look = 1.0 + if side_of_track == "R": + self.look = -1.0 + + self.delta_hae_max = height_threshold + self.nlim = max_number_iterations + + def rrdot_to_ground(self, r_tgt_coa, r_dot_tgt_coa, arp_position, arp_velocity) -> np.ndarray: + """ + This method implements the R/RDot Contour Ground Plane Intersection described in section 9.2. + + The precise projection to a surface of constant height above the WGS-84 reference ellipsoid along an + R/Rdot contour. The R/Rdot contour is relative to an ARP Center Of Aperture position and velocity. The + algorithm computes the R/Rdot projection to one or more ground planes that are tangent to the constant + height surface. Each ground plane projection point computed is slightly above the constant HAE surface. + The final surface position is computed by projecting from the final ground plane projection point down + to the HAE surface. + + :param r_tgt_coa: target COA range + :param r_dot_tgt_coa: target COA range rate + :param arp_position: ARP position + :param arp_velocity: ARP velocity + :return: the intersection between the R/Rdot Contour and the ground plane + """ + # (1) Compute the geodetic ground plane normal at the SCP. Compute the parameters for the initial ground plane. + # The reference point position is gref and the unit normal is u_gpn. + u_gpn = np.array( + [ + np.cos(self.scp_lle.latitude) * np.cos(self.scp_lle.longitude), + np.cos(self.scp_lle.latitude) * np.sin(self.scp_lle.longitude), + np.sin(self.scp_lle.latitude), + ] + ) + gref = self.scp_ecf.coordinate + (self.hae - self.scp_ecf.z) * u_gpn + + cont = True + n = 1 + while cont: + # (2) Compute the precise projection along the R/Rdot contour to Ground Plane. The result is ground plane + # point position gpp_ecf. Convert from ECF coordinates to geodetic coordinates (gpp_lle). + gp_surface_projection = GroundPlaneRRDotSurfaceProjection(ref_ecf=WorldCoordinate(gref), gpn=u_gpn) + gpp_ecf = gp_surface_projection.rrdot_to_ground(r_tgt_coa, r_dot_tgt_coa, arp_position, arp_velocity)[0] + gpp_lle = geocentric_to_geodetic(WorldCoordinate(coordinate=gpp_ecf)) + + # (3) Compute the unit vector in the increasing height direction at point gpp_lle, (u_up). Also + # compute the height difference at point gpp_lle relative to the desired surface height (delta_hae). + u_up = np.array( + [ + np.cos(gpp_lle.latitude) * np.cos(gpp_lle.longitude), + np.cos(gpp_lle.latitude) * np.sin(gpp_lle.longitude), + np.sin(gpp_lle.latitude), + ] + ) + delta_hae = gpp_lle.elevation - self.hae + + # (4) Test to see if the point is sufficiently close the surface or if the maximum number of iterations + # has been reached. Otherwise, compute a new ground reference point (gref) and unit normal (u_up); repeat + # Steps 2, 3 and 4. + if delta_hae <= self.delta_hae_max or n >= self.nlim: + cont = False + else: + gref = gpp_ecf - delta_hae * u_up + u_gpn = u_up + n += 1 + + # (5) Compute the unit slant plane normal vector, u_spn, that is tangent to the R/Rdot contour at point gpp. + # Unit vector u_spn points away from the center of the earth and in a direction of increasing HAE at gpp. + spn = np.cross(self.look * arp_velocity, gpp_ecf - arp_position) + u_spn = spn / np.linalg.norm(spn) + + # (6) Compute the straight line projection from point gpp_ecf along the slant plane normal to point slp. + # Point slp is very close to the precise R/Rdot contour intersection with the constant height surface. + # Convert the position of point slp from ECF coordinates to geodetic coordinates (slp_lle). + sf = np.dot(u_up, u_spn) + slp = gpp_ecf - (delta_hae / sf) * u_spn + slp_lle = geocentric_to_geodetic(WorldCoordinate(slp)) + + # (7) Assign surface point spp position by adjusting the HAE to be on the desired surface. Convert from + # geodetic coordinates to ECF coordinates. + spp_lle = GeodeticWorldCoordinate([slp_lle.longitude, slp_lle.latitude, self.hae]) + spp_ecf = geodetic_to_geocentric(spp_lle) + + return np.array([spp_ecf.coordinate]) + + +class DEMRRDotSurfaceProjection(RRDotSurfaceProjection): + """ + This class implements the Precise R/RDot Height Above a Digital Elevation Model (DEM) Projection described in + Section 10 of the SICD Specification Volume 3 (v1.3.0). + """ + + def __init__( + self, + scp_ecf: WorldCoordinate, + side_of_track: str, + elevation_model: ElevationModel, + max_adjacent_point_distance: float = 10.0, + height_threshold: float = 0.001, + ): + """ + Constructor for the projection that takes the image parameters and a digital elevation model (DEM). + The parameter defaults are the recommended values for the user selectable parameters in section 10.3. + + :param scp_ecf: Scene Center Point position in ECF coordinates + :param side_of_track: side of track imaged + :param elevation_model: the digital elevation model + :param max_adjacent_point_distance: Maximum distance between adjacent points along the R/Rdot contour + :param height_threshold: threshold for determining if a R/Rdot contour point is on the DEM surface (m) + """ + self.scp_ecf = scp_ecf + self.scp_lle = geocentric_to_geodetic(scp_ecf) + self.side_of_track = side_of_track + self.elevation_model = elevation_model + + # Integer based on Side of Track parameter. + self.look = 1.0 + if side_of_track == "R": + self.look = -1.0 + + elevation_summary = elevation_model.describe_region(self.scp_lle) + self.hae_min = elevation_summary.min_elevation + self.hae_max = elevation_summary.max_elevation + self.hae_max_surface_projection = HAERRDotSurfaceProjection( + scp_ecf=scp_ecf, side_of_track=side_of_track, hae=self.hae_max + ) + self.hae_min_surface_projection = HAERRDotSurfaceProjection( + scp_ecf=scp_ecf, side_of_track=side_of_track, hae=self.hae_min + ) + self.delta_dist_dem = 0.5 * elevation_summary.post_spacing + + self.delta_dist_rrc = max_adjacent_point_distance + self.delta_hd_lim = height_threshold + + def rrdot_to_ground(self, r_tgt_coa, r_dot_tgt_coa, arp_position, arp_velocity) -> np.ndarray: + """ + This method implements the R/RDot Contour Ground Plane Intersection described in section 10.3 + + The R/Rdot contour is relative to an ARP Center Of Aperture position and velocity. The earth surface is + described by a Digital Elevation Model (DEM) that defines a unique surface height as a function of two + horizontal coordinates. The projection computation may yield one or more surface points that lie along + the R/Rdot contour. + + :param r_tgt_coa: target COA range + :param r_dot_tgt_coa: target COA range rate + :param arp_position: ARP position + :param arp_velocity: ARP velocity + :return: the intersection between the R/Rdot Contour and the DEM, if multiple intersections occur they will + be returned in order of increasing height above the WGS-84 ellipsoid. + """ + + # (1) Compute the center point (ctr) and the radius of the R/Rdot projection contour (rrrc). + v_mag = np.linalg.norm(arp_velocity) + u_vel = arp_velocity / v_mag + cos_dca = -1.0 * r_dot_tgt_coa / v_mag + sin_dca = np.sqrt(1 - cos_dca * cos_dca) + ctr = arp_position + r_tgt_coa * cos_dca * u_vel + rrrc = r_tgt_coa * sin_dca + + # (2) Compute the unit vectors u_rrx and u_rry to be used to compute points located on the R/Rdot contour. + dec_arp = np.linalg.norm(arp_position) + u_up = arp_position / dec_arp + rry = np.cross(u_up, u_vel) + u_rry = rry / np.linalg.norm(rry) + u_rrx = np.cross(u_rry, u_vel) + + # (3) Compute the projection along the R/Rdot contour to the surface of constant HAE at height hae_max. + # The projection point at height hae_max is point_a. Also compute the cosine and sine of the contour angle + # to point_a, cos_caa and sin_caa. + point_a = self.hae_max_surface_projection.rrdot_to_ground(r_tgt_coa, r_dot_tgt_coa, arp_position, arp_velocity)[0] + cos_caa = np.dot(point_a - ctr, u_rrx) / rrrc + # This variable is defined in the specification but it does not appear to be used anywhere + # sin_caa = self.look * np.sqrt(1 - cos_caa * cos_caa) + + # (4) Compute the projection along the R/Rdot contour to the surface of constant HAE at height hae_min. + # The projection point at height hae_min is point_b. Also compute the cosine and sine of the contour angle + # to point_b, cos_cab and sin_cab. + point_b = self.hae_min_surface_projection.rrdot_to_ground(r_tgt_coa, r_dot_tgt_coa, arp_position, arp_velocity)[0] + cos_cab = np.dot(point_b - ctr, u_rrx) / rrrc + sin_cab = self.look * np.sqrt(1 - cos_cab * cos_cab) + + # (5) A set of points along the R/Rdot contour are to be computed. The points will be spaced in equal + # increments of the cosine of the contour angle. Compute the step size, delta_cos_ca. + + # (5.1) Step size delta_cos_rrc is computed such that the distance between adjacent points on the R/Rdot + # contour is approximately equal to delta_dist_rrc. + delta_cos_rrc = self.delta_dist_rrc * np.abs(sin_cab) / rrrc + + # (5.2) Step size delta_cos_dem is computed such that the horizontal distance between adjacent points on + # the R/Rdot contour is approximately equal to delta_dist_dem. + delta_cos_dem = self.delta_dist_dem * (np.abs(sin_cab) / cos_cab) / rrrc + + # (5.3) Set delta_cos_ca (Note the value of delta_cos_ca is < 0) + delta_cos_ca = -1.0 * min(delta_cos_rrc, delta_cos_dem) + + # (6) Determine the number of points along the R/Rdot contour to be computed, npts. + npts = int(np.floor((cos_caa - cos_cab) / delta_cos_ca)) + 2 + + # (7) Compute the set of points along the R/Rdot contour, {Pn} for n =0, 2, ..., npts-1. Initial point P1 is + # located on the hae_min surface. The final point is located above the hae_max surface. Point Pn is computed + # in ECF coordinates. Note that here n ranges from [0, npts-1] while in the specification n is [1, npts]. + # Equations have been modified accordingly. + points_ecf = [] + for n in range(0, npts): + cos_can = cos_cab + n * delta_cos_ca # n-1 is unnecessary since n is zero based here + sin_can = self.look * np.sqrt(1 - cos_can * cos_can) + pn = ctr + rrrc * (cos_can * u_rrx + sin_can * u_rry) + points_ecf.append(pn) + + # (8 - 10) For each of the NPTS points, convert from ECF coordinates to DEM coordinates (lon, lat, ele). Also + # compute the DEM surface height for the point with DEM horizontal coordinates (lon, lat). Compute the + # difference in height (delta_height) between the point on the contour and DEM surface point. Also, + # set an indicator to track if that point is ABOVE, ON, or BELOW the DEM surface. + # + # Contour points that are within delta_hd_lim of the surface point are considered to be “on” the surface and + # will be added to the result set. Also compute a result point when points n and n+1 when both are “off” + # the surface and the R/Rdot contour intersects the surface between them (i.e. indicator n-1 x indicator n = -1) + # + # All height coordinates are in meters. + intersection_points = [] + prev_indicator = None + prev_delta_height = None + for n in range(0, npts): + point_lle = geocentric_to_geodetic(WorldCoordinate(points_ecf[n])) + point_dem = GeodeticWorldCoordinate(point_lle.coordinate.copy()) + self.elevation_model.set_elevation(point_dem) + delta_height = point_lle.elevation - point_dem.elevation + + # Determine if the contour point is ABOVE (indicator = 1), ON (indicator = 0), or BELOW (indicator = -1) + if np.abs(delta_height) < self.delta_hd_lim: + # Contour point is on the DEM surface, add it to the result set + indicator = 0 + intersection_points.append(points_ecf[n]) + elif delta_height > self.delta_hd_lim: + # Contour point is above the DEM surface + indicator = 1 + else: + # Contour point is below the DEM surface + indicator = -1 + + # If in two adjacent points one is ABOVE and the other BELOW then the contour intersected the DEM + # surface between the two points. Interpolate an intersection point and add it to the result. + if prev_indicator and prev_indicator * indicator == -1: + # contour crossed between two points; compute the surface point between + frac = prev_delta_height / (prev_delta_height - delta_height) + cos_cas = cos_cab + (n - 1 + frac) * delta_cos_ca # here the -1 is necessary for previous point + sin_cas = self.look * np.sqrt(1 - cos_cas * cos_cas) + sm = ctr + rrrc * (cos_cas * u_rrx + sin_cas * u_rry) + intersection_points.append(sm) + + # Keep track of the current indicator and delta height for comparison to the next point + prev_indicator = indicator + prev_delta_height = delta_height + + # Return the set of surface points found. Points will be ordered of increasing height above the WGS-84 + # ellipsoid. + return np.array(intersection_points) + + class SICDSensorModel(SensorModel): """ This is an implementation of the SICD sensor model as described by SICD Volume 3 Image Projections Description @@ -704,7 +992,7 @@ def __init__( """ super().__init__() self.coa_projection_set = coa_projection_set - self.image_plane = coord_converter + self.coord_converter = coord_converter self.uvect_gpn = u_gpn self.scp_arp = scp_arp self.scp_varp = scp_varp @@ -715,8 +1003,7 @@ def __init__( self.uvect_spn *= -1.0 self.uvect_spn /= np.linalg.norm(self.uvect_spn) - # TODO: Add option for HAE ground assumption, does world_to_image always need a GroundPlaneProjection? - self.default_surface_projection = GroundPlaneRRDotSurfaceProjection(self.image_plane.scp_ecf, self.uvect_gpn) + self.default_surface_projection = GroundPlaneRRDotSurfaceProjection(self.coord_converter.scp_ecf, self.uvect_gpn) def image_to_world( self, @@ -726,7 +1013,9 @@ def image_to_world( ) -> GeodeticWorldCoordinate: """ This is an implementation of an Image Grid to Scene point projection that first projects the image - location to the R/RDot contour and then intersects the R/RDot contour with the elevation model. + location to the R/RDot contour and then intersects the R/RDot contour with the elevation model. If + an elevation model is provided then this routine intersects the R/Rdot contour with the DEM surface which + may result in multiple solutions. In that case the solution with the lowest HAE is returned. :param image_coordinate: the x,y image coordinate :param elevation_model: the optional elevation model, if none supplied a plane tangent to SCP is assumed @@ -734,13 +1023,18 @@ def image_to_world( :return: the lon, lat, elev geodetic coordinate of the surface matching the image coordinate """ row_col = np.array( - [image_coordinate.r + self.image_plane.first_pixel.r, image_coordinate.c + self.image_plane.first_pixel.c] + [ + image_coordinate.r + self.coord_converter.first_pixel.r, + image_coordinate.c + self.coord_converter.first_pixel.c, + ] ) - xrow_ycol = self.image_plane.rowcol_to_xrowycol(row_col=row_col) + xrow_ycol = self.coord_converter.rowcol_to_xrowycol(row_col=row_col) r_tgt_coa, r_dot_tgt_coa, time_coa, arp_coa, varp_coa = self.coa_projection_set.precise_rrdot_computation(xrow_ycol) if elevation_model is not None: - raise NotImplementedError("SICD sensor model with DEM not yet implemented") + surface_projection = DEMRRDotSurfaceProjection( + self.coord_converter.scp_ecf, self.side_of_track, elevation_model=elevation_model + ) else: surface_projection = self.default_surface_projection @@ -768,7 +1062,7 @@ def world_to_image(self, world_coordinate: GeodeticWorldCoordinate) -> ImageCoor # The GP to IP projection direction is along the SCP COA slant plane normal. Also, compute the image # plane unit normal, uIPN. Compute projection scale factor SF as shown. uvect_proj = self.uvect_spn - scale_factor = float(np.dot(uvect_proj, self.image_plane.uvect_ipn)) + scale_factor = float(np.dot(uvect_proj, self.coord_converter.uvect_ipn)) # (3) Set initial ground plane position G1 to the scene point position S. scene_point = np.array([ecf_world_coordinate.x, ecf_world_coordinate.y, ecf_world_coordinate.z]) @@ -782,9 +1076,9 @@ def world_to_image(self, world_coordinate: GeodeticWorldCoordinate) -> ImageCoor # (4) Project ground plane point g_n to image plane point i_n. The projection distance is dist_n. Compute # image coordinates xrown and ycoln. - dist_n = np.dot(self.image_plane.scp_ecf.coordinate - g_n, self.image_plane.uvect_ipn) / scale_factor + dist_n = np.dot(self.coord_converter.scp_ecf.coordinate - g_n, self.coord_converter.uvect_ipn) / scale_factor i_n = g_n + dist_n * uvect_proj - xrow_ycol_n = self.image_plane.ipp_to_xrowycol(i_n) + xrow_ycol_n = self.coord_converter.ipp_to_xrowycol(i_n) # (5) Compute the precise projection for image grid location (xrown, ycoln) to the ground plane containing # the scene point S. The result is point p_n. For image grid location (xrown, ycoln), compute COA @@ -805,9 +1099,11 @@ def world_to_image(self, world_coordinate: GeodeticWorldCoordinate) -> ImageCoor # grid location (xrown, ycoln) as the precise image grid location for scene point S. cont = delta_gpn > tolerance and (iteration < max_iterations) - row_col = self.image_plane.xrowycol_to_rowcol(xrow_ycol_n) + row_col = self.coord_converter.xrowycol_to_rowcol(xrow_ycol_n) # Convert the row_col image grid location to an x,y image coordinate. Note that row_col is in reference # to the full image, so we subtract off the first_pixel offset to make the image coordinate correct if this # is a chip. - return ImageCoordinate([row_col[1] - self.image_plane.first_pixel.x, row_col[0] - self.image_plane.first_pixel.y]) + return ImageCoordinate( + [row_col[1] - self.coord_converter.first_pixel.x, row_col[0] - self.coord_converter.first_pixel.y] + ) diff --git a/test/aws/osml/gdal/test_gdal_dem_tile_factory.py b/test/aws/osml/gdal/test_gdal_dem_tile_factory.py index 96d94d0..cb38444 100644 --- a/test/aws/osml/gdal/test_gdal_dem_tile_factory.py +++ b/test/aws/osml/gdal/test_gdal_dem_tile_factory.py @@ -21,7 +21,7 @@ def test_load_geotiff_tile(self): from aws.osml.photogrammetry import GeodeticWorldCoordinate tile_factory = GDALDigitalElevationModelTileFactory("./test/data") - elevation_array, sensor_model = tile_factory.get_tile("n47_e034_3arc_v2.tif") + elevation_array, sensor_model, summary = tile_factory.get_tile("n47_e034_3arc_v2.tif") assert elevation_array is not None assert elevation_array.shape == (1201, 1201) @@ -32,6 +32,12 @@ def test_load_geotiff_tile(self): assert center_image.x == pytest.approx(600.5, abs=1.0) assert center_image.y == pytest.approx(600.5, abs=1.0) + assert summary.min_elevation == -1.0 + assert summary.max_elevation == 152.0 + assert summary.no_data_value == -32767 + # The 3-arc second test file used here is somewhere around 90 meter resolution + assert abs(90.0 - summary.post_spacing) < 20.0 + if __name__ == "__main__": unittest.main() diff --git a/test/aws/osml/gdal/test_sensor_model_factory.py b/test/aws/osml/gdal/test_sensor_model_factory.py index f4d0f67..c347ad9 100644 --- a/test/aws/osml/gdal/test_sensor_model_factory.py +++ b/test/aws/osml/gdal/test_sensor_model_factory.py @@ -185,11 +185,11 @@ def test_sicd_sensor_models(self): scp_image_coord = ImageCoordinate( [ - sm.image_plane.scp_pixel.x - sm.image_plane.first_pixel.x, - sm.image_plane.scp_pixel.y - sm.image_plane.first_pixel.y, + sm.coord_converter.scp_pixel.x - sm.coord_converter.first_pixel.x, + sm.coord_converter.scp_pixel.y - sm.coord_converter.first_pixel.y, ] ) - scp_world_coord = geocentric_to_geodetic(sm.image_plane.scp_ecf) + scp_world_coord = geocentric_to_geodetic(sm.coord_converter.scp_ecf) assert np.allclose(scp_image_coord.coordinate, sm.world_to_image(scp_world_coord).coordinate, atol=1.0) diff --git a/test/aws/osml/photogrammetry/test_digital_elevation_model.py b/test/aws/osml/photogrammetry/test_digital_elevation_model.py index 4b4ea65..b54119c 100644 --- a/test/aws/osml/photogrammetry/test_digital_elevation_model.py +++ b/test/aws/osml/photogrammetry/test_digital_elevation_model.py @@ -13,6 +13,7 @@ def test_dem_interpolation(self): DigitalElevationModelTileFactory, DigitalElevationModelTileSet, ) + from aws.osml.photogrammetry.elevation_model import ElevationRegionSummary from aws.osml.photogrammetry.sensor_model import SensorModel mock_tile_set = mock.Mock(DigitalElevationModelTileSet) @@ -20,6 +21,7 @@ def test_dem_interpolation(self): # This is a sample 3x3 grid of elevation data test_elevation_data = np.array([[0.0, 1.0, 4.0], [1.0, 2.0, 3.0], [2.0, 3.0, 4.0]]) + test_elevation_summary = ElevationRegionSummary(0.0, 4.0, -1, 30.0) # These are the points we will test for interpolation test_grid_coordinates = [ @@ -44,7 +46,7 @@ def test_dem_interpolation(self): # This mock tile factory will always return the 3x3 elevation grid and the sensor model mock_tile_factory = mock.Mock(DigitalElevationModelTileFactory) - mock_tile_factory.get_tile.return_value = test_elevation_data, mock_sensor_model + mock_tile_factory.get_tile.return_value = test_elevation_data, mock_sensor_model, test_elevation_summary dem = DigitalElevationModel(mock_tile_set, mock_tile_factory) @@ -96,7 +98,7 @@ def test_missing_tile(self): mock_tile_set = mock.Mock(DigitalElevationModelTileSet) mock_tile_set.find_tile_id.return_value = "MockN00E000V0.tif" mock_tile_factory = mock.Mock(DigitalElevationModelTileFactory) - mock_tile_factory.get_tile.return_value = None, None + mock_tile_factory.get_tile.return_value = None, None, None dem = DigitalElevationModel(mock_tile_set, mock_tile_factory) diff --git a/test/aws/osml/photogrammetry/test_sicd_sensor_model.py b/test/aws/osml/photogrammetry/test_sicd_sensor_model.py index 357e1b0..f43cc2f 100644 --- a/test/aws/osml/photogrammetry/test_sicd_sensor_model.py +++ b/test/aws/osml/photogrammetry/test_sicd_sensor_model.py @@ -1,6 +1,7 @@ import unittest from math import radians from pathlib import Path +from typing import Optional, Tuple import numpy as np from xsdata.formats.dataclass.parsers import XmlParser @@ -8,6 +9,8 @@ import aws.osml.formats.sicd.models.sicd_v1_2_1 as sicd121 from aws.osml.gdal.sicd_sensor_model_builder import poly1d_to_native, poly2d_to_native, xyzpoly_to_native, xyztype_to_ndarray from aws.osml.photogrammetry import ( + ConstantElevationModel, + ElevationRegionSummary, GeodeticWorldCoordinate, ImageCoordinate, INCAProjectionSet, @@ -16,6 +19,7 @@ SARImageCoordConverter, SICDSensorModel, WorldCoordinate, + geocentric_to_geodetic, geodetic_to_geocentric, ) @@ -64,18 +68,38 @@ def test_xrgycr(self): side_of_track=str(sicd.scpcoa.side_of_track.value), ) + # This is a test class that forces a constant elevation model to have a min/max height range of 100 + # meters. It's necessary to force the DEM intersection code with the SICD sensor model to iterate through + # several points searching for an intersection. + class TestElevationModel(ConstantElevationModel): + def __init__(self, hae: float): + super().__init__(constant_elevation=hae) + + def describe_region( + self, world_coordinate: GeodeticWorldCoordinate + ) -> Optional[Tuple[float, float, float, float]]: + summary = super().describe_region(world_coordinate) + return ElevationRegionSummary( + min_elevation=summary.min_elevation - 100, + max_elevation=summary.max_elevation + 100, + no_data_value=summary.no_data_value, + post_spacing=summary.post_spacing, + ) + + scp_lle = geocentric_to_geodetic(scp_ecf) + elevation_model = TestElevationModel(scp_lle.elevation) geodetic_world_coordinate = sicd_sensor_model.image_to_world( - ImageCoordinate([sicd.image_data.scppixel.col, sicd.image_data.scppixel.row]) + ImageCoordinate([sicd.image_data.scppixel.col, sicd.image_data.scppixel.row]), elevation_model=elevation_model ) ecf_world_coordinate = geodetic_to_geocentric(geodetic_world_coordinate) - assert np.allclose(ecf_world_coordinate.coordinate, scp_ecf.coordinate) + assert np.allclose(ecf_world_coordinate.coordinate, scp_ecf.coordinate, atol=0.001) geo_scp_world_coordinate = GeodeticWorldCoordinate( [radians(sicd.geo_data.scp.llh.lon), radians(sicd.geo_data.scp.llh.lat), sicd.geo_data.scp.llh.hae] ) - assert np.allclose(geo_scp_world_coordinate.coordinate, geodetic_world_coordinate.coordinate) + assert np.allclose(geo_scp_world_coordinate.coordinate, geodetic_world_coordinate.coordinate, atol=1.0e-5) calculated_image_scp = sicd_sensor_model.world_to_image(geo_scp_world_coordinate) From ba9b1c1131b843e6e792abd3268626fd0221f0d7 Mon Sep 17 00:00:00 2001 From: Ranbir Aulakh Date: Thu, 14 Sep 2023 21:46:38 -0400 Subject: [PATCH 04/11] feat: Fix documentation commands for github actions (#13) --- .github/workflows/documentation.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/documentation.yaml b/.github/workflows/documentation.yaml index 26193d6..a9bc5ca 100644 --- a/.github/workflows/documentation.yaml +++ b/.github/workflows/documentation.yaml @@ -13,14 +13,15 @@ jobs: pip install sphinx python -m pip install sphinx-autoapi python -m pip install sphinx_rtd_theme + python -m pip install tox tox-gh-actions - name: Sphinx build run: | - sphinx-build doc _build + tox -e docs - name: Deploy uses: peaceiris/actions-gh-pages@v3.9.3 if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} with: publish_branch: gh-pages github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: _build/ + publish_dir: .tox/docs/tmp/html/ force_orphan: true From 961d0aa2dcfacbf7cd16e6c861dd0e5668b90509 Mon Sep 17 00:00:00 2001 From: drduhe Date: Tue, 19 Sep 2023 13:56:55 -0600 Subject: [PATCH 05/11] Updating CONTRIBUTING.md (#11) feat: Updating CONTRIBUTING.md to reflect project requirements. --- CONTRIBUTING.md | 275 ++++++++++++++++++++++++++----- README.md | 43 ++--- images/release-deployment.png | Bin 0 -> 79023 bytes images/release_deployment.sketch | Bin 0 -> 83380 bytes 4 files changed, 249 insertions(+), 69 deletions(-) mode change 100644 => 100755 CONTRIBUTING.md create mode 100644 images/release-deployment.png create mode 100644 images/release_deployment.sketch diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md old mode 100644 new mode 100755 index c4b6a1c..b879f17 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,59 +1,260 @@ -# Contributing Guidelines +# OSML Contributing Guidelines -Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional -documentation, we greatly value feedback and contributions from our community. +Thank you for your interest in contributing to our project! This document will guide you through the process of +contributing to our repository using a release-based GitFlow workflow. Please read the guidelines carefully in order to +submit your pull requests effectively. -Please read through this document before submitting any issues or pull requests to ensure we have all the necessary -information to effectively respond to your bug report or contribution. +## Table of Contents +- [Release-based GitFlow](#release-based-gitflow) +- [Branches](#branches) +- [Linting](#linting) +- [Use Cases](#use-cases) + - [Develop a new feature](#develop-a-new-feature) + - [Develop multiple features in parallel](#develop-multiple-features-in-parallel) + - [Create and deploy a release](#create-and-deploy-a-release) + - [Production hot fix](#production-hot-fix) +- [Code Style](#code-style) +- [Commit Messages](#commit-messages) +- [Issue Tracking](#issue-tracking) -## Reporting Bugs/Feature Requests +## Release-based GitFlow -We welcome you to use the GitHub issue tracker to report bugs or suggest features. +We follow a release-based GitFlow branching model to manage our codebase. It involves different types of branches to +ensure proper version control and collaboration. The primary branches in this workflow are: -When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already -reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: +note- `main`: This branch represents the latest production-ready state of the project code. +- `dev`: This branch is used as the integration branch, combining all feature branches for a specific release. +- `release/vX.Y.Z`: Branches in the format of `release/vX.Y.Z` are used to prepare a specific release. +- `feature/*`: Feature branches are created for the development of new features or significant enhancements. +- `hotfix/*`: Hotfix branches are created for bug fixes against production code. -* A reproducible test case or series of steps -* The version of our code being used -* Any modifications you've made relevant to the bug -* Anything unusual about your environment or deployment +## Branches -## Contributing via Pull Requests -Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: +Here's a brief description of the supported branches in our repository: -1. You are working against the latest source on the *main* branch. -2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. -3. You open an issue to discuss any significant work - we would hate for your time to be wasted. +![Release Deployment workflow](images/release-deployment.png) -To send us a pull request, please: +| Branch | Protected? | Base Branch | Description | +|:-----------------|:-----------|:-------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `main` | YES | N/A | What is live in production (**stable**).
A pull request is required to merge code into `main`. | +| `dev` | YES | `main` | The latest state of development (**unstable**).
A pull request is required to merge code into `dev` | +| `feature/*` | NO | `dev` | Cutting-edge features (**unstable**). These branches are used for any maintenance features / active development. | +| `release/vX.Y.Z` | NO | `dev` | A temporary release branch that follows the [semver](http://semver.org/) versioning. This is what is sent to UAT.
A pull request is required to merge code into any `release/vX.Y.Z` branch. | +| `hotfix/*` | NO | `main` | These are bug fixes against production.
This is used because develop might have moved on from the last published state.
Remember to merge this back into develop and any release branches. | -1. Fork the repository. -2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. -3. Ensure local tests pass. -4. Commit to your fork using clear commit messages. -5. Send us a pull request, answering any default questions in the pull request interface. -6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. -GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and -[creating a pull request](https://help.github.com/articles/creating-a-pull-request/). +## Linting +This package uses a number of tools to enforce formatting, linting, and general best practices: +* [eslint](https://github.com/pre-commit/mirrors-eslint) to check pep8 compliance and logical errors in code +* [prettier](https://github.com/pre-commit/mirrors-prettier) to check pep8 compliance and logical errors in code +* [pre-commit](https://github.com/pre-commit/pre-commit-hooks) to install and control linters in githooks -## Finding contributions to work on -Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. +After pulling the repository, you should enable auto linting git commit hooks by running: +```bash +python3 -m pip install pre-commit +pre-commit install +``` -## Code of Conduct -This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). -For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact -opensource-codeofconduct@amazon.com with any additional questions or comments. +In addition to running the linters against files included in a commit, you can perform linting on all the files +in the package by running: +```bash +pre-commit run --all-files --show-diff-on-failure +``` +or if using tox +```bash +tox -e lint +```` +## Use Cases -## Security issue notifications -If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. +### Develop a new feature +1. Make sure your `dev` branch is up-to-date -## Licensing +2. Create a feature branch based off of `dev` -See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. + ```bash + $ git checkout dev + $ git checkout -b feature/new-documentation + $ git push --set-upstream feature/new-documentation + ``` + +3. Develop the code for the new feature and commit. Push your changes often. This + allows others to see your changes and suggest improvements/changes as well as + provides a safety net should your hard drive crash. + + ```bash + $ ... make changes + $ git add -A . + $ git commit -m "Add new documentation files" + $ ... make more changes + $ git add -A . + $ git commit -m "Fix some spelling errors" + $ git push + ``` + +4. Navigate to the project on [GitHub](https://github.com) and open a pull request with + the following branch settings: + * Base: `dev` + * Compare: `feature/new-documentation` + +5. When the pull request was reviewed, merge and close it and delete the + `feature/new-documentation` branch. + +### Develop multiple features in parallel + +There's nothing special about that. Each developer follows the above [Develop a new feature](#develop-a-new-feature) process. + +### Create and deploy a release +1. Merge `main` into `dev` to ensure the new release will contain the + latest production code. This reduces the chance of a merge conflict during + the release. + + ```bash + $ git checkout dev + $ git merge main + ``` + +2. Create a new `release/vX.Y.Z` release branch off of `dev`. + + ```bash + $ git checkout -b release/vX.Y.Z + $ git push --set-upstream release/vX.Y.Z + ``` + +3. Stabilize the release by using bugfix branches off of the `release/vX.Y.Z` branch + (the same way you would do a feature branch off of `dev`). + + ```bash + $ git checkout release/vX.Y.Z + $ git checkout -b hotfix/fix-label-alignment + $ git push --set-upstream hotfix/fix-label-alignment + ... do work + $ git commit -m "Adjust label to align with button" + $ git push + ``` + +4. When the code is ready to release, navigate to the project on + [GitHub](https://github.com) and open a pull request with the following branch + settings: + * Base: `main` + * Compare: `release/vX.Y.Z` + Paste the Release Checklist into the PR body. Each project should define a release + checklist. TBD - will link release checklist here. + +5. At some point in the checklist you will merge the release branch into `main`. + You can do this by using the "Merge pull request" button on the release PR. + +6. Now you are ready to create the actual release. Navigate to the project page + on GitHub and draft a new release with the following settings: + * Tag version: `vX.Y.Z` + * Target: `main` + * Release title: `Release vX.Y.Z` + * Description: Includes a high-level list of things changed in this release. + Click `Publish release`. + +7. Merge the `release/vX.Y.Z` into `dev`. + + ```bash + $ git checkout dev + $ git merge release/vX.Y.Z + $ git push + ``` + +8. Finish off the tasks in the release checklist. Once everything is done, close + the release PR. + +9. Delete the release branch on GitHub. + +### Production hot fix + +A production hotfix is very similar to a full-scale release except that you do +your work in a branch taken directly off of `main`. Hotfixes are useful in cases +where you want to patch a bug in a released version, but `dev` has unreleased +code in it already. + +**TBD: Insert diagram** + +1. Create a hot fix branch based off of `main`. + + ```bash + $ git checkout main + $ git checkout -b hotfix/documentation-broken-links + $ git push --set-upstream origin hotfix/documentation-broken-links + ``` + +2. Add a test case to validate the bug, fix the bug, and commit. + + ```bash + ... add test, fix bug, verify + $ git add -A . + $ git commit -m "Fix broken links" + $ git push + ``` + +3. Navigate to the project on [GitHub](https://github.com) and open a pull request + with the following branch settings: + * Base: `main` + * Compare: `hotfix/documentation-broken-links` + Paste your release checklist into the PR and work through the PR to get the + hotfix into production. + +4. At some point in the checklist you will merge the hotfix branch into `main`. + You can do this by using the "Merge pull request" button on the release PR. + +5. Now that the hotfix code is in `main` you are ready to create the actual + release. Navigate to the project page on GitHub and draft a new release with + the following settings: + * Tag version: `vX.Y.Z` + * Target: `main` + * Release title: `Release vX.Y.Z (hotfix)` + * Description: Includes a high-level list of things changed in this release. + +Click `Publish release`. + +*Note: Hotfix releases _are_ actual releases. You should bump at least the +patch part of the version when releasing a hotfix, and so even hotfixes go +through the process of creating a release like this.* + +1. Merge the `hotfix/documentation-broken-links` into `dev`. + + ```bash + $ git checkout dev + $ git merge hotfix/documentation-broken-links + $ git push + ``` + +2. Finish off the tasks in the release checklist. Once everything is done, close + the hotfix PR. + +## Code Style + +We maintain a consistent code style throughout the project, so please ensure your changes align with our existing style. +Take a look at the existing codebase to understand the patterns and conventions we follow. + +## Commit Messages + +The [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) specification is a +lightweight convention on top of commit messages. It provides an easy set of rules for creating an explicit +commit history, which makes it easier to write automated tools on top of. This convention dovetails with SemVer +by describing the features, fixes, and breaking changes made in commit messages. Please refer to the linked +documentation for examples and additional context. + +<type>[optional scope]: <description> + +[optional body] + +[optional footer(s)] + + +## Issue Tracking + +We utilize the issue tracking functionality of GitHub to manage our project's roadmap and track bugs or feature requests. +If you encounter any problems or have a new idea, please search the issues to ensure it hasn't already been reported. +If necessary, open a new issue providing a clear description, steps to reproduce, and any relevant information. + +We greatly appreciate your effort and contribution to our project! Let's build something awesome together! diff --git a/README.md b/README.md index 986ac2b..37b624f 100644 --- a/README.md +++ b/README.md @@ -7,13 +7,14 @@ Imagery Transmission Format (NITF) standards. ## Installation -The intent is to vend / distribute this software through a Python Package Index. If your environment has a distribution +The intent is to vend / distribute this software through a Python Package Index. +If your environment has a distribution, you should be able to install it using pip: ```shell pip install osml-imagery-toolkit[gdal] ``` -If you are working from a source code you can build and install the package from the root directory of the +If you are working from a source code, you can build and install the package from the root directory of the distribution. ```shell pip install .[gdal] @@ -22,32 +23,6 @@ Note that GDAL is listed as an extra dependency for this package. This is done t don't want to use GDAL or those that have their own custom installation steps for that library. Future versions of this package will include image IO backbones that have fewer dependencies. -## Linting/Formatting - -This package uses a number of tools to enforce formatting, linting, and general best practices: - -- [black](https://github.com/psf/black) -- [isort](https://github.com/PyCQA/isort) for formatting with a max line length of 100 -- [mypy](https://github.com/pre-commit/mirrors-mypy) to enforce static type checking -- [flake8](https://github.com/PyCQA/flake8) to check pep8 compliance and logical errors in code -- [autopep](https://github.com/pre-commit/mirrors-autopep8) to check pep8 compliance and logical errors in code -- [eslint](https://github.com/pre-commit/mirrors-eslint) to check pep8 compliance and logical errors in code -- [prettier](https://github.com/pre-commit/mirrors-prettier) to check pep8 compliance and logical errors in code -- [pre-commit](https://github.com/pre-commit/pre-commit-hooks) to install and control linters in githooks - -```bash -python3 -m pip install pre-commit -pre-commit install -``` - -Additionally, you can perform linting on all the files in the package by running: -```bash -pre-commit run --all-files --show-diff-on-failure -``` -or if using tox -```bash -tox -e lint -``` ## Documentation @@ -74,8 +49,10 @@ from aws.osml.photogrammetry import ImageCoordinate, GeodeticWorldCoordinate, Se ### Tiling with Updated Image Metadata Many applications break large remote sensing images into smaller chips or tiles for distributed processing or -dissemination. GDAL's Translate function provides basic capabilities but it does not correctly update geospatial -metadata to reflect the new image extent. These utilities provide those functions so tile consumers can correctly +dissemination. +GDAL's Translate function provides basic capabilities, but it does not correctly update geospatial +metadata to reflect the new image extent. +These utilities provide those functions so tile consumers can correctly interpret the pixel information they have been provided. ```python @@ -94,8 +71,10 @@ nitf_encoded_tile_bytes = tile_factory.create_encoded_tile([0, 0, 1024, 1024]) ### More Precise Sensor Models OversightML provides implementations of the Replacement Sensor Model (RSM) and Rational Polynomial Camera (RPC) sensor -models to assist in geo positioning. When loading a dataset you will automatically get the most accurate sensor model -from the available image metadata. That sensor model can be used in conjunction with an optional elevation model to +models to assist in geo positioning. +When loading a dataset, you will automatically get the most accurate sensor model +from the available image metadata. +That sensor model can be used in conjunction with an optional elevation model to convert between image and geodetic coordinates. ```python diff --git a/images/release-deployment.png b/images/release-deployment.png new file mode 100644 index 0000000000000000000000000000000000000000..65dcc2b3848359ff516be42dba8edc11ced862e5 GIT binary patch literal 79023 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 Date: Wed, 20 Sep 2023 10:19:04 -0400 Subject: [PATCH 06/11] fix: correct calculation of INCA R/Rdot projection (#14) --- .../osml/photogrammetry/sicd_sensor_model.py | 2 +- .../photogrammetry/test_sicd_sensor_model.py | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/aws/osml/photogrammetry/sicd_sensor_model.py b/src/aws/osml/photogrammetry/sicd_sensor_model.py index 5edb348..12decb7 100644 --- a/src/aws/osml/photogrammetry/sicd_sensor_model.py +++ b/src/aws/osml/photogrammetry/sicd_sensor_model.py @@ -484,7 +484,7 @@ def _grid_specific_projection(self, xrow_ycol, coa_time, arp_position, arp_veloc # (2 repeated in v1.3.0 of the spec) Compute the ARP velocity at the time of closest approach # and the magnitude of the vector. arp_velocity_ca_tgt = self.varp_poly(time_ca_tgt) - mag_arp_velocity_ca_tgt = np.sum(arp_velocity_ca_tgt, axis=-1) + mag_arp_velocity_ca_tgt = np.linalg.norm(arp_velocity_ca_tgt) # (3) Compute the Doppler Rate Scale Factor (drsf_tgt) for image grid location (rg, az). drsf_tgt = self.drate_sf_poly(rg, az) diff --git a/test/aws/osml/photogrammetry/test_sicd_sensor_model.py b/test/aws/osml/photogrammetry/test_sicd_sensor_model.py index f43cc2f..ad7fe65 100644 --- a/test/aws/osml/photogrammetry/test_sicd_sensor_model.py +++ b/test/aws/osml/photogrammetry/test_sicd_sensor_model.py @@ -159,6 +159,33 @@ def test_rgzero_inca(self): assert np.allclose(calculated_image_scp.coordinate, scp_pixel.coordinate) + for icp in sicd.geo_data.image_corners.icp: + geo_point = GeodeticWorldCoordinate([radians(icp.lon), radians(icp.lat), sicd.geo_data.scp.llh.hae]) + + if icp.index == sicd121.CornerStringType.FRFC_1: + image_point = ImageCoordinate([sicd.image_data.first_col, sicd.image_data.first_row]) + elif icp.index == sicd121.CornerStringType.FRLC_2: + image_point = ImageCoordinate( + [sicd.image_data.first_col + sicd.image_data.num_cols, sicd.image_data.first_row] + ) + elif icp.index == sicd121.CornerStringType.LRLC_3: + image_point = ImageCoordinate( + [ + sicd.image_data.first_col + sicd.image_data.num_cols, + sicd.image_data.first_row + sicd.image_data.num_rows, + ] + ) + elif icp.index == sicd121.CornerStringType.LRFC_4: + image_point = ImageCoordinate( + [sicd.image_data.first_col, sicd.image_data.first_row + sicd.image_data.num_rows] + ) + else: + # Unknown image corner + assert False + + new_geo_point = sicd_sensor_model.image_to_world(image_point) + assert np.allclose(new_geo_point.coordinate[0:2], geo_point.coordinate[0:2], atol=0.00001) + def test_rgazim_pfa(self): sicd: sicd121.SICD = XmlParser().from_path(Path("./test/data/sicd/example.sicd121.pfa.xml")) From 530342abf4ea1c5e62524f69b749f294f640cf41 Mon Sep 17 00:00:00 2001 From: edparris Date: Thu, 21 Sep 2023 11:59:54 -0400 Subject: [PATCH 07/11] feat: update DRA to work with SICDs (#15) --- src/aws/osml/gdal/dynamic_range_adjustment.py | 25 ++++++++++++++----- src/aws/osml/gdal/gdal_utils.py | 13 +++++++++- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/aws/osml/gdal/dynamic_range_adjustment.py b/src/aws/osml/gdal/dynamic_range_adjustment.py index 97b0602..6ac78f7 100644 --- a/src/aws/osml/gdal/dynamic_range_adjustment.py +++ b/src/aws/osml/gdal/dynamic_range_adjustment.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Optional class DRAParameters: @@ -25,12 +25,20 @@ def __init__( @staticmethod def from_counts( - counts: List[float], min_percentage: float = 0.02, max_percentage: float = 0.98, a: float = 0.2, b: float = 0.4 + counts: List[float], + first_bucket_value: Optional[float] = None, + last_bucket_value: Optional[float] = None, + min_percentage: float = 0.02, + max_percentage: float = 0.98, + a: float = 0.2, + b: float = 0.4, ) -> "DRAParameters": """ This static factory method computes a new set of DRA parameters given a histogram of pixel values. :param counts: histogram of the pixel values + :param first_bucket_value: pixel value of the first bucket, defaults to 0 + :param last_bucket_value: pixel value of the last bucket, defaults to bucket index :param min_percentage: set point for low intensity pixels that may be outliers :param max_percentage: set point for high intensity pixels that may be outliers :param a: weighting factor for the low intensity range @@ -38,6 +46,10 @@ def from_counts( :return: a set of DRA parameters containing recommended and actual ranges of values """ num_histogram_bins = len(counts) + if not first_bucket_value: + first_bucket_value = 0 + if not last_bucket_value: + last_bucket_value = num_histogram_bins # Find the first and last non-zero counts actual_min_value = 0 @@ -69,11 +81,12 @@ def from_counts( min_value = max([actual_min_value, e_min - a * (e_max - e_min)]) max_value = min([actual_max_value, e_max + b * (e_max - e_min)]) + value_step = (last_bucket_value - first_bucket_value) / num_histogram_bins return DRAParameters( - suggested_min_value=min_value, - suggested_max_value=max_value, - actual_min_value=actual_min_value, - actual_max_value=actual_max_value, + suggested_min_value=min_value * value_step + first_bucket_value, + suggested_max_value=max_value * value_step + first_bucket_value, + actual_min_value=actual_min_value * value_step + first_bucket_value, + actual_max_value=actual_max_value * value_step + first_bucket_value, ) def __repr__(self): diff --git a/src/aws/osml/gdal/gdal_utils.py b/src/aws/osml/gdal/gdal_utils.py index 2633691..178bf46 100644 --- a/src/aws/osml/gdal/gdal_utils.py +++ b/src/aws/osml/gdal/gdal_utils.py @@ -144,12 +144,23 @@ def get_type_and_scales( selected_min = min_value selected_max = max_value if range_adjustment is not RangeAdjustmentType.NONE: + # GetStatistics(1,1) means it is ok to approximate but force computation is stats not already available + stats = band.GetStatistics(1, 1) + min_value = stats[0] + max_value = stats[1] + num_buckets = int(max_value - min_value) if band_type == gdalconst.GDT_Float32 or band_type == gdalconst.GDT_Float64: num_buckets = 255 + dra = DRAParameters.from_counts( - band.GetHistogram(buckets=num_buckets, max=max_value, min=min_value, include_out_of_range=1, approx_ok=1) + counts=band.GetHistogram( + buckets=num_buckets, max=max_value, min=min_value, include_out_of_range=1, approx_ok=1 + ), + first_bucket_value=min_value, + last_bucket_value=max_value, ) + if range_adjustment == RangeAdjustmentType.DRA: selected_min = dra.suggested_min_value selected_max = dra.suggested_max_value From b979e1963f648b2f4ef5d40f0f31d51d0dc56b51 Mon Sep 17 00:00:00 2001 From: edparris Date: Mon, 25 Sep 2023 16:51:43 -0400 Subject: [PATCH 08/11] feat: enable RSM sensor model by default (#16) --- src/aws/osml/gdal/gdal_utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/aws/osml/gdal/gdal_utils.py b/src/aws/osml/gdal/gdal_utils.py index 178bf46..ecddab9 100644 --- a/src/aws/osml/gdal/gdal_utils.py +++ b/src/aws/osml/gdal/gdal_utils.py @@ -50,8 +50,7 @@ def load_gdal_dataset(image_path: str) -> Tuple[gdal.Dataset, Optional[SensorMod SensorModelTypes.PROJECTIVE, SensorModelTypes.RPC, SensorModelTypes.SICD, - # TODO: Enable RSM model once testing complete - # SensorModelTypes.RSM, + SensorModelTypes.RSM, ] # Create the best sensor model available sensor_model = SensorModelFactory( From 1b2902f166dd98aca4123c9e69af595f6b520e4c Mon Sep 17 00:00:00 2001 From: edparris Date: Tue, 26 Sep 2023 17:54:53 -0400 Subject: [PATCH 09/11] feat: gdal sensor model now supports non-wgs84 crs (#17) --- .../osml/gdal/gdal_sensor_model_builder.py | 6 ++- src/aws/osml/gdal/gdal_utils.py | 2 + src/aws/osml/gdal/sensor_model_factory.py | 5 ++- .../osml/photogrammetry/gdal_sensor_model.py | 39 ++++++++++++++++--- 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/aws/osml/gdal/gdal_sensor_model_builder.py b/src/aws/osml/gdal/gdal_sensor_model_builder.py index 72ff844..288c98e 100644 --- a/src/aws/osml/gdal/gdal_sensor_model_builder.py +++ b/src/aws/osml/gdal/gdal_sensor_model_builder.py @@ -13,16 +13,18 @@ class GDALAffineSensorModelBuilder(SensorModelBuilder): This builder is used to create sensor models for images that have GDAL geo transforms. """ - def __init__(self, geo_transform: List[float]) -> None: + def __init__(self, geo_transform: List[float], proj_wkt: Optional[str] = None) -> None: """ Constructor for the builder accepting the required GDAL geotransform. :param geo_transform: the geotransform for this image + :param proj_wkt: the well known text string of the CRS used by the image :return: None """ super().__init__() self.geo_transform = geo_transform + self.proj_wkt = proj_wkt def build(self) -> Optional[GDALAffineSensorModel]: """ @@ -32,7 +34,7 @@ def build(self) -> Optional[GDALAffineSensorModel]: """ if self.geo_transform is None: return None - return GDALAffineSensorModel(self.geo_transform) + return GDALAffineSensorModel(self.geo_transform, self.proj_wkt) class GDALGCPSensorModelBuilder(SensorModelBuilder): diff --git a/src/aws/osml/gdal/gdal_utils.py b/src/aws/osml/gdal/gdal_utils.py index ecddab9..e0da938 100644 --- a/src/aws/osml/gdal/gdal_utils.py +++ b/src/aws/osml/gdal/gdal_utils.py @@ -35,6 +35,7 @@ def load_gdal_dataset(image_path: str) -> Tuple[gdal.Dataset, Optional[SensorMod # Get a GDAL Geo Transform and any available GCPs geo_transform = ds.GetGeoTransform(can_return_null=True) ground_control_points = ds.GetGCPs() + proj_wkt = ds.GetProjection() # If this image has NITF TREs defined parse them parsed_tres = None @@ -59,6 +60,7 @@ def load_gdal_dataset(image_path: str) -> Tuple[gdal.Dataset, Optional[SensorMod xml_tres=parsed_tres, xml_dess=xml_dess, geo_transform=geo_transform, + proj_wkt=proj_wkt, ground_control_points=ground_control_points, selected_sensor_model_types=selected_sensor_model_types, ).build() diff --git a/src/aws/osml/gdal/sensor_model_factory.py b/src/aws/osml/gdal/sensor_model_factory.py index b969aa3..99cb28c 100644 --- a/src/aws/osml/gdal/sensor_model_factory.py +++ b/src/aws/osml/gdal/sensor_model_factory.py @@ -80,6 +80,7 @@ def __init__( xml_tres: Optional[ET.Element] = None, xml_dess: Optional[List[str]] = None, geo_transform: Optional[List[float]] = None, + proj_wkt: Optional[str] = None, ground_control_points: Optional[List[gdal.GCP]] = None, selected_sensor_model_types: Optional[List[SensorModelTypes]] = None, ) -> None: @@ -93,6 +94,7 @@ def __init__( :param xml_tres: XML representing metadata in the tagged record extensions(TRE) :param xml_dess: XML representing data contained in the data extension segments (DES) :param geo_transform: a GDAL affine transform + :param proj_wkt: the well known text string of the CRS used by the image :param ground_control_points: a list of GDAL GCPs that identify correspondences in the image :param selected_sensor_model_types: a list of sensor models that should be attempted by this factory @@ -105,6 +107,7 @@ def __init__( self.xml_tres = xml_tres self.xml_dess = xml_dess self.geo_transform = geo_transform + self.proj_wkt = proj_wkt self.ground_control_points = ground_control_points self.selected_sensor_model_types = selected_sensor_model_types @@ -121,7 +124,7 @@ def build(self) -> Optional[SensorModel]: if SensorModelTypes.AFFINE in self.selected_sensor_model_types: if self.geo_transform is not None: - approximate_sensor_model = GDALAffineSensorModelBuilder(self.geo_transform).build() + approximate_sensor_model = GDALAffineSensorModelBuilder(self.geo_transform, self.proj_wkt).build() if SensorModelTypes.PROJECTIVE in self.selected_sensor_model_types: if self.ground_control_points is not None and len(self.ground_control_points) > 3: diff --git a/src/aws/osml/photogrammetry/gdal_sensor_model.py b/src/aws/osml/photogrammetry/gdal_sensor_model.py index 2f7fdef..42f3f29 100644 --- a/src/aws/osml/photogrammetry/gdal_sensor_model.py +++ b/src/aws/osml/photogrammetry/gdal_sensor_model.py @@ -2,8 +2,10 @@ from typing import Any, Dict, List, Optional import numpy as np +import pyproj +from pyproj.enums import TransformDirection -from .coordinates import GeodeticWorldCoordinate, ImageCoordinate +from .coordinates import LLA_PROJ, GeodeticWorldCoordinate, ImageCoordinate from .elevation_model import ElevationModel from .sensor_model import SensorModel @@ -23,11 +25,12 @@ class GDALAffineSensorModel(SensorModel): The necessary transform matrix can be obtained from a dataset using the GetGeoTransform() operation. """ - def __init__(self, geo_transform: List) -> None: + def __init__(self, geo_transform: List, proj_wkt: Optional[str] = None) -> None: """ Construct the sensor model from the affine transform provided by transform :param geo_transform: the 6 coefficients of the affine transform + :param proj_wkt: the well known text string of the CRS used by the image :return: None """ @@ -46,6 +49,11 @@ def __init__(self, geo_transform: List) -> None: ) # Use NumPy to calculate an inverse transform self.inv_transform = np.linalg.inv(self.transform) + + self.image_to_wgs84 = None + if proj_wkt: + self.image_to_wgs84 = pyproj.Transformer.from_crs(pyproj.CRS.from_string(proj_wkt), LLA_PROJ.crs) + except np.linalg.LinAlgError: raise ValueError("GeoTransform can not be inverted. Not a valid matrix for a sensor model.") @@ -67,7 +75,17 @@ def image_to_world( """ # The transform is expecting coordinates [x, y, 1.0] as an input. augmented_image_coord = np.append(image_coordinate.coordinate, [1.0]) - lonlat_coordinate = np.matmul(self.transform, augmented_image_coord) + image_crs_coordinate = np.matmul(self.transform, augmented_image_coord) + if self.image_to_wgs84 is not None: + lonlat_coordinate = self.image_to_wgs84.transform( + image_crs_coordinate[0], + image_crs_coordinate[1], + image_crs_coordinate[2], + radians=False, + direction=TransformDirection.FORWARD, + ) + else: + lonlat_coordinate = image_crs_coordinate world_coordinate = GeodeticWorldCoordinate([radians(lonlat_coordinate[0]), radians(lonlat_coordinate[1]), 0.0]) if elevation_model: elevation_model.set_elevation(world_coordinate) @@ -85,6 +103,17 @@ def world_to_image(self, world_coordinate: GeodeticWorldCoordinate) -> ImageCoor """ # The GDAL geo transform does not support elevation data. The inverse transform was created assuming the input # coordinate is a 2D geo + 1.0 (i.e. [longitude, latitude, 1.0] - lonlat_degrees_coordinate = np.array([degrees(world_coordinate.longitude), degrees(world_coordinate.latitude), 1.0]) - xy_coordinate = np.matmul(self.inv_transform, lonlat_degrees_coordinate) + if self.image_to_wgs84 is not None: + image_crs_coordinate = np.array( + self.image_to_wgs84.transform( + degrees(world_coordinate.longitude), + degrees(world_coordinate.latitude), + 1.0, + radians=False, + direction=TransformDirection.INVERSE, + ) + ) + else: + image_crs_coordinate = np.array((degrees(world_coordinate.longitude), degrees(world_coordinate.latitude), 1.0)) + xy_coordinate = np.matmul(self.inv_transform, image_crs_coordinate) return ImageCoordinate([xy_coordinate[0], xy_coordinate[1]]) From 3bf2df2fc8d16c4930e568b8b4a018a3dc18e139 Mon Sep 17 00:00:00 2001 From: edparris Date: Wed, 27 Sep 2023 17:23:07 -0400 Subject: [PATCH 10/11] docs: update readme for SAR features (#18) --- README.md | 65 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 37b624f..59ee09d 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # OversightML Imagery Toolkit -The OversightML Imagery Core is a Python package that contains image processing and photogrammetry routines commonly +The OversightML Imagery Toolkit is a Python package that contains image processing and photogrammetry routines commonly used during the analysis of imagery collected by satellites and unmanned aerial vehicles (UAVs). It builds upon GDAL -by providing additional support for images compliant with the Sensor Independent Complex Data (SICD) and National -Imagery Transmission Format (NITF) standards. +by providing additional support for images compliant with the National Imagery Transmission Format (NITF) and Sensor +Independent Complex Data (SICD) standards. ## Installation @@ -35,10 +35,11 @@ tox -e docs ## Example Usage -This library contains three core packages under the `aws.osml` namespace. +This library contains four core packages under the `aws.osml` namespace. * photogrammetry: convert locations between the image (x, y) and geodetic (lon, lat, elev) coordinate systems * gdal: help load and manage datasets loaded by GDAL * image_processing: common image manipulation routines +* formats: utilities for handling format specific information; normally not accessed directly ```python from aws.osml.gdal import GDALImageFormats, GDALCompressionOptions, load_gdal_dataset @@ -49,11 +50,10 @@ from aws.osml.photogrammetry import ImageCoordinate, GeodeticWorldCoordinate, Se ### Tiling with Updated Image Metadata Many applications break large remote sensing images into smaller chips or tiles for distributed processing or -dissemination. -GDAL's Translate function provides basic capabilities, but it does not correctly update geospatial -metadata to reflect the new image extent. -These utilities provide those functions so tile consumers can correctly -interpret the pixel information they have been provided. +dissemination. GDAL's Translate function provides basic capabilities, but it does not correctly update geospatial +metadata to reflect the new image extent. These utilities provide those functions so tile consumers can correctly +interpret the pixel information they have been provided. For NITF imagery that includes the addition of a new ICHIPB +TRE. With SICD the XML ImageData elements are adjusted to identify the sub-image bounds. ```python # Load the image and create a sensor model @@ -68,29 +68,50 @@ tile_factory = GDALTileFactory(ds, nitf_encoded_tile_bytes = tile_factory.create_encoded_tile([0, 0, 1024, 1024]) ``` +### Tiling for Display + +Some images, for example 11-bit panchromatic images or SAR imagery with floating point complex data, can not be +displayed directly without remapping the pixels into an 8-bit per pixel grayscale or RGB color model. The TileFactory +supports creation of tiles suitable for human review by setting both the output_type and range_adjustment options. + +```python +viz_tile_factory = GDALTileFactory(ds, + sensor_model, + GDALImageFormats.PNG, + GDALCompressionOptions.NONE, + output_type=gdalconst.GDT_Byte, + range_adjustment=RangeAdjustmentType.DRA) + +viz_tile = viz_tile_factory.create_encoded_tile([0, 0, 1024, 1024]) +``` + ### More Precise Sensor Models -OversightML provides implementations of the Replacement Sensor Model (RSM) and Rational Polynomial Camera (RPC) sensor -models to assist in geo positioning. -When loading a dataset, you will automatically get the most accurate sensor model -from the available image metadata. -That sensor model can be used in conjunction with an optional elevation model to -convert between image and geodetic coordinates. +OversightML provides implementations of the Replacement Sensor Model (RSM), Rational Polynomial +Camera (RPC), and Sensor Independent Complex Data (SICD) sensor models to assist in geo positioning. +When loading a dataset, the toolkit will construct the most accurate sensor model +from the available image metadata. That sensor model can be used in conjunction with an optional +elevation model to convert between image and geodetic coordinates. ```python ds, sensor_model = load_gdal_dataset("./imagery/sample.nitf") -elevation_model = DigitalElevationModel(SRTMTileSet(version="1arc_v3"), - GDALDigitalElevationModelTileFactory("./local-SRTM-tiles") - ) +elevation_model = DigitalElevationModel( + SRTMTileSet(version="1arc_v3"), + GDALDigitalElevationModelTileFactory("./local-SRTM-tiles")) -geodetic_location_of_ul_corner = sensor_model.image_to_world(ImageCoordinate([0, 0]), elevation_model=elevation_model) +# Note the order of ImageCoordinate is (x, y) +geodetic_location_of_ul_corner = sensor_model.image_to_world( + ImageCoordinate([0, 0]), + elevation_model=elevation_model) lon_degrees = -77.404453 lat_degrees = 38.954831 meters_above_ellipsoid = 100.0 -image_location = sensor_model.world_to_image(GeodeticWorldCoordinate([radians(lon_degrees), - radians(lat_degrees), - meters_above_ellipsoid])) + +image_location = sensor_model.world_to_image( + GeodeticWorldCoordinate([radians(lon_degrees), + radians(lat_degrees), + meters_above_ellipsoid])) ``` ## Contributing From b3169e7145a82612b4c58383d9537b1a7717f108 Mon Sep 17 00:00:00 2001 From: edparris Date: Wed, 27 Sep 2023 18:00:29 -0400 Subject: [PATCH 11/11] build: bump version for v1.1.0 --- README.md | 16 ++++++++-------- setup.cfg | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 59ee09d..5f159aa 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ The OversightML Imagery Toolkit is a Python package that contains image processing and photogrammetry routines commonly used during the analysis of imagery collected by satellites and unmanned aerial vehicles (UAVs). It builds upon GDAL -by providing additional support for images compliant with the National Imagery Transmission Format (NITF) and Sensor +by providing additional support for images compliant with the National Imagery Transmission Format (NITF) and Sensor Independent Complex Data (SICD) standards. ## Installation @@ -70,14 +70,14 @@ nitf_encoded_tile_bytes = tile_factory.create_encoded_tile([0, 0, 1024, 1024]) ### Tiling for Display -Some images, for example 11-bit panchromatic images or SAR imagery with floating point complex data, can not be +Some images, for example 11-bit panchromatic images or SAR imagery with floating point complex data, can not be displayed directly without remapping the pixels into an 8-bit per pixel grayscale or RGB color model. The TileFactory supports creation of tiles suitable for human review by setting both the output_type and range_adjustment options. ```python -viz_tile_factory = GDALTileFactory(ds, - sensor_model, - GDALImageFormats.PNG, +viz_tile_factory = GDALTileFactory(ds, + sensor_model, + GDALImageFormats.PNG, GDALCompressionOptions.NONE, output_type=gdalconst.GDT_Byte, range_adjustment=RangeAdjustmentType.DRA) @@ -87,10 +87,10 @@ viz_tile = viz_tile_factory.create_encoded_tile([0, 0, 1024, 1024]) ### More Precise Sensor Models -OversightML provides implementations of the Replacement Sensor Model (RSM), Rational Polynomial +OversightML provides implementations of the Replacement Sensor Model (RSM), Rational Polynomial Camera (RPC), and Sensor Independent Complex Data (SICD) sensor models to assist in geo positioning. When loading a dataset, the toolkit will construct the most accurate sensor model -from the available image metadata. That sensor model can be used in conjunction with an optional +from the available image metadata. That sensor model can be used in conjunction with an optional elevation model to convert between image and geodetic coordinates. ```python @@ -101,7 +101,7 @@ elevation_model = DigitalElevationModel( # Note the order of ImageCoordinate is (x, y) geodetic_location_of_ul_corner = sensor_model.image_to_world( - ImageCoordinate([0, 0]), + ImageCoordinate([0, 0]), elevation_model=elevation_model) lon_degrees = -77.404453 diff --git a/setup.cfg b/setup.cfg index cb2cca5..5a6b63a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,11 +1,11 @@ [metadata] name = osml-imagery-toolkit -version = 1.0.0 +version = 1.1.0 description = Toolkit to work with imagery collected by satellites and UAVs long_description = file: README.md long_description_content_type = text/markdown author = Amazon Web Services -author_email = todo-public-library-poc@amazon.com +author_email = aws-osml-admin@amazon.com license = MIT No Attribution