class RcvParametersType(Serializable): """ The receive parameters geometry implementation. """ _fields = ('RcvTime', 'RcvPos', 'RcvVel', 'SideOfTrack', 'SlantRange', 'GroundRange', 'DopplerConeAngle', 'GrazeAngle', 'IncidenceAngle', 'AzimuthAngle') _required = _fields _numeric_format = { 'SlantRange': FLOAT_FORMAT, 'GroundRange': FLOAT_FORMAT, 'DopplerConeAngle': FLOAT_FORMAT, 'GrazeAngle': FLOAT_FORMAT, 'IncidenceAngle': FLOAT_FORMAT, 'AzimuthAngle': FLOAT_FORMAT } # descriptors RcvTime = FloatDescriptor( 'RcvTime', _required, strict=DEFAULT_STRICT, docstring='Receive time for the first sample for the reference vector.' ) # type: float RcvPos = SerializableDescriptor( 'RcvPos', XYZType, _required, strict=DEFAULT_STRICT, docstring='APC position in ECF coordinates.') # type: XYZType RcvVel = SerializableDescriptor( 'RcvVel', XYZType, _required, strict=DEFAULT_STRICT, docstring='APC velocity in ECF coordinates.') # type: XYZType SideOfTrack = StringEnumDescriptor( 'SideOfTrack', ('L', 'R'), _required, strict=DEFAULT_STRICT, docstring='Side of Track parameter for the collection.') # type: str SlantRange = FloatDescriptor( 'SlantRange', _required, strict=DEFAULT_STRICT, bounds=(0, None), docstring='Slant range from the APC to the CRP.') # type: float GroundRange = FloatDescriptor( 'GroundRange', _required, strict=DEFAULT_STRICT, bounds=(0, None), docstring='Ground range from the APC nadir to the CRP.') # type: float DopplerConeAngle = FloatDescriptor( 'DopplerConeAngle', _required, strict=DEFAULT_STRICT, bounds=(0, 180), docstring='Doppler Cone Angle between APC velocity and deg CRP Line of ' 'Sight (LOS).') # type: float GrazeAngle = FloatDescriptor( 'GrazeAngle', _required, strict=DEFAULT_STRICT, bounds=(0, 90), docstring='Grazing angle for the APC to CRP LOS and the Earth Tangent ' 'Plane (ETP) at the CRP.') # type: float IncidenceAngle = FloatDescriptor( 'IncidenceAngle', _required, strict=DEFAULT_STRICT, bounds=(0, 90), docstring='Incidence angle for the APC to CRP LOS and the Earth Tangent ' 'Plane (ETP) at the CRP.') # type: float AzimuthAngle = FloatDescriptor( 'AzimuthAngle', _required, strict=DEFAULT_STRICT, bounds=(0, 360), docstring='Angle from north to the line from the CRP to the APC ETP ' 'Nadir (i.e. North to +GPX). Measured clockwise from North ' 'toward East.') # type: float def __init__(self, RcvTime=None, RcvPos=None, RcvVel=None, SideOfTrack=None, SlantRange=None, GroundRange=None, DopplerConeAngle=None, GrazeAngle=None, IncidenceAngle=None, AzimuthAngle=None, **kwargs): if '_xml_ns' in kwargs: self._xml_ns = kwargs['_xml_ns'] if '_xml_ns_key' in kwargs: self._xml_ns_key = kwargs['_xml_ns_key'] self.RcvTime = RcvTime self.RcvPos = RcvPos self.RcvVel = RcvVel self.SideOfTrack = SideOfTrack self.SlantRange = SlantRange self.GroundRange = GroundRange self.DopplerConeAngle = DopplerConeAngle self.GrazeAngle = GrazeAngle self.IncidenceAngle = IncidenceAngle self.AzimuthAngle = AzimuthAngle super(RcvParametersType, self).__init__(**kwargs) @property def look(self): """ int: An integer version of `SideOfTrack`: * None if `SideOfTrack` is not defined * -1 if SideOfTrack == 'R' * 1 if SideOftrack == 'L' """ if self.SideOfTrack is None: return None return -1 if self.SideOfTrack == 'R' else 1
class ResearchType(Serializable): _fields = ('MetadataVersion', 'DetailCollectionInfo', 'DetailSubCollectionInfo', 'DetailImageInfo', 'DetailSensorInfo', 'DetailFiducialInfo', 'DetailObjectInfo') _required = ('MetadataVersion', ) # descriptors MetadataVersion = StringDescriptor( 'MetadataVersion', _required, docstring='The version number') # type: str DetailCollectionInfo = SerializableDescriptor( 'DetailCollectionInfo', CollectionInfoType, _required, docstring='High level information about the data collection' ) # type: Optional[CollectionInfoType] DetailSubCollectionInfo = SerializableDescriptor( 'DetailSubCollectionInfo', SubCollectionInfoType, _required, docstring='Information about sub-division of the overall data collection' ) # type: Optional[SubCollectionInfoType] DetailImageInfo = SerializableDescriptor( 'DetailImageInfo', ImageInfoType, _required, docstring='Information about the referenced image' ) # type: Optional[ImageInfoType] DetailSensorInfo = SerializableDescriptor( 'DetailSensorInfo', SensorInfoType, _required, docstring='Information about the sensor' ) # type: Optional[SensorInfoType] DetailFiducialInfo = SerializableDescriptor( 'DetailFiducialInfo', FiducialInfoType, _required, docstring='Information about the ground-truthed fiducials' ) # type: Optional[FiducialInfoType] DetailObjectInfo = SerializableDescriptor( 'DetailObjectInfo', ObjectInfoType, _required, docstring='Information about the ground-truthed objects' ) # type: Optional[ObjectInfoType] def __init__(self, MetadataVersion='Unknown', DetailCollectionInfo=None, DetailSubCollectionInfo=None, DetailImageInfo=None, DetailSensorInfo=None, DetailFiducialInfo=None, DetailObjectInfo=None, **kwargs): """ Parameters ---------- MetadataVersion : str DetailCollectionInfo : CollectionInfoType DetailSubCollectionInfo : SubCollectionInfoType DetailImageInfo : ImageInfoType DetailSensorInfo : SensorInfo DetailFiducialInfo : FiducialInfoType DetailObjectInfo : ObjectInfoType kwargs Other keyword arguments """ if '_xml_ns' in kwargs: self._xml_ns = kwargs['_xml_ns'] if '_xml_ns_key' in kwargs: self._xml_ns_key = kwargs['_xml_ns_key'] self.MetadataVersion = MetadataVersion self.DetailCollectionInfo = DetailCollectionInfo self.DetailSubCollectionInfo = DetailSubCollectionInfo self.DetailImageInfo = DetailImageInfo self.DetailSensorInfo = DetailSensorInfo self.DetailFiducialInfo = DetailFiducialInfo self.DetailObjectInfo = DetailObjectInfo super(ResearchType, self).__init__(**kwargs) def to_xml_bytes(self, urn=None, tag='RESEARCH', check_validity=False, strict=DEFAULT_STRICT): if urn is None: urn = _AFRL_SPECIFICATION_NAMESPACE return super(ResearchType, self).to_xml_bytes(urn=urn, tag=tag, check_validity=check_validity, strict=strict) def to_xml_string(self, urn=None, tag='RESEARCH', check_validity=False, strict=DEFAULT_STRICT): return self.to_xml_bytes(urn=urn, tag=tag, check_validity=check_validity, strict=strict).decode('utf-8') def apply_sicd(self, sicd, base_file_name, populate_in_periphery=False, include_out_of_range=False, padding_fraction=0.05, minimum_pad=0): """ Apply the given sicd to define all the relevant derived data, assuming that the starting point is physical ground truth populated, and image details and locations will be inferred. This modifies the structure in place. Parameters ---------- sicd : SICDType base_file_name : str populate_in_periphery : bool include_out_of_range : bool padding_fraction : None|float minimum_pad : int|float """ # assume that collection info and subcollection info are previously defined # define the image info if self.DetailImageInfo is not None: raise ValueError('Image Info is already defined') self.DetailImageInfo = ImageInfoType.from_sicd(sicd, base_file_name) # define sensor info if self.DetailSensorInfo is not None: raise ValueError('Sensor Info is already defined') self.DetailSensorInfo = SensorInfoType.from_sicd(sicd) if self.DetailFiducialInfo is None: self.DetailFiducialInfo = FiducialInfoType( NumberOfFiducialsInImage=0, NumberOfFiducialsInScene=0) else: self.DetailFiducialInfo.set_image_location_from_sicd( sicd, populate_in_periphery=populate_in_periphery, include_out_of_range=include_out_of_range) if self.DetailObjectInfo is None: self.DetailObjectInfo = ObjectInfoType(NumberOfObjectsInImage=0, NumberOfObjectsInScene=0) else: self.DetailObjectInfo.set_image_location_from_sicd( sicd, layover_shift=True, populate_in_periphery=populate_in_periphery, include_out_of_range=include_out_of_range, padding_fraction=padding_fraction, minimum_pad=minimum_pad) def apply_sicd_reader(self, sicd_reader, populate_in_periphery=False, include_out_of_range=False, padding_fraction=0.05, minimum_pad=0): """ Apply the given sicd to define all the relevant derived data, assuming that the starting point is physical ground truth populated, and image details and locations will be inferred. This modifies the structure in place. Parameters ---------- sicd_reader : SICDReader populate_in_periphery : bool include_out_of_range : bool padding_fraction : None|float minimum_pad : int|float """ base_file = os.path.split(sicd_reader.file_name)[1] self.apply_sicd(sicd_reader.sicd_meta, base_file, populate_in_periphery=populate_in_periphery, include_out_of_range=include_out_of_range, padding_fraction=padding_fraction, minimum_pad=minimum_pad)
class CPHDType(Serializable): """ """ _fields = ('CollectionInfo', 'Data', 'Global', 'Channel', 'SRP', 'RadarCollection', 'Antenna', 'VectorParameters') _required = ('CollectionInfo', 'Data', 'Global', 'Channel', 'SRP', 'VectorParameters') # descriptors CollectionInfo = SerializableDescriptor( 'CollectionInfo', CollectionInfoType, _required, strict=DEFAULT_STRICT, docstring='General information about the collection.' ) # type: CollectionInfoType Data = SerializableDescriptor( 'Data', DataType, _required, strict=DEFAULT_STRICT, docstring= 'Parameters that describe binary data components contained in the ' 'product.') # type: DataType Global = SerializableDescriptor( 'Global', GlobalType, _required, strict=DEFAULT_STRICT, docstring='Global parameters that apply to metadata components and CPHD ' 'signal arrays.') # type: GlobalType Channel = SerializableDescriptor( 'Channel', ChannelType, _required, strict=DEFAULT_STRICT, docstring='Channel specific parameters for CPHD channels.' ) # type: ChannelType SRP = SerializableDescriptor( 'SRP', SRPTyp, _required, strict=DEFAULT_STRICT, docstring='The Stabilization Reference Point (SRP) parameters.' ) # type: SRPTyp RadarCollection = SerializableDescriptor( 'RadarCollection', RadarCollectionType, _required, strict=DEFAULT_STRICT, docstring='') # type: Union[None, RadarCollectionType] Antenna = SerializableDescriptor( 'Antenna', AntennaType, _required, strict=DEFAULT_STRICT, docstring= 'Antenna parameters that describe antenna orientation, mainlobe ' 'steering and gain patterns vs. ' 'time.') # type: Union[None, AntennaType] VectorParameters = SerializableDescriptor( 'VectorParameters', VectorParametersType, _required, strict=DEFAULT_STRICT, docstring='Structure specifying the Vector parameters provided for ' 'each channel of a given product.') # type: VectorParametersType def __init__(self, CollectionInfo=None, Data=None, Global=None, Channel=None, SRP=None, RadarCollection=None, Antenna=None, VectorParameters=None, **kwargs): """ Parameters ---------- CollectionInfo : CollectionInfoType Data : DataType Global : GlobalType Channel : ChannelType SRP : SRPTyp RadarCollection : None|RadarCollectionType Antenna : None|AntennaType VectorParameters : VectorParametersType kwargs """ if '_xml_ns' in kwargs: self._xml_ns = kwargs['_xml_ns'] if '_xml_ns_key' in kwargs: self._xml_ns_key = kwargs['_xml_ns_key'] self.CollectionInfo = CollectionInfo self.Data = Data self.Global = Global self.Channel = Channel self.SRP = SRP self.RadarCollection = RadarCollection self.Antenna = Antenna self.VectorParameters = VectorParameters super(CPHDType, self).__init__(**kwargs) def to_xml_bytes(self, urn=None, tag='CPHD', check_validity=False, strict=DEFAULT_STRICT): if urn is None: urn = _CPHD_SPECIFICATION_NAMESPACE return super(CPHDType, self).to_xml_bytes(urn=urn, tag=tag, check_validity=check_validity, strict=strict) def to_xml_string(self, urn=None, tag='CPHD', check_validity=False, strict=DEFAULT_STRICT): return self.to_xml_bytes(urn=urn, tag=tag, check_validity=check_validity, strict=strict).decode('utf-8') def get_pvp_dtype(self): """ Gets the dtype for the corresponding PVP structured array. Note that they must all have homogeneous dtype. Returns ------- numpy.dtype This will be a compound dtype for a structured array. """ if self.VectorParameters is None: raise ValueError('No VectorParameters defined.') return self.VectorParameters.get_vector_dtype() @classmethod def from_xml_file(cls, file_path): """ Construct the cphd object from a stand-alone xml file path. Parameters ---------- file_path : str Returns ------- CPHDType """ root_node, xml_ns = parse_xml_from_file(file_path) ns_key = 'default' if 'default' in xml_ns else None return cls.from_node(root_node, xml_ns=xml_ns, ns_key=ns_key) @classmethod def from_xml_string(cls, xml_string): """ Construct the cphd object from an xml string. Parameters ---------- xml_string : str|bytes Returns ------- CPHDType """ root_node, xml_ns = parse_xml_from_string(xml_string) ns_key = 'default' if 'default' in xml_ns else None return cls.from_node(root_node, xml_ns=xml_ns, ns_key=ns_key)
class EBType(Serializable): """ Electrical boresight (EB) steering directions for an electronically steered array. """ _fields = ('DCXPoly', 'DCYPoly') _required = _fields # descriptors DCXPoly = SerializableDescriptor( 'DCXPoly', Poly1DType, _required, strict=DEFAULT_STRICT, docstring= 'Electrical boresight steering *X-axis direction cosine (DCX)* as a function of ' 'slow time ``(variable 1)``.') # type: Poly1DType DCYPoly = SerializableDescriptor( 'DCYPoly', Poly1DType, _required, strict=DEFAULT_STRICT, docstring= 'Electrical boresight steering *Y-axis direction cosine (DCY)* as a function of ' 'slow time ``(variable 1)``.') # type: Poly1DType def __init__(self, DCXPoly=None, DCYPoly=None, **kwargs): """ Parameters ---------- DCXPoly : Poly1DType|numpy.ndarray|list|tuple DCYPoly : Poly1DType|numpy.ndarray|list|tuple kwargs : dict """ if '_xml_ns' in kwargs: self._xml_ns = kwargs['_xml_ns'] if '_xml_ns_key' in kwargs: self._xml_ns_key = kwargs['_xml_ns_key'] self.DCXPoly = DCXPoly self.DCYPoly = DCYPoly super(EBType, self).__init__(**kwargs) def __call__(self, t): """ Evaluate the polynomial at points `t`. This passes `t` straight through to :func:`polyval` of `numpy.polynomial.polynomial` for each of `DCXPoly,DCYPoly` components. If any of `DCXPoly,DCYPoly` is not populated, then `None` is returned. Parameters ---------- t : float|int|numpy.ndarray The point(s) at which to evaluate. Returns ------- None|numpy.ndarray """ if self.DCXPoly is None or self.DCYPoly is None: return None return numpy.array([self.DCXPoly(t), self.DCYPoly(t)])
class AntParamType(Serializable): """ The antenna parameters container. """ _fields = ('XAxisPoly', 'YAxisPoly', 'FreqZero', 'EB', 'Array', 'Elem', 'GainBSPoly', 'EBFreqShift', 'MLFreqDilation') _required = ('XAxisPoly', 'YAxisPoly', 'FreqZero', 'Array') _numeric_format = {'FreqZero': '0.16G'} # descriptors XAxisPoly = SerializableDescriptor( 'XAxisPoly', XYZPolyType, _required, strict=DEFAULT_STRICT, docstring= 'Antenna X-Axis unit vector in ECF coordinates as a function of time ``(variable 1)``.' ) # type: XYZPolyType YAxisPoly = SerializableDescriptor( 'YAxisPoly', XYZPolyType, _required, strict=DEFAULT_STRICT, docstring= 'Antenna Y-Axis unit vector in ECF coordinates as a function of time ``(variable 1)``.' ) # type: XYZPolyType FreqZero = FloatDescriptor( 'FreqZero', _required, strict=DEFAULT_STRICT, docstring= 'RF frequency *(f0)* used to specify the array pattern and electrical boresite *(EB)* ' 'steering direction cosines.') # type: float EB = SerializableDescriptor( 'EB', EBType, _required, strict=DEFAULT_STRICT, docstring= 'Electrical boresight *(EB)* steering directions for an electronically steered array.' ) # type: EBType Array = SerializableDescriptor( 'Array', GainPhasePolyType, _required, strict=DEFAULT_STRICT, docstring= 'Array pattern polynomials that define the shape of the main-lobe.' ) # type: GainPhasePolyType Elem = SerializableDescriptor( 'Elem', GainPhasePolyType, _required, strict=DEFAULT_STRICT, docstring= 'Element array pattern polynomials for electronically steered arrays.' ) # type: GainPhasePolyType GainBSPoly = SerializableDescriptor( 'GainBSPoly', Poly1DType, _required, strict=DEFAULT_STRICT, docstring= 'Gain polynomial *(in dB)* as a function of frequency for boresight *(BS)* at :math:`DCX=0, DCY=0`. ' 'Frequency ratio :math:`(f-f0)/f0` is the input variable ``(variable 1)``, and the constant ' 'coefficient is always `0.0`.') # type: Poly1DType EBFreqShift = BooleanDescriptor('EBFreqShift', _required, strict=DEFAULT_STRICT, docstring=""" Parameter indicating whether the electronic boresite shifts with frequency for an electronically steered array. * `False` - No shift with frequency. * `True` - Shift with frequency per ideal array theory. """) # type: bool MLFreqDilation = BooleanDescriptor('MLFreqDilation', _required, strict=DEFAULT_STRICT, docstring=""" Parameter indicating the mainlobe (ML) width changes with frequency. * `False` - No change with frequency. * `True` - Change with frequency per ideal array theory. """) # type: bool def __init__(self, XAxisPoly=None, YAxisPoly=None, FreqZero=None, EB=None, Array=None, Elem=None, GainBSPoly=None, EBFreqShift=None, MLFreqDilation=None, **kwargs): """ Parameters ---------- XAxisPoly : XYZPolyType YAxisPoly : XYZPolyType FreqZero : float EB : EBType Array : GainPhasePolyType Elem : GainPhasePolyType GainBSPoly : Poly1DType|numpy.ndarray|list|tuple EBFreqShift : bool MLFreqDilation : bool kwargs : dict """ if '_xml_ns' in kwargs: self._xml_ns = kwargs['_xml_ns'] if '_xml_ns_key' in kwargs: self._xml_ns_key = kwargs['_xml_ns_key'] self.XAxisPoly, self.YAxisPoly = XAxisPoly, YAxisPoly self.FreqZero = FreqZero self.EB = EB self.Array, self.Elem = Array, Elem self.GainBSPoly = GainBSPoly self.EBFreqShift, self.MLFreqDilation = EBFreqShift, MLFreqDilation super(AntParamType, self).__init__(**kwargs) def _apply_reference_frequency(self, reference_frequency): if self.FreqZero is not None: self.FreqZero += reference_frequency
class ExploitationFeaturesCollectionInformationType(Serializable): """ General collection information. """ _fields = ('SensorName', 'RadarMode', 'CollectionDateTime', 'LocalDateTime', 'CollectionDuration', 'Resolution', 'InputROI', 'Polarizations') _required = ('SensorName', 'RadarMode', 'CollectionDateTime', 'CollectionDuration') _collections_tags = { 'Polarizations': { 'array': False, 'child_tag': 'Polarization' } } _numeric_format = {'CollectionDuration': '0.16G'} # Descriptor SensorName = StringDescriptor('SensorName', _required, strict=DEFAULT_STRICT, docstring='The name of the sensor.') # str RadarMode = SerializableDescriptor( 'RadarMode', RadarModeType, _required, strict=DEFAULT_STRICT, docstring='Radar collection mode.') # type: RadarModeType CollectionDateTime = DateTimeDescriptor( 'CollectionDateTime', _required, strict=DEFAULT_STRICT, docstring= 'Collection date and time defined in Coordinated Universal Time (UTC). The seconds ' 'should be followed by a Z to indicate UTC.') # type: numpy.datetime64 CollectionDuration = FloatDescriptor( 'CollectionDuration', _required, strict=DEFAULT_STRICT, docstring='The duration of the collection (units = seconds).' ) # type: float Resolution = SerializableDescriptor( 'Resolution', RangeAzimuthType, _required, strict=DEFAULT_STRICT, docstring= 'Uniformly-weighted resolution (range and azimuth) processed in ' 'the slant plane.') # type: Union[None, RangeAzimuthType] InputROI = SerializableDescriptor( 'InputROI', InputROIType, _required, strict=DEFAULT_STRICT, docstring='ROI representing portion of input data used to make ' 'this product.') # type: Union[None, InputROIType] Polarizations = SerializableListDescriptor( 'Polarizations', TxRcvPolarizationType, _collections_tags, _required, strict=DEFAULT_STRICT, docstring='Transmit and receive polarization(s).' ) # type: Union[None, List[TxRcvPolarizationType]] def __init__(self, SensorName=None, RadarMode=None, CollectionDateTime=None, LocalDateTime=None, CollectionDuration=None, Resolution=None, Polarizations=None, **kwargs): """ Parameters ---------- SensorName : str RadarMode : RadarModeType CollectionDateTime : numpy.datetime64|datetime.datetime|datetime.date|str LocalDateTime : None|str|datetime.datetime CollectionDuration : float Resolution : None|RangeAzimuthType|numpy.ndarray|list|tuple Polarizations : None|List[TxRcvPolarizationType] kwargs """ self._local_date_time = None if '_xml_ns' in kwargs: self._xml_ns = kwargs['_xml_ns'] if '_xml_ns_key' in kwargs: self._xml_ns_key = kwargs['_xml_ns_key'] self.SensorName = SensorName self.RadarMode = RadarMode self.CollectionDateTime = CollectionDateTime self.CollectionDuration = CollectionDuration self.LocalDateTime = LocalDateTime self.Resolution = Resolution self.Polarizations = Polarizations super(ExploitationFeaturesCollectionInformationType, self).__init__(**kwargs) @property def LocalDateTime(self): """None|str: The local date/time string of the collection. *Optional.*""" return self._local_date_time @LocalDateTime.setter def LocalDateTime( self, value): # type: (Union[None, str, datetime.datetime]) -> None if value is None: self._local_date_time = None return elif isinstance(value, datetime.datetime): value = value.isoformat('T') if isinstance(value, string_types): self._local_date_time = value else: logger.error( 'Attribute LocalDateTime of class ExploitationFeaturesCollectionInformationType\n\t' 'requires a datetime.datetime or string. Got unsupported type {}.\n\t' 'Setting value to None.'.format(type(value))) self._local_date_time = None @classmethod def from_sicd(cls, sicd): """ Construct from a sicd element. Parameters ---------- sicd : SICDType Returns ------- ExploitationFeaturesCollectionInformationType """ if not isinstance(sicd, SICDType): raise TypeError('Requires SICDType instance, got type {}'.format( type(sicd))) polarizations = [ TxRcvPolarizationType.from_sicd_value(entry.TxRcvPolarization) for entry in sicd.RadarCollection.RcvChannels ] return cls( SensorName=sicd.CollectionInfo.CollectorName, RadarMode=RadarModeType(**sicd.CollectionInfo.RadarMode.to_dict()), CollectionDateTime=sicd.Timeline.CollectStart, CollectionDuration=sicd.Timeline.CollectDuration, Resolution=(sicd.Grid.Row.SS, sicd.Grid.Col.SS), Polarizations=polarizations)
class CollectionType(Serializable): """ Metadata regarding one of the input collections. """ _fields = ('Information', 'Geometry', 'Phenomenology', 'identifier') _required = ('Information', 'identifier') _set_as_attribute = ('identifier', ) # Descriptor Information = SerializableDescriptor( 'Information', ExploitationFeaturesCollectionInformationType, _required, strict=DEFAULT_STRICT, docstring='General collection information.' ) # type: ExploitationFeaturesCollectionInformationType Geometry = SerializableDescriptor( 'Geometry', ExploitationFeaturesCollectionGeometryType, _required, strict=DEFAULT_STRICT, docstring='Key geometry parameters independent of product ' 'processing.' ) # type: Union[None, ExploitationFeaturesCollectionGeometryType] Phenomenology = SerializableDescriptor( 'Phenomenology', ExploitationFeaturesCollectionPhenomenologyType, _required, strict=DEFAULT_STRICT, docstring='Phenomenology related to both the geometry and the final ' 'product processing.' ) # type: Union[None, ExploitationFeaturesCollectionPhenomenologyType] identifier = StringDescriptor( 'identifier', _required, strict=DEFAULT_STRICT, docstring='The exploitation feature identifier.') # type: str def __init__(self, Information=None, Geometry=None, Phenomenology=None, identifier=None, **kwargs): """ Parameters ---------- Information : ExploitationFeaturesCollectionInformationType Geometry : None|ExploitationFeaturesCollectionGeometryType Phenomenology : None|ExploitationFeaturesCollectionPhenomenologyType identifier : str kwargs """ if '_xml_ns' in kwargs: self._xml_ns = kwargs['_xml_ns'] if '_xml_ns_key' in kwargs: self._xml_ns_key = kwargs['_xml_ns_key'] self.Information = Information self.Geometry = Geometry self.Phenomenology = Phenomenology self.identifier = identifier super(CollectionType, self).__init__(**kwargs) @classmethod def from_calculator(cls, calculator, sicd): """ Create from an ExploitationCalculator object. Parameters ---------- calculator : ExploitationCalculator sicd : SICDType Returns ------- CollectionType """ if not isinstance(calculator, ExploitationCalculator): raise TypeError( 'Requires input which is an instance of ExploitationCalculator, got type {}' .format(type(calculator))) return cls( identifier=sicd.CollectionInfo.CoreName, Information=ExploitationFeaturesCollectionInformationType. from_sicd(sicd), Geometry=ExploitationFeaturesCollectionGeometryType. from_calculator(calculator), Phenomenology=ExploitationFeaturesCollectionPhenomenologyType. from_calculator(calculator))
class SCPCOAType(Serializable): """ Center of Aperture (COA) for the Scene Center Point (SCP). """ _fields = ( 'SCPTime', 'ARPPos', 'ARPVel', 'ARPAcc', 'SideOfTrack', 'SlantRange', 'GroundRange', 'DopplerConeAng', 'GrazeAng', 'IncidenceAng', 'TwistAng', 'SlopeAng', 'AzimAng', 'LayoverAng') _required = _fields _numeric_format = { 'SCPTime': '0.16G', 'SlantRange': '0.16G', 'GroundRange': '0.16G', 'DopplerConeAng': '0.16G', 'GrazeAng': '0.16G', 'IncidenceAng': '0.16G', 'TwistAng': '0.16G', 'SlopeAng': '0.16G', 'AzimAng': '0.16G', 'LayoverAng': '0.16G'} # class variables _SIDE_OF_TRACK_VALUES = ('L', 'R') # descriptors SCPTime = FloatDescriptor( 'SCPTime', _required, strict=DEFAULT_STRICT, docstring='*Center Of Aperture time for the SCP (t_COA_SCP)*, relative to collection ' 'start in seconds.') # type: float ARPPos = SerializableDescriptor( 'ARPPos', XYZType, _required, strict=DEFAULT_STRICT, docstring='Aperture position at *t_COA_SCP* in ECF coordinates.') # type: XYZType ARPVel = SerializableDescriptor( 'ARPVel', XYZType, _required, strict=DEFAULT_STRICT, docstring='ARP Velocity at *t_COA_SCP* in ECF coordinates.') # type: XYZType ARPAcc = SerializableDescriptor( 'ARPAcc', XYZType, _required, strict=DEFAULT_STRICT, docstring='ARP Acceleration at *t_COA_SCP* in ECF coordinates.') # type: XYZType SideOfTrack = StringEnumDescriptor( 'SideOfTrack', _SIDE_OF_TRACK_VALUES, _required, strict=DEFAULT_STRICT, docstring='Side of track.') # type: str SlantRange = FloatDescriptor( 'SlantRange', _required, strict=DEFAULT_STRICT, docstring='Slant range from the aperture to the *SCP* in meters.') # type: float GroundRange = FloatDescriptor( 'GroundRange', _required, strict=DEFAULT_STRICT, docstring='Ground Range from the aperture nadir to the *SCP*. Distance measured along spherical earth model ' 'passing through the *SCP* in meters.') # type: float DopplerConeAng = FloatDescriptor( 'DopplerConeAng', _required, strict=DEFAULT_STRICT, docstring='The Doppler Cone Angle to SCP at *t_COA_SCP* in degrees.') # type: float GrazeAng = FloatDescriptor( 'GrazeAng', _required, strict=DEFAULT_STRICT, bounds=(0., 90.), docstring='Grazing Angle between the SCP *Line of Sight (LOS)* and *Earth Tangent Plane (ETP)*.') # type: float IncidenceAng = FloatDescriptor( 'IncidenceAng', _required, strict=DEFAULT_STRICT, bounds=(0., 90.), docstring='Incidence Angle between the *LOS* and *ETP* normal.') # type: float TwistAng = FloatDescriptor( 'TwistAng', _required, strict=DEFAULT_STRICT, bounds=(-90., 90.), docstring='Angle between cross range in the *ETP* and cross range in the slant plane.') # type: float SlopeAng = FloatDescriptor( 'SlopeAng', _required, strict=DEFAULT_STRICT, bounds=(0., 90.), docstring='Slope Angle from the *ETP* to the slant plane at *t_COA_SCP*.') # type: float AzimAng = FloatDescriptor( 'AzimAng', _required, strict=DEFAULT_STRICT, bounds=(0., 360.), docstring='Angle from north to the line from the *SCP* to the aperture nadir at *COA*. Measured ' 'clockwise in the *ETP*.') # type: float LayoverAng = FloatDescriptor( 'LayoverAng', _required, strict=DEFAULT_STRICT, bounds=(0., 360.), docstring='Angle from north to the layover direction in the *ETP* at *COA*. Measured ' 'clockwise in the *ETP*.') # type: float def __init__(self, SCPTime=None, ARPPos=None, ARPVel=None, ARPAcc=None, SideOfTrack=None, SlantRange=None, GroundRange=None, DopplerConeAng=None, GrazeAng=None, IncidenceAng=None, TwistAng=None, SlopeAng=None, AzimAng=None, LayoverAng=None, **kwargs): """ Parameters ---------- SCPTime : float ARPPos : XYZType|numpy.ndarray|list|tuple ARPVel : XYZType|numpy.ndarray|list|tuple ARPAcc : XYZType|numpy.ndarray|list|tuple SideOfTrack : str SlantRange : float GroundRange : float DopplerConeAng : float GrazeAng : float IncidenceAng : float TwistAng : float SlopeAng : float AzimAng : float LayoverAng : float kwargs : dict """ self._ROV = None self._squint = None self._shadow = None self._shadow_magnitude = None self._layover_magnitude = None if '_xml_ns' in kwargs: self._xml_ns = kwargs['_xml_ns'] if '_xml_ns_key' in kwargs: self._xml_ns_key = kwargs['_xml_ns_key'] self.SCPTime = SCPTime self.ARPPos, self.ARPVel, self.ARPAcc = ARPPos, ARPVel, ARPAcc self.SideOfTrack = SideOfTrack self.SlantRange, self.GroundRange = SlantRange, GroundRange self.DopplerConeAng, self.GrazeAng, self.IncidenceAng = DopplerConeAng, GrazeAng, IncidenceAng self.TwistAng, self.SlopeAng, self.AzimAng, self.LayoverAng = TwistAng, SlopeAng, AzimAng, LayoverAng super(SCPCOAType, self).__init__(**kwargs) @property def look(self): """ int: An integer version of `SideOfTrack`: * None if `SideOfTrack` is not defined * -1 if SideOfTrack == 'R' * 1 if SideOftrack == 'L' """ if self.SideOfTrack is None: return None else: return -1 if self.SideOfTrack == 'R' else 1 @property def ROV(self): """ float: The Ratio of Range to Velocity at Center of Aperture time. """ return self._ROV @property def ThetaDot(self): """ float: Derivative of Theta as a function of time at Center of Aperture time. """ if self.DopplerConeAng is None or self.ROV is None: return None return float(numpy.sin(numpy.deg2rad(self.DopplerConeAng))/self.ROV) @property def MultipathGround(self): """ float: The anticipated angle of multipath features on the ground in degrees. """ if self.GrazeAng is None or self.TwistAng is None: return None return numpy.rad2deg( -numpy.arctan(numpy.tan(numpy.deg2rad(self.TwistAng))*numpy.sin(numpy.deg2rad(self.GrazeAng)))) @property def Multipath(self): """ float: The anticipated angle of multipath features in degrees. """ if self.AzimAng is None or self.MultipathGround is None: return None return numpy.mod(self.AzimAng - 180 + self.MultipathGround, 360) @property def Shadow(self): """ float: The anticipated angle of shadow features in degrees. """ return self._shadow @property def ShadowMagnitude(self): """ float: The anticipated relative magnitude of shadow features. """ return self._shadow_magnitude @property def Squint(self): """ float: The squint angle, in degrees. """ return self._squint @property def LayoverMagnitude(self): """ float: The anticipated relative magnitude of layover features. """ return self._layover_magnitude def _derive_scp_time(self, Grid, overwrite=False): """ Expected to be called by SICD parent. Parameters ---------- Grid : sarpy.io.complex.sicd_elements.GridType overwrite : bool Returns ------- None """ if Grid is None or Grid.TimeCOAPoly is None: return # nothing can be done if not overwrite and self.SCPTime is not None: return # nothing should be done scp_time = Grid.TimeCOAPoly.Coefs[0, 0] self.SCPTime = scp_time def _derive_position(self, Position, overwrite=False): """ Derive aperture position parameters, if necessary. Expected to be called by SICD parent. Parameters ---------- Position : sarpy.io.complex.sicd_elements.Position.PositionType overwrite : bool Returns ------- None """ if Position is None or Position.ARPPoly is None or self.SCPTime is None: return # nothing can be derived # set aperture position, velocity, and acceleration at scptime from position # polynomial, if necessary poly = Position.ARPPoly scptime = self.SCPTime if self.ARPPos is None or overwrite: self.ARPPos = XYZType.from_array(poly(scptime)) self.ARPVel = XYZType.from_array(poly.derivative_eval(scptime, 1)) self.ARPAcc = XYZType.from_array(poly.derivative_eval(scptime, 2)) def _derive_geometry_parameters(self, GeoData, overwrite=False): """ Expected to be called by SICD parent. Parameters ---------- GeoData : sarpy.io.complex.sicd_elements.GeoData.GeoDataType overwrite : bool Returns ------- None """ if GeoData is None or GeoData.SCP is None or GeoData.SCP.ECF is None or \ self.ARPPos is None or self.ARPVel is None: return # nothing can be derived # construct our calculator calculator = GeometryCalculator( GeoData.SCP.ECF.get_array(), self.ARPPos.get_array(), self.ARPVel.get_array()) # set all the values self._ROV = calculator.ROV if self.SideOfTrack is None or overwrite: self.SideOfTrack = calculator.SideOfTrack if self.SlantRange is None or overwrite: self.SlantRange = calculator.SlantRange if self.GroundRange is None or overwrite: self.GroundRange = calculator.GroundRange if self.DopplerConeAng is None or overwrite: self.DopplerConeAng = calculator.DopplerConeAng graz, inc = calculator.get_graze_and_incidence() if self.GrazeAng is None or overwrite: self.GrazeAng = graz if self.IncidenceAng is None or overwrite: self.IncidenceAng = inc if self.TwistAng is None or overwrite: self.TwistAng = calculator.TwistAng self._squint = calculator.SquintAngle if self.SlopeAng is None or overwrite: self.SlopeAng = calculator.SlopeAng if self.AzimAng is None or overwrite: self.AzimAng = calculator.AzimAng layover, self._layover_magnitude = calculator.get_layover() if self.LayoverAng is None or overwrite: self.LayoverAng = layover self._shadow, self._shadow_magnitude = calculator.get_shadow() def rederive(self, Grid, Position, GeoData): """ Rederive all derived quantities. Parameters ---------- Grid : sarpy.io.complex.sicd_elements.GridType Position : sarpy.io.complex.sicd_elements.Position.PositionType GeoData : sarpy.io.complex.sicd_elements.GeoData.GeoDataType Returns ------- None """ self._derive_scp_time(Grid, overwrite=True) self._derive_position(Position, overwrite=True) self._derive_geometry_parameters(GeoData, overwrite=True) def check_values(self, GeoData): """ Check derived values for validity. Parameters ---------- GeoData : sarpy.io.complex.sicd_elements.GeoData.GeoDataType Returns ------- bool """ if GeoData is None or GeoData.SCP is None or GeoData.SCP.ECF is None or \ self.ARPPos is None or self.ARPVel is None: return True # nothing can be derived # construct our calculator calculator = GeometryCalculator( GeoData.SCP.ECF.get_array(), self.ARPPos.get_array(), self.ARPVel.get_array()) cond = True if calculator.SideOfTrack != self.SideOfTrack: self.log_validity_error( 'SideOfTrack is expected to be {}, and is populated as {}'.format( calculator.SideOfTrack, self.SideOfTrack)) cond = False for attribute in ['SlantRange', 'GroundRange']: val1 = getattr(self, attribute) val2 = getattr(calculator, attribute) if abs(val1/val2 - 1) > 1e-6: self.log_validity_error( 'attribute {} is expected to have value {}, but is populated as {}'.format(attribute, val1, val2)) cond = False for attribute in [ 'DopplerConeAng', 'GrazeAng', 'IncidenceAng', 'TwistAng', 'SlopeAng', 'AzimAng', 'LayoverAng']: val1 = getattr(self, attribute) val2 = getattr(calculator, attribute) if abs(val1 - val2) > 1e-3: self.log_validity_error( 'attribute {} is expected to have value {}, but is populated as {}'.format(attribute, val1, val2)) cond = False return cond
class SensorInfoType(Serializable): _fields = ( 'Name', 'SensorMfg', 'OperatingAgency', 'Type', 'Mode', 'Band', 'Bandwidth', 'CenterFrequency', 'NearRange', 'SlantRangeSwathWidth', 'Polarization', 'Range', 'DepressionAngle', 'LinearDynamicRange', 'BeamWidth', 'Aimpoint', 'AircraftHeading', 'AircraftTrackAngle', 'Look', 'SquintAngle', 'AircraftLocation', 'AircraftVelocity', 'FlightNumber', 'PassNumber') _required = ( 'Type', 'Range', 'DepressionAngle', 'Aimpoint', 'Look', 'SquintAngle', 'AircraftLocation', 'AircraftVelocity') _numeric_format = { 'Bandwidth': '0.17G', 'CenterFrequency': '0.17G', 'NearRange': '0.17G', 'SlantRangeSwathWidth': '0.17G', 'Range': '0.17G', 'DepressionAngle': '0.17G', 'LinearDynamicRange': '0.17G', 'AircraftHeading': '0.17G', 'AircraftTrackAngle': '0.17G',} # descriptors Name = StringDescriptor( 'Name', _required, docstring='The name of the sensor') # type: Optional[str] SensorMfg = StringDescriptor( 'SensorMfg', _required, docstring='The manufacturer of the sensor') # type: Optional[str] OperatingAgency = StringDescriptor( 'OperatingAgency', _required, docstring='The agency or company that operates the sensor') # type: Optional[str] Type = StringDescriptor( 'Type', _required, docstring='The type of sensor (i.e SAR or EO)') # type: str Mode = StringDescriptor( 'Mode', _required, docstring='Sensor operating mode') # type: Optional[str] Band = StringDescriptor( 'Band', _required, docstring='designation of the sensor frequency band') # type: Optional[str] Bandwidth = FloatDescriptor( 'Bandwidth', _required, docstring='Radio Frequency bandwidth of the sensor system in GHz') # type: Optional[float] CenterFrequency = FloatDescriptor( 'CenterFrequency', _required, docstring='Center operating frequency of the sensor system in GHz') # type: Optional[float] NearRange = FloatDescriptor( 'NearRange', _required, docstring='The slant range distance measured from the sensor to the ' 'near range of the image') # type: Optional[float] SlantRangeSwathWidth = FloatDescriptor( 'SlantRangeSwathWidth', _required, docstring='The width of the image as measured in the slant range' ) # type: Optional[float] Polarization = StringDescriptor( 'Polarization', _required, docstring='The polarization of the transmitted/received signals') # type: Optional[str] Range = FloatDescriptor( 'Range', _required, docstring='Measured slant range between the sensor aperture ' 'and the scene center') # type: float DepressionAngle = FloatDescriptor( 'DepressionAngle', _required, docstring='Measured depression angle between the sensor line-of-sight ' 'and the local horizontal reference plane') # type: float LinearDynamicRange = FloatDescriptor( 'LinearDynamicRange', _required, docstring="The span of the signal amplitudes (or power levels) over " "which the system's response is linear. Typically the ratio " "of the largest input signal that causes a 1 db compression " "in receiver dynamic gain and the minimum signal defined by " "receiver noise.") # type: Optional[float] BeamWidth = SerializableDescriptor( 'BeamWidth', BeamWidthType, _required, docstring='The width of the radar beam at its half power' ) # type: Optional[BeamWidthType] Aimpoint = SerializableDescriptor( 'Aimpoint', LatLonEleType, _required, docstring='The sensor aim point') # type: LatLonEleType AircraftHeading = FloatDescriptor( 'AircraftHeading', _required, docstring='Aircraft heading relative to True North, in degrees' ) # type: Optional[float] AircraftTrackAngle = FloatDescriptor( 'AircraftTrackAngle', _required, docstring='The bearing from the aircraft position at the first pulse ' 'to the aircraft position at the last') # type: Optional[float] Look = StringEnumDescriptor( 'Look', {'Left', 'Right', 'Nadir'}, _required, docstring='Direction of the sensor look angle relative to aircraft ' 'motion') # type: str SquintAngle = SerializableDescriptor( 'SquintAngle', SquintAngleType, _required, docstring='Measured angle between the sensor line-of-sight and the ' 'lateral axis of the aircraft') # type: SquintAngleType AircraftLocation = SerializableDescriptor( 'AircraftLocation', AircraftLocationType, _required, docstring='The aircraft location (at scene center COA time?)') # type: AircraftLocationType AircraftVelocity = SerializableDescriptor( 'AircraftVelocity', XYZType, _required, docstring='Aircraft velocity in ECEF coordinates (at scene center COA time?)') # type: XYZType FlightNumber = IntegerDescriptor( 'FlightNumber', _required, docstring='The aircraft flight number') # type: Optional[int] PassNumber = IntegerDescriptor( 'PassNumber', _required, docstring='The aircraft pass number') # type: Optional[int] def __init__(self, Name=None, SensorMfg=None, OperatingAgency=None, Type=None, Mode=None, Band=None, Bandwidth=None, CenterFrequency=None, NearRange=None, SlantRangeSwathWidth=None, Polarization=None, Range=None, DepressionAngle=None, LinearDynamicRange=None, BeamWidth=None, Aimpoint=None, AircraftHeading=None, AircraftTrackAngle=None, Look=None, SquintAngle=None, AircraftLocation=None, AircraftVelocity=None, FlightNumber=None, PassNumber=None, **kwargs): """ Parameters ---------- Name : None|str SensorMfg : None|str OperatingAgency : None|str Type : str Mode : None|str Band : None|str Bandwidth : None|float CenterFrequency : None|float NearRange : None|float SlantRangeSwathWidth : None|float Polarization : None|str Range : float DepressionAngle : float LinearDynamicRange : None|float BeamWidth : BeamWidthType Aimpoint : LatLonEleType|numpy.ndarray|list|tuple AircraftHeading : None|float AircraftTrackAngle : None|float Look : str SquintAngle : SquintAngleType AircraftLocation : AircraftLocationType|numpy.ndarray|list|tuple AircraftVelocity : XYZType|numpy.ndarray|list|tuple FlightNumber : None|int PassNumber : None|int kwargs Other keyword arguments """ if '_xml_ns' in kwargs: self._xml_ns = kwargs['_xml_ns'] if '_xml_ns_key' in kwargs: self._xml_ns_key = kwargs['_xml_ns_key'] self.Name = Name self.SensorMfg = SensorMfg self.OperatingAgency = OperatingAgency self.Type = Type self.Mode = Mode self.Band = Band self.Bandwidth = Bandwidth self.CenterFrequency = CenterFrequency self.NearRange = NearRange self.SlantRangeSwathWidth = SlantRangeSwathWidth self.Polarization = Polarization self.Range = Range self.DepressionAngle = DepressionAngle self.LinearDynamicRange = LinearDynamicRange self.BeamWidth = BeamWidth self.Aimpoint = Aimpoint self.AircraftHeading = AircraftHeading self.AircraftTrackAngle = AircraftTrackAngle self.Look = Look self.SquintAngle = SquintAngle self.AircraftLocation = AircraftLocation self.AircraftVelocity = AircraftVelocity self.FlightNumber = FlightNumber self.PassNumber = PassNumber super(SensorInfoType, self).__init__(**kwargs) @classmethod def from_sicd(cls, sicd): """ Construct the sensor info from a sicd structure Parameters ---------- sicd : SICDType Returns ------- SensorInfoType """ transmit_freq_proc = sicd.ImageFormation.TxFrequencyProc center_freq = 0.5*(transmit_freq_proc.MinProc + transmit_freq_proc.MaxProc)*1e-9 polarization = sicd.ImageFormation.get_polarization().replace(':', '') look = 'Left' if sicd.SCPCOA.SideOfTrack == 'L' else 'Right' slant_squint = 90 - sicd.SCPCOA.DopplerConeAng ground_squint = 90 - numpy.rad2deg( numpy.arccos( numpy.cos(numpy.deg2rad(sicd.SCPCOA.DopplerConeAng)) / numpy.cos(numpy.deg2rad(sicd.SCPCOA.GrazeAng)) ) ) arp_pos_llh = ecf_to_geodetic(sicd.SCPCOA.ARPPos.get_array()) return SensorInfoType( Name=sicd.CollectionInfo.CollectorName, Type='SAR', Mode=sicd.CollectionInfo.RadarMode.ModeType, Band=sicd.ImageFormation.get_transmit_band_name(), CenterFrequency=center_freq, Polarization=polarization, Range=sicd.SCPCOA.SlantRange, DepressionAngle=sicd.SCPCOA.GrazeAng, Aimpoint=sicd.GeoData.SCP.LLH.get_array(), Look=look, SquintAngle=SquintAngleType(SlantPlane=slant_squint, GroundPlane=ground_squint), AircraftLocation=arp_pos_llh, AircraftVelocity=sicd.SCPCOA.ARPVel.get_array())
class GlobalType(Serializable): """ The Global type definition. """ _fields = ('DomainType', 'SGN', 'Timeline', 'FxBand', 'TOASwath', 'TropoParameters', 'IonoParameters') _required = ('DomainType', 'SGN', 'Timeline', 'FxBand', 'TOASwath') # descriptors DomainType = StringEnumDescriptor( 'DomainType', ('FX', 'TOA'), _required, strict=DEFAULT_STRICT, docstring= 'Indicates the domain represented by the sample dimension of the ' 'CPHD signal array(s), where "FX" denotes Transmit Frequency, and ' '"TOA" denotes Difference in Time of Arrival') # type: str SGN = IntegerEnumDescriptor( 'SGN', (-1, 1), _required, strict=DEFAULT_STRICT, docstring= 'Phase SGN applied to compute target signal phase as a function of ' r'target :math:`\Delta TOA^{TGT}`. Target phase in cycles. ' r'For simple phase model :math:`Phase(fx) = SGN \times fx \times \Delta TOA^{TGT}` ' r'In TOA domain, phase of the mainlobe peak ' r':math:`Phase(\Delta TOA^{TGT}) = SGN \times fx_C \times \Delta TOA^{TGT}`' '.') # type: int Timeline = SerializableDescriptor( 'Timeline', TimelineType, _required, strict=DEFAULT_STRICT, docstring= 'Parameters that describe the collection times for the data contained ' 'in the product') # type: TimelineType FxBand = SerializableDescriptor( 'FxBand', FxBandType, _required, strict=DEFAULT_STRICT, docstring= 'Parameters that describe the FX frequency limits for the signal array(s) ' 'contained in the product.') # type: FxBandType TOASwath = SerializableDescriptor( 'TOASwath', TOASwathType, _required, strict=DEFAULT_STRICT, docstring= 'Parameters that describe the time-of-arrival (TOA) swath limits for the ' 'signal array(s) contained in the product.') # type: TOASwathType TropoParameters = SerializableDescriptor( 'TropoParameters', TropoParametersType, _required, strict=DEFAULT_STRICT, docstring='Parameters used to compute the propagation delay due to the ' 'troposphere.') # type: Union[None, TropoParametersType] IonoParameters = SerializableDescriptor( 'IonoParameters', IonoParametersType, _required, strict=DEFAULT_STRICT, docstring='Parameters used to compute propagation effects due to the ' 'ionosphere.') # type: Union[None, IonoParametersType] def __init__(self, DomainType=None, SGN=None, Timeline=None, FxBand=None, TOASwath=None, TropoParameters=None, IonoParameters=None, **kwargs): """ Parameters ---------- DomainType : str SGN : int Timeline : TimelineType FxBand : FxBandType|numpy.ndarray|list|tuple TOASwath : TOASwathType|numpy.ndarray|list|tuple TropoParameters : None|TropoParametersType IonoParameters : None|IonoParametersType kwargs """ if '_xml_ns' in kwargs: self._xml_ns = kwargs['_xml_ns'] if '_xml_ns_key' in kwargs: self._xml_ns_key = kwargs['_xml_ns_key'] self.DomainType = DomainType self.SGN = SGN self.Timeline = Timeline self.FxBand = FxBand self.TOASwath = TOASwath self.TropoParameters = TropoParameters self.IonoParameters = IonoParameters super(GlobalType, self).__init__(**kwargs)
class PFAType(Serializable): """Parameters for the Polar Formation Algorithm.""" _fields = ('FPN', 'IPN', 'PolarAngRefTime', 'PolarAngPoly', 'SpatialFreqSFPoly', 'Krg1', 'Krg2', 'Kaz1', 'Kaz2', 'STDeskew') _required = ('FPN', 'IPN', 'PolarAngRefTime', 'PolarAngPoly', 'SpatialFreqSFPoly', 'Krg1', 'Krg2', 'Kaz1', 'Kaz2') _numeric_format = { 'PolarAngRefTime': '0.16G', 'Krg1': '0.16G', 'Krg2': '0.16G', 'Kaz1': '0.16G', 'Kaz2': '0.16G' } # descriptors FPN = UnitVectorDescriptor( 'FPN', XYZType, _required, strict=DEFAULT_STRICT, docstring= 'Focus Plane unit normal in ECF coordinates. Unit vector FPN points away from the center of ' 'the Earth.') # type: XYZType IPN = UnitVectorDescriptor( 'IPN', XYZType, _required, strict=DEFAULT_STRICT, docstring= 'Image Formation Plane unit normal in ECF coordinates. Unit vector IPN points away from the ' 'center of the Earth.') # type: XYZType PolarAngRefTime = FloatDescriptor( 'PolarAngRefTime', _required, strict=DEFAULT_STRICT, docstring= 'Polar image formation reference time *(in seconds)*. Polar Angle = 0 at the reference time. ' 'Measured relative to collection start. *Note: Reference time is typically set equal to the SCP ' 'COA time but may be different.*') # type: float PolarAngPoly = SerializableDescriptor( 'PolarAngPoly', Poly1DType, _required, strict=DEFAULT_STRICT, docstring= 'Polynomial function that yields Polar Angle *(in radians)* as function of time ' 'relative to Collection Start.') # type: Poly1DType SpatialFreqSFPoly = SerializableDescriptor( 'SpatialFreqSFPoly', Poly1DType, _required, strict=DEFAULT_STRICT, docstring= 'Polynomial that yields the *Spatial Frequency Scale Factor (KSF)* as a function of Polar ' r'Angle. That is, :math:`Polar Angle[radians] \to KSF[dimensionless]`. Used to scale RF ' 'frequency *(fx, Hz)* to aperture spatial frequency *(Kap, cycles/m)*. Where,' r':math:`Kap = fx\cdot (2/c)\cdot KSF`, and `Kap` is the effective spatial ' 'frequency in the polar aperture.') # type: Poly1DType Krg1 = FloatDescriptor( 'Krg1', _required, strict=DEFAULT_STRICT, docstring= 'Minimum *range spatial frequency (Krg)* output from the polar to rectangular ' 'resampling.') # type: float Krg2 = FloatDescriptor( 'Krg2', _required, strict=DEFAULT_STRICT, docstring= 'Maximum *range spatial frequency (Krg)* output from the polar to rectangular ' 'resampling.') # type: float Kaz1 = FloatDescriptor( 'Kaz1', _required, strict=DEFAULT_STRICT, docstring= 'Minimum *azimuth spatial frequency (Kaz)* output from the polar to rectangular ' 'resampling.') # type: float Kaz2 = FloatDescriptor( 'Kaz2', _required, strict=DEFAULT_STRICT, docstring= 'Maximum *azimuth spatial frequency (Kaz)* output from the polar to rectangular ' 'resampling.') # type: float STDeskew = SerializableDescriptor( 'STDeskew', STDeskewType, _required, strict=DEFAULT_STRICT, docstring= 'Parameters to describe image domain slow time *(ST)* Deskew processing.' ) # type: STDeskewType def __init__(self, FPN=None, IPN=None, PolarAngRefTime=None, PolarAngPoly=None, SpatialFreqSFPoly=None, Krg1=None, Krg2=None, Kaz1=None, Kaz2=None, STDeskew=None, **kwargs): """ Parameters ---------- FPN : XYZType|numpy.ndarray|list|tuple IPN : XYZType|numpy.ndarray|list|tuple PolarAngRefTime : float PolarAngPoly : Poly1DType|numpy.ndarray|list|tuple SpatialFreqSFPoly : Poly1DType|numpy.ndarray|list|tuple Krg1 : float Krg2 : float Kaz1 : float Kaz2 : float STDeskew : STDeskewType kwargs : dict """ if '_xml_ns' in kwargs: self._xml_ns = kwargs['_xml_ns'] if '_xml_ns_key' in kwargs: self._xml_ns_key = kwargs['_xml_ns_key'] self.FPN = FPN self.IPN = IPN self.PolarAngRefTime = PolarAngRefTime self.PolarAngPoly = PolarAngPoly self.SpatialFreqSFPoly = SpatialFreqSFPoly self.Krg1, self.Krg2 = Krg1, Krg2 self.Kaz1, self.Kaz2 = Kaz1, Kaz2 self.STDeskew = STDeskew super(PFAType, self).__init__(**kwargs) def pfa_polar_coords(self, Position, SCP, times): """ Calculate the PFA parameters necessary for mapping phase history to polar coordinates. Parameters ---------- Position : sarpy.io.complex.sicd_elements.Position.PositionType SCP : numpy.ndarray times : numpy.ndarray|float|int Returns ------- (numpy.ndarray, numpy.ndarray)|(float, float) `(k_a, k_sf)`, where `k_a` is polar angle, and `k_sf` is spatial frequency scale factor. The shape of the output array (or scalar) will match the shape of the `times` array (or scalar). """ def project_to_image_plane(points): # type: (numpy.ndarray) -> numpy.ndarray # project into the image plane along line normal to the focus plane offset = (SCP - points).dot(ipn) / fpn.dot(ipn) if offset.ndim == 0: return points + offset * fpn else: return points + numpy.outer(offset, fpn) if self.IPN is None or self.FPN is None: return None, None ipn = self.IPN.get_array(dtype='float64') fpn = self.FPN.get_array(dtype='float64') if isinstance(times, (float, int)) or times.ndim == 0: o_shape = None times = numpy.array([ times, ], dtype='float64') else: o_shape = times.shape times = numpy.reshape(times, (-1, )) positions = Position.ARPPoly(times) reference_position = Position.ARPPoly(self.PolarAngRefTime) image_plane_positions = project_to_image_plane(positions) image_plane_coa = project_to_image_plane(reference_position) # establish image plane coordinate system ip_x = image_plane_coa - SCP ip_x /= numpy.linalg.norm(ip_x) ip_y = numpy.cross(ip_x, ipn) # compute polar angle of sensor position in image plane ip_range = image_plane_positions - SCP ip_range /= numpy.linalg.norm(ip_range, axis=1)[:, numpy.newaxis] k_a = -numpy.arctan2(ip_range.dot(ip_y), ip_range.dot(ip_x)) # compute the spatial frequency scale factor range_vectors = positions - SCP range_vectors /= numpy.linalg.norm(range_vectors, axis=1)[:, numpy.newaxis] sin_graze = range_vectors.dot(fpn) sin_graze_ip = ip_range.dot(fpn) k_sf = numpy.sqrt( (1 - sin_graze * sin_graze) / (1 - sin_graze_ip * sin_graze_ip)) if o_shape is None: return k_a[0], k_sf[0] elif len(o_shape) > 1: return numpy.reshape(k_a, o_shape), numpy.reshape(k_sf, o_shape) else: return k_a, k_sf def _derive_parameters(self, Grid, SCPCOA, GeoData, Position, Timeline): """ Expected to be called from SICD parent. Parameters ---------- Grid : sarpy.io.complex.sicd_elements.Grid.GridType SCPCOA : sarpy.io.complex.sicd_elements.SCPCOA.SCPCOAType GeoData : sarpy.io.complex.sicd_elements.GeoData.GeoDataType Position : sarpy.io.complex.sicd_elements.Position.PositionType Timeline : sarpy.io.complex.sicd_elements.Timeline.TimelineType Returns ------- None """ if self.PolarAngRefTime is None and SCPCOA.SCPTime is not None: self.PolarAngRefTime = SCPCOA.SCPTime if GeoData is None or GeoData.SCP is None or GeoData.SCP.ECF is None: return scp = GeoData.SCP.ECF.get_array() if SCPCOA.ARPPos is not None and SCPCOA.ARPVel is not None: scp = GeoData.SCP.ECF.get_array() etp = geocoords.wgs_84_norm(scp) arp = SCPCOA.ARPPos.get_array() los = (scp - arp) ulos = los / norm(los) look = SCPCOA.look arp_vel = SCPCOA.ARPVel.get_array() uspz = look * numpy.cross(arp_vel, ulos) uspz /= norm(uspz) if Grid is not None and Grid.ImagePlane is not None: if self.IPN is None: if Grid.ImagePlane == 'SLANT': self.IPN = XYZType.from_array(uspz) elif Grid.ImagePlane == 'GROUND': self.IPN = XYZType.from_array(etp) elif self.IPN is None: self.IPN = XYZType.from_array( uspz) # assuming slant -> most common if self.FPN is None: self.FPN = XYZType.from_array(etp) if Position is not None and \ Timeline is not None and Timeline.CollectDuration is not None and \ (self.PolarAngPoly is None or self.SpatialFreqSFPoly is None): pol_ref_pos = Position.ARPPoly(self.PolarAngRefTime) # fit the PFA polynomials times = numpy.linspace(0, Timeline.CollectDuration, 15) k_a, k_sf = self.pfa_polar_coords(Position, scp, times) self.PolarAngPoly = Poly1DType( Coefs=polynomial.polyfit(times, k_a, 5, full=False)) self.SpatialFreqSFPoly = Poly1DType( Coefs=polynomial.polyfit(k_a, k_sf, 5, full=False)) if Grid is not None and Grid.Row is not None and \ Grid.Row.KCtr is not None and Grid.Row.ImpRespBW is not None: if self.Krg1 is None: self.Krg1 = Grid.Row.KCtr - 0.5 * Grid.Row.ImpRespBW if self.Krg2 is None: self.Krg2 = Grid.Row.KCtr + 0.5 * Grid.Row.ImpRespBW if Grid is not None and Grid.Col is not None and \ Grid.Col.KCtr is not None and Grid.Col.ImpRespBW is not None: if self.Kaz1 is None: self.Kaz1 = Grid.Col.KCtr - 0.5 * Grid.Col.ImpRespBW if self.Kaz2 is None: self.Kaz2 = Grid.Col.KCtr + 0.5 * Grid.Col.ImpRespBW def _check_polar_ang_ref(self): """ Checks the polar angle origin makes sense. Returns ------- bool """ if self.PolarAngPoly is None or self.PolarAngRefTime is None: return True cond = True polar_angle_ref = self.PolarAngPoly(self.PolarAngRefTime) if abs(polar_angle_ref) > 1e-4: self.log_validity_error( 'The PolarAngPoly evaluated at PolarAngRefTime yields {}, which should be 0' .format(polar_angle_ref)) cond = False return cond def _basic_validity_check(self): condition = super(PFAType, self)._basic_validity_check() condition &= self._check_polar_ang_ref() return condition
class PVPType(Serializable): _fields = ('RcvTime', 'RcvPos', 'RcvVel', 'RefPhi0', 'RefFreq', 'DFIC0', 'FICRate', 'FRCV1', 'FRCV2', 'DGRGC', 'SIGNAL', 'AmpSF', 'RcvAntenna', 'TxPulse', 'AddedPVP') _required = ('RcvTime', 'RcvPos', 'RcvVel', 'RefPhi0', 'RefFreq', 'DFIC0', 'FICRate', 'FRCV1', 'FRCV2') _collections_tags = {'AddedPVP': {'array': False, 'child_tag': 'AddedPVP'}} # descriptors RcvTime = SerializableDescriptor( 'RcvTime', PerVectorParameterF8, _required, strict=DEFAULT_STRICT, docstring='') # type: PerVectorParameterF8 RcvPos = SerializableDescriptor( 'RcvPos', PerVectorParameterXYZ, _required, strict=DEFAULT_STRICT, docstring='') # type: PerVectorParameterXYZ RcvVel = SerializableDescriptor( 'RcvVel', PerVectorParameterXYZ, _required, strict=DEFAULT_STRICT, docstring='') # type: PerVectorParameterXYZ RefPhi0 = SerializableDescriptor( 'RefPhi0', PerVectorParameterF8, _required, strict=DEFAULT_STRICT, docstring='') # type: PerVectorParameterF8 RefFreq = SerializableDescriptor( 'RefFreq', PerVectorParameterF8, _required, strict=DEFAULT_STRICT, docstring='') # type: PerVectorParameterF8 DFIC0 = SerializableDescriptor('DFIC0', PerVectorParameterF8, _required, strict=DEFAULT_STRICT, docstring='') # type: PerVectorParameterF8 FICRate = SerializableDescriptor( 'FICRate', PerVectorParameterF8, _required, strict=DEFAULT_STRICT, docstring='') # type: PerVectorParameterF8 FRCV1 = SerializableDescriptor('FRCV1', PerVectorParameterF8, _required, strict=DEFAULT_STRICT, docstring='') # type: PerVectorParameterF8 FRCV2 = SerializableDescriptor('FRCV2', PerVectorParameterF8, _required, strict=DEFAULT_STRICT, docstring='') # type: PerVectorParameterF8 DGRGC = SerializableDescriptor( 'DGRGC', PerVectorParameterF8, _required, strict=DEFAULT_STRICT, docstring='') # type: Union[None, PerVectorParameterF8] SIGNAL = SerializableDescriptor( 'SIGNAL', PerVectorParameterI8, _required, strict=DEFAULT_STRICT, docstring='') # type: Union[None, PerVectorParameterI8] AmpSF = SerializableDescriptor( 'AmpSF', PerVectorParameterF8, _required, strict=DEFAULT_STRICT, docstring='') # type: Union[None, PerVectorParameterF8] RcvAntenna = SerializableDescriptor( 'RcvAntenna', RcvAntennaType, _required, strict=DEFAULT_STRICT, docstring='') # type: Union[None, RcvAntennaType] TxPulse = SerializableDescriptor( 'TxPulse', TxPulseType, _required, strict=DEFAULT_STRICT, docstring='') # type: Union[None, TxPulseType] AddedPVP = SerializableListDescriptor( 'AddedPVP', UserDefinedPVPType, _collections_tags, _required, strict=DEFAULT_STRICT, docstring='') # type: Union[None, List[UserDefinedPVPType]] def __init__(self, RcvTime=None, RcvPos=None, RcvVel=None, RefPhi0=None, RefFreq=None, DFIC0=None, FICRate=None, FRCV1=None, FRCV2=None, DGRGC=None, SIGNAL=None, AmpSF=None, RcvAntenna=None, TxPulse=None, AddedPVP=None, **kwargs): """ Parameters ---------- RcvTime : PerVectorParameterF8 RcvPos : PerVectorParameterXYZ RcvVel : PerVectorParameterXYZ RefPhi0 : PerVectorParameterF8 RefFreq : PerVectorParameterF8 DFIC0 : PerVectorParameterF8 FICRate : PerVectorParameterF8 FRCV1 : PerVectorParameterF8 FRCV2 : PerVectorParameterF8 DGRGC : None|PerVectorParameterF8 SIGNAL : None|PerVectorParameterI8 AmpSF : None|PerVectorParameterF8 RcvAntenna : None|RcvAntennaType TxPulse : None|TxPulseType AddedPVP : None|List[UserDefinedPVPType] kwargs """ if '_xml_ns' in kwargs: self._xml_ns = kwargs['_xml_ns'] if '_xml_ns_key' in kwargs: self._xml_ns_key = kwargs['_xml_ns_key'] self.RcvTime = RcvTime self.RcvPos = RcvPos self.RcvVel = RcvVel self.RefPhi0 = RefPhi0 self.RefFreq = RefFreq self.DFIC0 = DFIC0 self.FICRate = FICRate self.FRCV1 = FRCV1 self.FRCV2 = FRCV2 self.DGRGC = DGRGC self.SIGNAL = SIGNAL self.AmpSF = AmpSF self.RcvAntenna = RcvAntenna self.TxPulse = TxPulse self.AddedPVP = AddedPVP super(PVPType, self).__init__(**kwargs) def get_size(self): """ Gets the size in bytes of each vector. Returns ------- int """ def get_num_words(obj): sz = getattr(obj, 'Size') if sz is not None: return sz sz = 0 # noinspection PyProtectedMember for fld in obj._fields: fld_val = getattr(obj, fld) if fld_val is not None: if fld_val.array: for arr_val in fld_val: sz += get_num_words(arr_val) else: sz += get_num_words(fld_val) return sz return get_num_words(self) * BYTES_PER_WORD def get_offset_size_format(self, field): """ Get the Offset (in bytes), Size (in bytes) for the given field, as well as the corresponding struct format string. Parameters ---------- field : str The desired field name. Returns ------- None|(int, int, str) """ def osf_tuple(val_in): return val_in.Offset * BYTES_PER_WORD, val_in.Size * BYTES_PER_WORD, homogeneous_dtype( val_in.Format).char # noinspection PyProtectedMember if field in self._fields[:-1]: val = getattr(self, field) if val is None: return None return osf_tuple(val) elif self.RcvAntenna and field in self.RcvAntenna._fields: val = getattr(self.RcvAntenna, field) if val is None: return None return osf_tuple(val) elif self.TxPulse and field in self.TxPulse._fields: val = getattr(self.TxPulse, field) if val is None: return None return osf_tuple(val) elif self.TxPulse and self.TxPulse.TxAntenna and field in self.TxPulse.TxAntenna._fields: val = getattr(self.TxPulse.TxAntenna, field) if val is None: return None return osf_tuple(val) else: if self.AddedPVP is None: return None for val in self.AddedPVP: if field == val.Name: return osf_tuple(val) return None def get_vector_dtype(self): """ Gets the dtype for the corresponding structured array for the full PVP set. Returns ------- numpy.dtype This will be a compound dtype for a structured array. """ names = [] formats = [] offsets = [] for field in self._fields: val = getattr(self, field) if val is None: continue elif field == "AddedPVP": for entry in val: names.append(entry.Name) formats.append(binary_format_string_to_dtype(entry.Format)) offsets.append(entry.Offset * BYTES_PER_WORD) elif field == 'RcvAntenna' or field == 'TxPulse': continue else: names.append(field) formats.append(binary_format_string_to_dtype(val.Format)) offsets.append(val.Offset * BYTES_PER_WORD) if self.RcvAntenna is not None: # noinspection PyProtectedMember for field in self.RcvAntenna._fields: val = getattr(self.RcvAntenna, field) if val is None: continue else: names.append(field) formats.append(binary_format_string_to_dtype(val.Format)) offsets.append(val.Offset * BYTES_PER_WORD) if self.TxPulse is not None: # noinspection PyProtectedMember for field in self.TxPulse._fields: val = getattr(self.TxPulse, field) if val is None: continue elif field == 'TxAntenna': continue else: names.append(field) formats.append(binary_format_string_to_dtype(val.Format)) offsets.append(val.Offset * BYTES_PER_WORD) if self.TxPulse.TxAntenna is not None: # noinspection PyProtectedMember for field in self.TxPulse.TxAntenna._fields: val = getattr(self.TxPulse.TxAntenna, field) if val is None: continue else: names.append(field) formats.append( binary_format_string_to_dtype(val.Format)) offsets.append(val.Offset * BYTES_PER_WORD) return numpy.dtype({ 'names': names, 'formats': formats, 'offsets': offsets })
class TxPulseType(Serializable): _fields = ('TxTime', 'TxPos', 'TxVel', 'FX1', 'FX2', 'TXmt', 'TxLFM', 'TxAntenna') _required = ('TxTime', 'TxPos', 'TxVel', 'FX1', 'FX2', 'TXmt') # descriptors TxTime = SerializableDescriptor('TxTime', PerVectorParameterF8, _required, strict=DEFAULT_STRICT, docstring='') # type: PerVectorParameterF8 TxPos = SerializableDescriptor('TxPos', PerVectorParameterXYZ, _required, strict=DEFAULT_STRICT, docstring='') # type: PerVectorParameterXYZ TxVel = SerializableDescriptor('TxVel', PerVectorParameterXYZ, _required, strict=DEFAULT_STRICT, docstring='') # type: PerVectorParameterXYZ FX1 = SerializableDescriptor('FX1', PerVectorParameterF8, _required, strict=DEFAULT_STRICT, docstring='') # type: PerVectorParameterF8 FX2 = SerializableDescriptor('FX2', PerVectorParameterF8, _required, strict=DEFAULT_STRICT, docstring='') # type: PerVectorParameterF8 TXmt = SerializableDescriptor('TXmt', PerVectorParameterF8, _required, strict=DEFAULT_STRICT, docstring='') # type: PerVectorParameterF8 TxLFM = SerializableDescriptor( 'TxLFM', PerVectorParameterTxLFM, _required, strict=DEFAULT_STRICT, docstring='') # type: Union[None, PerVectorParameterTxLFM] TxAntenna = SerializableDescriptor( 'TxAntenna', TxAntennaType, _required, strict=DEFAULT_STRICT, docstring='') # type: Union[None, TxAntennaType] def __init__(self, TxTime=None, TxPos=None, TxVel=None, FX1=None, FX2=None, TXmt=None, TxLFM=None, TxAntenna=None, **kwargs): """ Parameters ---------- TxTime : PerVectorParameterF8 TxPos : PerVectorParameterXYZ TxVel : PerVectorParameterXYZ FX1 : PerVectorParameterF8 FX2 : PerVectorParameterF8 TXmt : PerVectorParameterF8 TxLFM : None|PerVectorParameterTxLFM TxAntenna : None|TxAntennaType kwargs """ if '_xml_ns' in kwargs: self._xml_ns = kwargs['_xml_ns'] if '_xml_ns_key' in kwargs: self._xml_ns_key = kwargs['_xml_ns_key'] self.TxTime = TxTime self.TxPos = TxPos self.TxVel = TxVel self.FX1 = FX1 self.FX2 = FX2 self.TXmt = TXmt self.TxLFM = TxLFM self.TxAntenna = TxAntenna super(TxPulseType, self).__init__(**kwargs)
class GlobalType(Serializable): """ The Global type definition. """ _fields = ( 'DomainType', 'PhaseSGN', 'RefFreqIndex', 'CollectStart', 'CollectDuration', 'TxTime1', 'TxTime2', 'ImageArea') _required = ( 'DomainType', 'PhaseSGN', 'CollectStart', 'CollectDuration', 'TxTime1', 'TxTime2', 'ImageArea') _numeric_format = { 'CollectDuration': FLOAT_FORMAT, 'TxTime1': FLOAT_FORMAT, 'TxTime2': FLOAT_FORMAT} # descriptors DomainType = StringEnumDescriptor( 'DomainType', ('FX', 'TOA'), _required, strict=DEFAULT_STRICT, docstring='Indicates the domain represented by the sample dimension of the ' 'CPHD signal array(s), where "FX" denotes Transmit Frequency, and ' '"TOA" denotes Difference in Time of Arrival') # type: str PhaseSGN = IntegerEnumDescriptor( 'PhaseSGN', (-1, 1), _required, strict=DEFAULT_STRICT, docstring='Phase SGN applied to compute target signal phase as a function of ' r'target :math:`\Delta TOA^{TGT}`. Target phase in cycles. ' r'For simple phase model :math:`Phase(fx) = SGN \times fx \times \Delta TOA^{TGT}` ' r'In TOA domain, phase of the mainlobe peak ' r':math:`Phase(\Delta TOA^{TGT}) = SGN \times fx_C \times \Delta TOA^{TGT}`' '.') # type: int RefFreqIndex = IntegerDescriptor( 'RefFreqIndex', _required, strict=DEFAULT_STRICT, docstring='Indicates if the RF frequency values are expressed as offsets from ' 'a reference frequency (RefFreq).') # type: Union[None, int] CollectStart = DateTimeDescriptor( 'CollectStart', _required, strict=DEFAULT_STRICT, numpy_datetime_units='us', docstring='Collection Start date and time (UTC). Time reference used for times ' 'measured from collection start (i.e. slow time t = 0). For bistatic ' 'collections, the time is the transmit platform collection ' 'start time. The default display precision is microseconds, but this ' 'does not that accuracy in value.') # type: numpy.datetime64 CollectDuration = FloatDescriptor( 'CollectDuration', _required, strict=DEFAULT_STRICT, docstring='The duration of the collection, in seconds.') # type: float TxTime1 = FloatDescriptor( 'TxTime1', _required, strict=DEFAULT_STRICT, bounds=(0, None), docstring='Earliest TxTime value for any signal vector in the product. ' 'Time relative to Collection Start in seconds.') # type: float TxTime2 = FloatDescriptor( 'TxTime2', _required, strict=DEFAULT_STRICT, bounds=(0, None), docstring='Latest TxTime value for any signal vector in the product. ' 'Time relative to Collection Start in seconds.') # type: float ImageArea = SerializableDescriptor( 'ImageArea', ImageAreaType, _required, strict=DEFAULT_STRICT, docstring='Parameters describing the ground area covered by this ' 'product.') # type: ImageAreaType def __init__(self, DomainType=None, PhaseSGN=None, RefFreqIndex=None, CollectStart=None, CollectDuration=None, TxTime1=None, TxTime2=None, ImageArea=None, **kwargs): """ Parameters ---------- DomainType : str PhaseSGN : int RefFreqIndex : None|int CollectStart : numpy.datetime64|datetime.datetime|str CollectDuration : float TxTime1 : float TxTime2 : float ImageArea : ImageAreaType kwargs """ if '_xml_ns' in kwargs: self._xml_ns = kwargs['_xml_ns'] if '_xml_ns_key' in kwargs: self._xml_ns_key = kwargs['_xml_ns_key'] self.DomainType = DomainType self.PhaseSGN = PhaseSGN self.RefFreqIndex = RefFreqIndex self.CollectStart = CollectStart self.CollectDuration = CollectDuration self.TxTime1 = TxTime1 self.TxTime2 = TxTime2 self.ImageArea = ImageArea super(GlobalType, self).__init__(**kwargs)
class GeoDataType(Serializable): """Container specifying the image coverage area in geographic coordinates.""" _fields = ('EarthModel', 'SCP', 'ImageCorners', 'ValidData') _required = ('EarthModel', 'SCP', 'ImageCorners') _collections_tags = { 'ValidData': {'array': True, 'child_tag': 'Vertex'}, 'ImageCorners': {'array': True, 'child_tag': 'ICP'}, } # other class variables _EARTH_MODEL_VALUES = ('WGS_84', ) # descriptors EarthModel = StringEnumDescriptor( 'EarthModel', _EARTH_MODEL_VALUES, _required, strict=True, default_value='WGS_84', docstring='The Earth Model.') # type: str SCP = SerializableDescriptor( 'SCP', SCPType, _required, strict=DEFAULT_STRICT, docstring='The Scene Center Point *(SCP)* in full (global) image. This is the ' 'precise location.') # type: SCPType ImageCorners = SerializableCPArrayDescriptor( 'ImageCorners', LatLonCornerStringType, _collections_tags, _required, strict=DEFAULT_STRICT, docstring='The geographic image corner points array. Image corners points projected to the ' 'ground/surface level. Points may be projected to the same height as the SCP if ground/surface ' 'height data is not available. The corner positions are approximate geographic locations and ' 'not intended for analytical use.') # type: Union[SerializableCPArray, List[LatLonCornerStringType]] ValidData = SerializableArrayDescriptor( 'ValidData', LatLonArrayElementType, _collections_tags, _required, strict=DEFAULT_STRICT, minimum_length=3, docstring='The full image array includes both valid data and some zero filled pixels.' ) # type: Union[SerializableArray, List[LatLonArrayElementType]] def __init__(self, EarthModel='WGS_84', SCP=None, ImageCorners=None, ValidData=None, GeoInfos=None, **kwargs): """ Parameters ---------- EarthModel : str SCP : SCPType ImageCorners : SerializableCPArray|List[LatLonCornerStringType]|numpy.ndarray|list|tuple ValidData : SerializableArray|List[LatLonArrayElementType]|numpy.ndarray|list|tuple GeoInfos : List[GeoInfoType] kwargs : dict """ if '_xml_ns' in kwargs: self._xml_ns = kwargs['_xml_ns'] if '_xml_ns_key' in kwargs: self._xml_ns_key = kwargs['_xml_ns_key'] self.EarthModel = EarthModel self.SCP = SCP self.ImageCorners = ImageCorners self.ValidData = ValidData self._GeoInfos = [] if GeoInfos is None: pass elif isinstance(GeoInfos, GeoInfoType): self.setGeoInfo(GeoInfos) elif isinstance(GeoInfos, (list, tuple)): for el in GeoInfos: self.setGeoInfo(el) else: raise ValueError('GeoInfos got unexpected type {}'.format(type(GeoInfos))) super(GeoDataType, self).__init__(**kwargs) def derive(self): """ Populates any potential derived data in GeoData. Is expected to be called by the `SICD` parent as part of a more extensive derived data effort. Returns ------- None """ pass @property def GeoInfos(self): """ List[GeoInfoType]: list of GeoInfos. """ return self._GeoInfos def getGeoInfo(self, key): """ Get the GeoInfo(s) with name attribute == `key` Parameters ---------- key : str Returns ------- List[GeoInfoType] """ return [entry for entry in self._GeoInfos if entry.name == key] def setGeoInfo(self, value): """ Add the given GeoInfo to the GeoInfos list. Parameters ---------- value : GeoInfoType Returns ------- None """ if isinstance(value, ElementTree.Element): gi_key = self._child_xml_ns_key.get('GeoInfos', self._xml_ns_key) value = GeoInfoType.from_node(value, self._xml_ns, ns_key=gi_key) elif isinstance(value, dict): value = GeoInfoType.from_dict(value) if isinstance(value, GeoInfoType): self._GeoInfos.append(value) else: raise TypeError('Trying to set GeoInfo element with unexpected type {}'.format(type(value))) @classmethod def from_node(cls, node, xml_ns, ns_key=None, kwargs=None): if kwargs is None: kwargs = OrderedDict() gi_key = cls._child_xml_ns_key.get('GeoInfos', ns_key) kwargs['GeoInfos'] = find_children(node, 'GeoInfo', xml_ns, gi_key) return super(GeoDataType, cls).from_node(node, xml_ns, ns_key=ns_key, kwargs=kwargs) def to_node(self, doc, tag, ns_key=None, parent=None, check_validity=False, strict=DEFAULT_STRICT, exclude=()): node = super(GeoDataType, self).to_node( doc, tag, ns_key=ns_key, parent=parent, check_validity=check_validity, strict=strict, exclude=exclude) # slap on the GeoInfo children for entry in self._GeoInfos: entry.to_node(doc, 'GeoInfo', ns_key=ns_key, parent=node, strict=strict) return node def to_dict(self, check_validity=False, strict=DEFAULT_STRICT, exclude=()): out = super(GeoDataType, self).to_dict(check_validity=check_validity, strict=strict, exclude=exclude) # slap on the GeoInfo children if len(self.GeoInfos) > 0: out['GeoInfos'] = [entry.to_dict(check_validity=check_validity, strict=strict) for entry in self._GeoInfos] return out def _basic_validity_check(self): condition = super(GeoDataType, self)._basic_validity_check() return condition
class DetailImageInfoType(Serializable): _fields = ('DataFilename', 'ClassificationMarkings', 'Filetype', 'DataCheckSum', 'DataSize', 'DataPlane', 'DataDomain', 'DataType', 'BitsPerSample', 'DataFormat', 'DataByteOrder', 'NumPixels', 'ImageCollectionDate', 'ZuluOffset', 'SensorReferencePoint', 'SensorCalibrationFactor', 'DataCalibrated', 'Resolution', 'PixelSpacing', 'WeightingType', 'OverSamplingFactor', 'Width_3dB', 'ImageQualityDescription', 'ImageHeading', 'ImageCorners', 'SlantPlane', 'GroundPlane', 'SceneCenterReferenceLine') _required = ('DataFilename', 'ClassificationMarkings', 'DataPlane', 'DataType', 'DataFormat', 'NumPixels', 'ImageCollectionDate', 'SensorReferencePoint', 'Resolution', 'PixelSpacing', 'WeightingType', 'ImageCorners') _tag_overide = {'Width_3dB': '_3dBWidth'} # descriptors DataFilename = StringDescriptor( 'DataFilename', _required, docstring='The base file name to which this information pertains' ) # type: str ClassificationMarkings = SerializableDescriptor( 'ClassificationMarkings', ClassificationMarkingsType, _required, docstring='The classification information' ) # type: ClassificationMarkingsType Filetype = StringDescriptor( 'Filetype', _required, docstring='The image file type') # type: Optional[str] DataCheckSum = StringDescriptor( 'DataCheckSum', _required, docstring='The unique 32-bit identifier for the sensor block data' ) # type: Optional[str] DataSize = IntegerDescriptor( 'DataSize', _required, docstring='The image size in bytes') # type: Optional[int] DataPlane = StringEnumDescriptor('DataPlane', {'Slant', 'Ground'}, _required, default_value='Slant', docstring='The image plane.') # type: str DataDomain = StringDescriptor( 'DataDomain', _required, docstring='The image data domain') # type: Optional[str] DataType = StringDescriptor( 'DataType', _required, docstring='The image data type') # type: Optional[str] BitsPerSample = IntegerDescriptor( 'BitsPerSample', _required, docstring='The number of bits per sample') # type: Optional[int] DataFormat = StringDescriptor( 'DataFormat', _required, docstring='The image data format') # type: str DataByteOrder = StringEnumDescriptor( 'DataByteOrder', {'Big-Endian', 'Little-Endian'}, _required, docstring='The image data byte order.') # type: Optional[str] NumPixels = SerializableDescriptor( 'NumPixels', NumPixelsType, _required, docstring='The number of image pixels') # type: NumPixelsType ImageCollectionDate = DateTimeDescriptor( 'ImageCollectionDate', _required, docstring='The date/time of the image collection in UTC' ) # type: Optional[numpy.datetime64] ZuluOffset = IntegerDescriptor( 'ZuluOffset', _required, docstring='The local time offset from UTC' ) # type: Optional[int] # TODO: this isn't always integer SensorReferencePoint = StringEnumDescriptor( 'DataPlane', {'Left', 'Right', 'Top', 'Bottom'}, _required, docstring='Description of the sensor location relative to the scene.' ) # type: Optional[str] SensorCalibrationFactor = FloatDescriptor( 'SensorCalibrationFactor', _required, docstring= 'Multiplicative factor used to scale raw image data to the return ' 'of a calibrated reference reflector or active source' ) # type: Optional[float] DataCalibrated = StringDescriptor( 'DataCalibrated', _required, docstring='Has the data been calibrated?' ) # type: Optional[str] # TODO: this obviously should be a xs:boolean Resolution = SerializableDescriptor( 'Resolution', RangeCrossRangeType, _required, docstring='Resolution (intrinsic) of the sensor system/mode in meters.' ) # type: RangeCrossRangeType PixelSpacing = SerializableDescriptor( 'PixelSpacing', RangeCrossRangeType, _required, docstring='Pixel spacing of the image in meters.' ) # type: RangeCrossRangeType WeightingType = SerializableDescriptor( 'WeightingType', StringRangeCrossRangeType, _required, docstring='Weighting function applied to the image during formation.' ) # type: StringRangeCrossRangeType OverSamplingFactor = SerializableDescriptor( 'OverSamplingFactor', RangeCrossRangeType, _required, docstring='The factor by which the pixel space is oversampled.' ) # type: Optional[RangeCrossRangeType] Width_3dB = SerializableDescriptor( 'Width_3dB', RangeCrossRangeType, _required, docstring='The 3 dB system impulse response with, in meters' ) # type: Optional[RangeCrossRangeType] ImageQualityDescription = StringDescriptor( 'ImageQualityDescription', _required, docstring='General description of image quality' ) # type: Optional[str] ImageHeading = FloatDescriptor( 'ImageHeading', _required, docstring='Image heading relative to True North, in degrees' ) # type: Optional[float] ImageCorners = SerializableDescriptor( 'ImageCorners', ImageCornerType, _required, docstring='The image corners') # type: ImageCornerType SlantPlane = SerializableDescriptor( 'SlantPlane', PixelSpacingType, _required, docstring='The slant plane pixel spacing' ) # type: Optional[PixelSpacingType] GroundPlane = SerializableDescriptor( 'GroundPlane', PixelSpacingType, _required, docstring='The ground plane pixel spacing' ) # type: Optional[PixelSpacingType] SceneCenterReferenceLine = FloatDescriptor( 'SceneCenterReferenceLine', _required, docstring='The ideal line (heading) at the intersection of the radar ' 'line-of-sight with the horizontal reference plane ' 'created by the forward motion of the aircraft, ' 'in degrees') # type: Optional[float] def __init__(self, DataFilename=None, ClassificationMarkings=None, FileType=None, DataCheckSum=None, DataSize=None, DataPlane='Slant', DataDomain=None, DataType=None, BitsPerSample=None, DataFormat=None, DataByteOrder=None, NumPixels=None, ImageCollectionDate=None, ZuluOffset=None, SensorReferencePoint=None, SensorCalibrationFactor=None, DataCalibrated=None, Resolution=None, PixelSpacing=None, WeightingType=None, OverSamplingFactor=None, Width_3dB=None, ImageQualityDescription=None, ImageHeading=None, ImageCorners=None, SlantPlane=None, GroundPlane=None, SceneCenterReferenceLine=None, **kwargs): """ Parameters ---------- DataFilename : str ClassificationMarkings : ClassificationMarkingsType FileType : str DataCheckSum : None|str DataSize : int DataPlane : str DataDomain : None|str DataType : None|str BitsPerSample : None|int DataFormat : None|str DataByteOrder : None|str NumPixels : NumPixelsType|numpy.ndarray|list|tuple ImageCollectionDate : numpy.datetime64|datetime|date|str ZuluOffset : None|int SensorReferencePoint : None|str SensorCalibrationFactor : None|float DataCalibrated : None|str Resolution : RangeCrossRangeType|numpy.ndarray|list|tuple PixelSpacing : RangeCrossRangeType|numpy.ndarray|list|tuple WeightingType : StringRangeCrossRangeType OverSamplingFactor : None|RangeCrossRangeType Width_3dB : None|RangeCrossRangeType|numpy.ndarray|list|tuple ImageQualityDescription : None|str ImageHeading : None|float ImageCorners : ImageCornerType SlantPlane : None|PixelSpacingType GroundPlane : None|PixelSpacingType SceneCenterReferenceLine : None|float kwargs Other keyword arguments """ if '_xml_ns' in kwargs: self._xml_ns = kwargs['_xml_ns'] if '_xml_ns_key' in kwargs: self._xml_ns_key = kwargs['_xml_ns_key'] self.DataFilename = DataFilename if ClassificationMarkings is None: self.ClassificationMarkings = ClassificationMarkingsType() else: self.ClassificationMarkings = ClassificationMarkings self.Filetype = FileType self.DataCheckSum = DataCheckSum self.DataSize = DataSize self.DataPlane = DataPlane self.DataDomain = DataDomain self.DataType = DataType self.BitsPerSample = BitsPerSample self.DataFormat = DataFormat self.DataByteOrder = DataByteOrder self.NumPixels = NumPixels self.ImageCollectionDate = ImageCollectionDate self.ZuluOffset = ZuluOffset self.SensorReferencePoint = SensorReferencePoint self.SensorCalibrationFactor = SensorCalibrationFactor self.DataCalibrated = DataCalibrated self.Resolution = Resolution self.PixelSpacing = PixelSpacing self.WeightingType = WeightingType self.OverSamplingFactor = OverSamplingFactor self.Width_3dB = Width_3dB self.ImageQualityDescription = ImageQualityDescription self.ImageHeading = ImageHeading self.ImageCorners = ImageCorners self.SlantPlane = SlantPlane self.GroundPlane = GroundPlane self.SceneCenterReferenceLine = SceneCenterReferenceLine super(DetailImageInfoType, self).__init__(**kwargs) @classmethod def from_sicd(cls, sicd, base_file_name, file_type='NITF02.10'): """ Construct the ImageInfo from the sicd object and given image file name. Parameters ---------- sicd : SICDType base_file_name : str file_type : str The file type. This should probably always be NITF02.10 for now. Returns ------- DetailImageInfoType """ pixel_type = sicd.ImageData.PixelType if pixel_type == 'RE32F_IM32F': data_type = 'in-phase/quadrature' bits_per_sample = 32 data_format = 'float' elif pixel_type == 'RE16I_IM16I': data_type = 'in-phase/quadrature' bits_per_sample = 16 data_format = 'integer' elif pixel_type == 'AMP8I_PHS8I': data_type = 'magnitude-phase' bits_per_sample = 8 data_format = 'unsigned integer' else: raise ValueError('Unhandled') icps = ImageCornerType(UpperLeft=sicd.GeoData.ImageCorners.FRFC, UpperRight=sicd.GeoData.ImageCorners.FRLC, LowerRight=sicd.GeoData.ImageCorners.LRLC, LowerLeft=sicd.GeoData.ImageCorners.LRFC) if sicd.Grid.ImagePlane == 'SLANT': data_plane = 'Slant' elif sicd.Grid.ImagePlane == 'Ground': data_plane = 'Ground' else: data_plane = None return DetailImageInfoType( DataFilename=base_file_name, ClassificationMarkings=ClassificationMarkingsType( Classification=sicd.CollectionInfo.Classification), FileType=file_type, DataPlane=data_plane, DataType=data_type, BitsPerSample=bits_per_sample, DataFormat=data_format, DataByteOrder='Big-Endian', NumPixels=(sicd.ImageData.NumRows, sicd.ImageData.NumCols), ImageCollectionDate=sicd.Timeline.CollectStart, SensorReferencePoint='Top', Resolution=(sicd.Grid.Row.ImpRespWid, sicd.Grid.Col.ImpRespWid), PixelSpacing=(sicd.Grid.Row.SS, sicd.Grid.Col.SS), WeightingType=StringRangeCrossRangeType( Range=sicd.Grid.Row.WgtType.WindowName, CrossRange=sicd.Grid.Col.WgtType.WindowName), Width_3dB=(sicd.Grid.Row.ImpRespWid, sicd.Grid.Col.ImpRespWid ), # TODO: I don't think that this is correct? ImageHeading=sicd.SCPCOA.AzimAng, ImageCorners=icps)
class RgAzCompType(Serializable): """ Parameters included for a Range, Doppler image. """ _fields = ('AzSF', 'KazPoly') _required = _fields _numeric_format = {'AzSF': '0.16G'} # descriptors AzSF = FloatDescriptor( 'AzSF', _required, strict=DEFAULT_STRICT, docstring= 'Scale factor that scales image coordinate az = ycol (meters) to a delta cosine of the ' 'Doppler Cone Angle at COA, *(in 1/m)*') # type: float KazPoly = SerializableDescriptor( 'KazPoly', Poly1DType, _required, strict=DEFAULT_STRICT, docstring= 'Polynomial function that yields azimuth spatial frequency *(Kaz = Kcol)* as a function of ' 'slow time ``(variable 1)``. That is ' r':math:`\text{Slow Time (sec)} \to \text{Azimuth spatial frequency (cycles/meter)}`. ' 'Time relative to collection start.') # type: Poly1DType def __init__(self, AzSF=None, KazPoly=None, **kwargs): """ Parameters ---------- AzSF : float KazPoly : Poly1DType|numpy.ndarray|list|tuple kwargs : dict """ if '_xml_ns' in kwargs: self._xml_ns = kwargs['_xml_ns'] if '_xml_ns_key' in kwargs: self._xml_ns_key = kwargs['_xml_ns_key'] self.AzSF = AzSF self.KazPoly = KazPoly super(RgAzCompType, self).__init__(**kwargs) def _derive_parameters(self, Grid, Timeline, SCPCOA): """ Expected to be called by the SICD object. Parameters ---------- Grid : sarpy.io.complex.sicd_elements.GridType Timeline : sarpy.io.complex.sicd_elements.TimelineType SCPCOA : sarpy.io.complex.sicd_elements.SCPCOA.SCPCOAType Returns ------- None """ look = SCPCOA.look az_sf = -look * numpy.sin(numpy.deg2rad( SCPCOA.DopplerConeAng)) / SCPCOA.SlantRange if self.AzSF is None: self.AzSF = az_sf elif abs(self.AzSF - az_sf) > 1e-3: logger.warning('The derived value for RgAzComp.AzSF is {},\n\t' 'while the current setting is {}.'.format( az_sf, self.AzSF)) if self.KazPoly is None: if Grid.Row.KCtr is not None and Timeline is not None and Timeline.IPP is not None and \ Timeline.IPP.size == 1 and Timeline.IPP[0].IPPPoly is not None and SCPCOA.SCPTime is not None: st_rate_coa = Timeline.IPP[0].IPPPoly.derivative_eval( SCPCOA.SCPTime, 1) krg_coa = Grid.Row.KCtr if Grid.Row is not None and Grid.Row.DeltaKCOAPoly is not None: krg_coa += Grid.Row.DeltaKCOAPoly.Coefs[0, 0] # Scale factor described in SICD spec delta_kaz_per_delta_v = \ look*krg_coa*norm(SCPCOA.ARPVel.get_array()) * \ numpy.sin(numpy.deg2rad(SCPCOA.DopplerConeAng))/(SCPCOA.SlantRange*st_rate_coa) self.KazPoly = Poly1DType(Coefs=delta_kaz_per_delta_v * Timeline.IPP[0].IPPPoly.Coefs)
class AntPatternType(Serializable): """ Parameter set that defines each one-way Antenna Pattern. """ _fields = ('Identifier', 'FreqZero', 'EBFreqShift', 'MLFreqDilation', 'GainZero', 'GainBSPoly', 'ArrayGPId', 'ElementGPId') _required = ('Identifier', 'FreqZero', 'EBFreqShift', 'MLFreqDilation', 'ArrayGPId', 'ElementGPId') _numeric_format = {'FreqZero': '0.16G', 'GainZero': '0.16G'} # descriptors Identifier = StringDescriptor( 'Identifier', _required, strict=DEFAULT_STRICT, docstring='String that uniquely identifies this Antenna Pattern' ) # type: str FreqZero = FloatDescriptor( 'FreqZero', _required, strict=DEFAULT_STRICT, docstring= 'The reference frequency value for which the patterns are computed.' ) # type: float EBFreqShift = BooleanDescriptor( 'EBFreqShift', _required, strict=DEFAULT_STRICT, docstring= "Parameter indicating whether the electronic boresight shifts with " "frequency.") # type: bool MLFreqDilation = BooleanDescriptor( 'MLFreqDilation', _required, strict=DEFAULT_STRICT, docstring="Parameter indicating the mainlobe (ML) width changes with " "frequency.") # type: bool GainZero = FloatDescriptor( 'GainZero', _required, strict=DEFAULT_STRICT, docstring='The reference antenna gain at zero steering angle at the ' 'reference frequency, measured in dB.') # type: float GainBSPoly = SerializableDescriptor( 'GainBSPoly', Poly1DType, _required, strict=DEFAULT_STRICT, docstring= 'Gain polynomial *(in dB)* as a function of frequency for boresight *(BS)* ' 'at :math:`DCX=0, DCY=0`. ' 'Frequency ratio :math:`(f-f0)/f0` is the input variable, and the constant ' 'coefficient is always `0.0`.') # type: Poly1DType ArrayGPId = StringDescriptor( 'ArrayGPId', _required, strict=DEFAULT_STRICT, docstring= 'Support array identifier of the sampled gain/phase of the array ' 'at ref frequency.') # type: str ElementGPId = StringDescriptor( 'ElementGPId', _required, strict=DEFAULT_STRICT, docstring= 'Support array identifier of the sampled gain/phase of the element ' 'at ref frequency.') # type: str def __init__(self, Identifier=None, FreqZero=None, EBFreqShift=None, MLFreqDilation=None, GainZero=None, GainBSPoly=None, ArrayGPId=None, ElementGPId=None, **kwargs): """ Parameters ---------- Identifier : str FreqZero : float EBFreqShift : bool MLFreqDilation : bool GainZero : None|float GainBSPoly : None|Poly1DType ArrayGPId : str ElementGPId : str kwargs """ if '_xml_ns' in kwargs: self._xml_ns = kwargs['_xml_ns'] if '_xml_ns_key' in kwargs: self._xml_ns_key = kwargs['_xml_ns_key'] self.Identifier = Identifier self.FreqZero = FreqZero self.EBFreqShift = EBFreqShift self.MLFreqDilation = MLFreqDilation self.GainZero = GainZero self.GainBSPoly = GainBSPoly self.ArrayGPId = ArrayGPId self.ElementGPId = ElementGPId super(AntPatternType, self).__init__(**kwargs)
class ExploitationFeaturesCollectionPhenomenologyType(Serializable): """ Phenomenology related to both the geometry and the final product processing. All values computed at the center time of the full collection. """ _fields = ('Shadow', 'Layover', 'MultiPath', 'GroundTrack', 'Extensions') _required = () _collections_tags = { 'Extensions': { 'array': False, 'child_tag': 'Extension' } } _numeric_format = {'MultiPath': '0.16G', 'GroundTrack': '0.16G'} # Descriptor Shadow = SerializableDescriptor( 'Shadow', AngleMagnitudeType, _required, strict=DEFAULT_STRICT, docstring='The phenomenon where vertical objects occlude radar ' 'energy.') # type: Union[None, AngleMagnitudeType] Layover = SerializableDescriptor( 'Layover', AngleMagnitudeType, _required, strict=DEFAULT_STRICT, docstring= 'The phenomenon where vertical objects appear as ground objects with ' 'the same range/range rate.') # type: Union[None, AngleMagnitudeType] MultiPath = FloatModularDescriptor( 'MultiPath', 180.0, _required, strict=DEFAULT_STRICT, docstring= 'This is a range dependent phenomenon which describes the energy from a ' 'single scatter returned to the radar via more than one path and results ' 'in a nominally constant direction in the ETP.' ) # type: Union[None, float] GroundTrack = FloatModularDescriptor( 'GroundTrack', 180.0, _required, strict=DEFAULT_STRICT, docstring= 'Counter-clockwise angle from increasing row direction to ground track ' 'at the center of the image.') # type: Union[None, float] Extensions = ParametersDescriptor( 'Extensions', _collections_tags, _required, strict=DEFAULT_STRICT, docstring='Exploitation feature extension related to geometry for a ' 'single input image.') # type: ParametersCollection def __init__(self, Shadow=None, Layover=None, MultiPath=None, GroundTrack=None, Extensions=None, **kwargs): """ Parameters ---------- Shadow : None|AngleMagnitudeType|numpy.ndarray|list|tuple Layover : None|AngleMagnitudeType|numpy.ndarray|list|tuple MultiPath : None|float GroundTrack : None|float Extensions : None|ParametersCollection|dict kwargs """ if '_xml_ns' in kwargs: self._xml_ns = kwargs['_xml_ns'] if '_xml_ns_key' in kwargs: self._xml_ns_key = kwargs['_xml_ns_key'] self.Shadow = Shadow self.Layover = Layover self.MultiPath = MultiPath self.GroundTrack = GroundTrack self.Extensions = Extensions super(ExploitationFeaturesCollectionPhenomenologyType, self).__init__(**kwargs) @classmethod def from_calculator(cls, calculator): """ Create from an ExploitationCalculator object. Parameters ---------- calculator : ExploitationCalculator Returns ------- ExploitationFeaturesCollectionPhenomenologyType """ if not isinstance(calculator, ExploitationCalculator): raise TypeError( 'Requires input which is an instance of ExploitationCalculator, got type {}' .format(type(calculator))) return cls(Shadow=calculator.Shadow, Layover=calculator.Layover, MultiPath=calculator.MultiPath, GroundTrack=calculator.GroundTrack)
class SICDType(Serializable): """ Sensor Independent Complex Data object, containing all the relevant data to formulate products. """ _fields = ('CollectionInfo', 'ImageCreation', 'ImageData', 'GeoData', 'Grid', 'Timeline', 'Position', 'RadarCollection', 'ImageFormation', 'SCPCOA', 'Radiometric', 'Antenna', 'ErrorStatistics', 'MatchInfo', 'RgAzComp', 'PFA', 'RMA') _required = ('CollectionInfo', 'ImageData', 'GeoData', 'Grid', 'Timeline', 'Position', 'RadarCollection', 'ImageFormation', 'SCPCOA') _choice = ({'required': False, 'collection': ('RgAzComp', 'PFA', 'RMA')}, ) # descriptors CollectionInfo = SerializableDescriptor( 'CollectionInfo', CollectionInfoType, _required, strict=False, docstring='General information about the collection.' ) # type: CollectionInfoType ImageCreation = SerializableDescriptor( 'ImageCreation', ImageCreationType, _required, strict=False, docstring='General information about the image creation.' ) # type: ImageCreationType ImageData = SerializableDescriptor( 'ImageData', ImageDataType, _required, strict=False, # it is senseless to not have this element docstring='The image pixel data.') # type: ImageDataType GeoData = SerializableDescriptor( 'GeoData', GeoDataType, _required, strict=False, docstring='The geographic coordinates of the image coverage area.' ) # type: GeoDataType Grid = SerializableDescriptor( 'Grid', GridType, _required, strict=False, docstring='The image sample grid.') # type: GridType Timeline = SerializableDescriptor( 'Timeline', TimelineType, _required, strict=False, docstring='The imaging collection time line.') # type: TimelineType Position = SerializableDescriptor( 'Position', PositionType, _required, strict=False, docstring= 'The platform and ground reference point coordinates as a function of time.' ) # type: PositionType RadarCollection = SerializableDescriptor( 'RadarCollection', RadarCollectionType, _required, strict=False, docstring='The radar collection information.' ) # type: RadarCollectionType ImageFormation = SerializableDescriptor( 'ImageFormation', ImageFormationType, _required, strict=False, docstring='The image formation process.') # type: ImageFormationType SCPCOA = SerializableDescriptor( 'SCPCOA', SCPCOAType, _required, strict=False, docstring= '*Center of Aperture (COA)* for the *Scene Center Point (SCP)*.' ) # type: SCPCOAType Radiometric = SerializableDescriptor( 'Radiometric', RadiometricType, _required, strict=False, docstring='The radiometric calibration parameters.' ) # type: RadiometricType Antenna = SerializableDescriptor( 'Antenna', AntennaType, _required, strict=False, docstring= 'Parameters that describe the antenna illumination patterns during the collection.' ) # type: AntennaType ErrorStatistics = SerializableDescriptor( 'ErrorStatistics', ErrorStatisticsType, _required, strict=False, docstring= 'Parameters used to compute error statistics within the *SICD* sensor model.' ) # type: ErrorStatisticsType MatchInfo = SerializableDescriptor( 'MatchInfo', MatchInfoType, _required, strict=False, docstring='Information about other collections that are matched to the ' 'current collection. The current collection is the collection ' 'from which this *SICD* product was generated.') # type: MatchInfoType RgAzComp = SerializableDescriptor( 'RgAzComp', RgAzCompType, _required, strict=False, docstring='Parameters included for a *Range, Doppler* image.' ) # type: RgAzCompType PFA = SerializableDescriptor( 'PFA', PFAType, _required, strict=False, docstring='Parameters included when the image is formed using the ' '*Polar Formation Algorithm (PFA)*.') # type: PFAType RMA = SerializableDescriptor( 'RMA', RMAType, _required, strict=False, docstring='Parameters included when the image is formed using the ' '*Range Migration Algorithm (RMA)*.') # type: RMAType def __init__(self, CollectionInfo=None, ImageCreation=None, ImageData=None, GeoData=None, Grid=None, Timeline=None, Position=None, RadarCollection=None, ImageFormation=None, SCPCOA=None, Radiometric=None, Antenna=None, ErrorStatistics=None, MatchInfo=None, RgAzComp=None, PFA=None, RMA=None, **kwargs): """ Parameters ---------- CollectionInfo : CollectionInfoType ImageCreation : ImageCreationType ImageData : ImageDataType GeoData : GeoDataType Grid : GridType Timeline : TimelineType Position : PositionType RadarCollection : RadarCollectionType ImageFormation : ImageFormationType SCPCOA : SCPCOAType Radiometric : RadiometricType Antenna : AntennaType ErrorStatistics : ErrorStatisticsType MatchInfo : MatchInfoType RgAzComp : RgAzCompType PFA : PFAType RMA : RMAType kwargs : dict """ if '_xml_ns' in kwargs: self._xml_ns = kwargs['_xml_ns'] if '_xml_ns_key' in kwargs: self._xml_ns_key = kwargs['_xml_ns_key'] nitf = kwargs.get('_NITF', {}) if not isinstance(nitf, dict): raise TypeError( 'Provided NITF options are required to be in dictionary form.') self._NITF = nitf self._coa_projection = None self.CollectionInfo = CollectionInfo self.ImageCreation = ImageCreation self.ImageData = ImageData self.GeoData = GeoData self.Grid = Grid self.Timeline = Timeline self.Position = Position self.RadarCollection = RadarCollection self.ImageFormation = ImageFormation self.SCPCOA = SCPCOA self.Radiometric = Radiometric self.Antenna = Antenna self.ErrorStatistics = ErrorStatistics self.MatchInfo = MatchInfo self.RgAzComp = RgAzComp self.PFA = PFA self.RMA = RMA super(SICDType, self).__init__(**kwargs) @property def coa_projection(self): """ The COA Projection object, if previously defined through using :func:`define_coa_projection`. Returns ------- None|sarpy.geometry.point_projection.COAProjection """ return self._coa_projection @property def NITF(self): """ Optional dictionary of NITF header information, pertains only to subsequent SICD file writing. Returns ------- Dict """ return self._NITF @property def ImageFormType(self): # type: () -> str """ str: *READ ONLY* Identifies the specific image formation type supplied. This is determined by returning the (first) attribute among `RgAzComp`, `PFA`, `RMA` which is populated. `OTHER` will be returned if none of them are populated. """ for attribute in self._choice[0]['collection']: if getattr(self, attribute) is not None: return attribute return 'OTHER' def update_scp(self, point, coord_system='ECF'): """ Modify the SCP point, and modify the associated SCPCOA fields. Parameters ---------- point : numpy.ndarray|tuple|list coord_system : str Either 'ECF' or 'LLH', and 'ECF' will take precedence. Returns ------- None """ if isinstance(point, (list, tuple)): point = numpy.array(point, dtype='float64') if not isinstance(point, numpy.ndarray): raise TypeError('point must be an numpy.ndarray') if point.shape != (3, ): raise ValueError( 'point must be a one-dimensional, 3 element array') if coord_system == 'LLH': self.GeoData.SCP.LLH = point else: self.GeoData.SCP.ECF = point if self.SCPCOA is not None: self.SCPCOA.rederive(self.Grid, self.Position, self.GeoData) def _basic_validity_check(self): condition = super(SICDType, self)._basic_validity_check() condition &= detailed_validation_checks(self) return condition def is_valid(self, recursive=False, stack=False): all_required = self._basic_validity_check() if not recursive: return all_required valid_children = self._recursive_validity_check(stack=stack) return all_required & valid_children def define_geo_image_corners(self, override=False): """ Defines the GeoData image corner points (if possible), if they are not already defined. Returns ------- None """ if self.GeoData is None: self.GeoData = GeoDataType() if self.GeoData.ImageCorners is not None and not override: return # nothing to be done try: vertex_data = self.ImageData.get_full_vertex_data( dtype=numpy.float64) corner_coords = self.project_image_to_ground_geo(vertex_data) except (ValueError, AttributeError): return self.GeoData.ImageCorners = corner_coords def define_geo_valid_data(self): """ Defines the GeoData valid data corner points (if possible), if they are not already defined. Returns ------- None """ if self.GeoData is None or self.GeoData.ValidData is not None: return # nothing to be done try: valid_vertices = self.ImageData.get_valid_vertex_data( dtype=numpy.float64) if valid_vertices is not None: self.GeoData.ValidData = self.project_image_to_ground_geo( valid_vertices) except AttributeError: pass def derive(self): """ Populates any potential derived data in the SICD structure. This should get called after reading an XML, or as a user desires. Returns ------- None """ # Note that there is dependency in calling order between steps - don't naively rearrange the following. if self.SCPCOA is None: self.SCPCOA = SCPCOAType() # noinspection PyProtectedMember self.SCPCOA._derive_scp_time(self.Grid) if self.Grid is not None: # noinspection PyProtectedMember self.Grid._derive_time_coa_poly(self.CollectionInfo, self.SCPCOA) # noinspection PyProtectedMember self.SCPCOA._derive_position(self.Position) if self.Position is None and self.SCPCOA.ARPPos is not None and \ self.SCPCOA.ARPVel is not None and self.SCPCOA.SCPTime is not None: self.Position = PositionType( ) # important parameter derived in the next step if self.Position is not None: # noinspection PyProtectedMember self.Position._derive_arp_poly(self.SCPCOA) if self.GeoData is not None: self.GeoData.derive( ) # ensures both coordinate systems are defined for SCP if self.Grid is not None: # noinspection PyProtectedMember self.Grid.derive_direction_params(self.ImageData) if self.RadarCollection is not None: self.RadarCollection.derive() if self.ImageFormation is not None: # call after RadarCollection.derive(), and only if the entire transmitted bandwidth was used to process. # noinspection PyProtectedMember self.ImageFormation._derive_tx_frequency_proc(self.RadarCollection) # noinspection PyProtectedMember self.SCPCOA._derive_geometry_parameters(self.GeoData) # verify ImageFormation things make sense im_form_algo = None if self.ImageFormation is not None and self.ImageFormation.ImageFormAlgo is not None: im_form_algo = self.ImageFormation.ImageFormAlgo.upper() if im_form_algo == 'RGAZCOMP': # Check Grid settings if self.Grid is None: self.Grid = GridType() # noinspection PyProtectedMember self.Grid._derive_rg_az_comp(self.GeoData, self.SCPCOA, self.RadarCollection, self.ImageFormation) # Check RgAzComp settings if self.RgAzComp is None: self.RgAzComp = RgAzCompType() # noinspection PyProtectedMember self.RgAzComp._derive_parameters(self.Grid, self.Timeline, self.SCPCOA) elif im_form_algo == 'PFA': if self.PFA is None: self.PFA = PFAType() # noinspection PyProtectedMember self.PFA._derive_parameters(self.Grid, self.SCPCOA, self.GeoData, self.Position, self.Timeline) if self.Grid is not None: # noinspection PyProtectedMember self.Grid._derive_pfa(self.GeoData, self.RadarCollection, self.ImageFormation, self.Position, self.PFA) elif im_form_algo == 'RMA' or self.RMA is not None: if self.RMA is not None: # noinspection PyProtectedMember self.RMA._derive_parameters(self.SCPCOA, self.Position, self.RadarCollection, self.ImageFormation) if self.Grid is not None: # noinspection PyProtectedMember self.Grid._derive_rma(self.RMA, self.GeoData, self.RadarCollection, self.ImageFormation, self.Position) self.define_geo_image_corners() self.define_geo_valid_data() if self.Radiometric is not None: # noinspection PyProtectedMember self.Radiometric._derive_parameters(self.Grid, self.SCPCOA) def get_transmit_band_name(self): """ Gets the processed transmit band name. Returns ------- str """ if self.ImageFormation is None: return 'UN' return self.ImageFormation.get_transmit_band_name() def get_processed_polarization_abbreviation(self): """ Gets the processed polarization abbreviation (two letters). Returns ------- str """ if self.ImageFormation is None: return 'UN' return self.ImageFormation.get_polarization_abbreviation() def get_processed_polarization(self): """ Gets the processed polarization. Returns ------- str """ if self.ImageFormation is None: return 'UN' return self.ImageFormation.get_polarization() def apply_reference_frequency(self, reference_frequency): """ If the reference frequency is used, adjust the necessary fields accordingly. Parameters ---------- reference_frequency : float The reference frequency. Returns ------- None """ if self.RadarCollection is None: raise ValueError( 'RadarCollection is not defined. The reference frequency cannot be applied.' ) elif not self.RadarCollection.RefFreqIndex: # it's None or 0 raise ValueError( 'RadarCollection.RefFreqIndex is not defined. The reference frequency should not be applied.' ) # noinspection PyProtectedMember self.RadarCollection._apply_reference_frequency(reference_frequency) if self.ImageFormation is not None: # noinspection PyProtectedMember self.ImageFormation._apply_reference_frequency(reference_frequency) if self.Antenna is not None: # noinspection PyProtectedMember self.Antenna._apply_reference_frequency(reference_frequency) if self.RMA is not None: # noinspection PyProtectedMember self.RMA._apply_reference_frequency(reference_frequency) def get_ground_resolution(self): """ Gets the ground resolution for the sicd. Returns ------- (float, float) """ graze = numpy.deg2rad(self.SCPCOA.GrazeAng) twist = numpy.deg2rad(self.SCPCOA.TwistAng) row_ss = self.Grid.Row.SS col_ss = self.Grid.Col.SS row_ground = abs(float(row_ss / numpy.cos(graze))) col_ground = float( numpy.sqrt((numpy.tan(graze) * numpy.tan(twist) * row_ss)**2 + (col_ss / numpy.cos(twist))**2)) return row_ground, col_ground def can_project_coordinates(self): """ Determines whether the necessary elements are populated to permit projection between image and physical coordinates. If False, then the (first discovered) reason why not will be logged at error level. Returns ------- bool """ if self._coa_projection is not None: return True # GeoData elements? if self.GeoData is None: logger.error( 'Formulating a projection is not feasible because GeoData is not populated.' ) return False if self.GeoData.SCP is None: logger.error( 'Formulating a projection is not feasible because GeoData.SCP is not populated.' ) return False if self.GeoData.SCP.ECF is None: logger.error( 'Formulating a projection is not feasible because GeoData.SCP.ECF is not populated.' ) return False # ImageData elements? if self.ImageData is None: logger.error( 'Formulating a projection is not feasible because ImageData is not populated.' ) return False if self.ImageData.FirstRow is None: logger.error( 'Formulating a projection is not feasible because ImageData.FirstRow is not populated.' ) return False if self.ImageData.FirstCol is None: logger.error( 'Formulating a projection is not feasible because ImageData.FirstCol is not populated.' ) return False if self.ImageData.SCPPixel is None: logger.error( 'Formulating a projection is not feasible because ImageData.SCPPixel is not populated.' ) return False if self.ImageData.SCPPixel.Row is None: logger.error( 'Formulating a projection is not feasible because ImageData.SCPPixel.Row is not populated.' ) return False if self.ImageData.SCPPixel.Col is None: logger.error( 'Formulating a projection is not feasible because ImageData.SCPPixel.Col is not populated.' ) return False # Position elements? if self.Position is None: logger.error( 'Formulating a projection is not feasible because Position is not populated.' ) return False if self.Position.ARPPoly is None: logger.error( 'Formulating a projection is not feasible because Position.ARPPoly is not populated.' ) return False # Grid elements? if self.Grid is None: logger.error( 'Formulating a projection is not feasible because Grid is not populated.' ) return False if self.Grid.TimeCOAPoly is None: logger.warning( 'Formulating a projection may be inaccurate, because Grid.TimeCOAPoly is not populated and ' 'a constant approximation will be used.') if self.Grid.Row is None: logger.error( 'Formulating a projection is not feasible because Grid.Row is not populated.' ) return False if self.Grid.Row.SS is None: logger.error( 'Formulating a projection is not feasible because Grid.Row.SS is not populated.' ) return False if self.Grid.Col is None: logger.error( 'Formulating a projection is not feasible because Grid.Col is not populated.' ) return False if self.Grid.Col.SS is None: logger.error( 'Formulating a projection is not feasible because Grid.Col.SS is not populated.' ) return False if self.Grid.Type is None: logger.error( 'Formulating a projection is not feasible because Grid.Type is not populated.' ) return False # specifics for Grid.Type value if self.Grid.Type == 'RGAZIM': if self.ImageFormation is None: logger.error( 'Formulating a projection is not feasible because Grid.Type = "RGAZIM",\n\t' 'but ImageFormation is not populated.') return False if self.ImageFormation.ImageFormAlgo is None: logger.error( 'Formulating a projection is not feasible because Grid.Type = "RGAZIM",\n\t' 'but ImageFormation.ImageFormAlgo is not populated.') return False if self.ImageFormation.ImageFormAlgo == 'PFA': if self.PFA is None: logger.error('ImageFormation.ImageFormAlgo is "PFA",\n\t' 'but the PFA parameter is not populated.\n\t' 'No projection can be done.') return False if self.PFA.PolarAngPoly is None: logger.error( 'ImageFormation.ImageFormAlgo is "PFA",\n\t' 'but the PFA.PolarAngPoly parameter is not populated.\n\t' 'No projection can be done.') return False if self.PFA.SpatialFreqSFPoly is None: logger.error( 'ImageFormation.ImageFormAlgo is "PFA",\n\t' 'but the PFA.SpatialFreqSFPoly parameter is not populated.\n\t' 'No projection can be done.') return False elif self.ImageFormation.ImageFormAlgo == 'RGAZCOMP': if self.RgAzComp is None: logger.error( 'ImageFormation.ImageFormAlgo is "RGAZCOMP",\n\t' 'but the RgAzComp parameter is not populated.\n\t' 'No projection can be done.') return False if self.RgAzComp.AzSF is None: logger.error( 'ImageFormation.ImageFormAlgo is "RGAZCOMP",\n\t' 'but the RgAzComp.AzSF parameter is not populated.\n\t' 'No projection can be done.') return False else: logger.error( 'Grid.Type = "RGAZIM", and got unhandled ImageFormation.ImageFormAlgo {}.\n\t' 'No projection can be done.'.format( self.ImageFormation.ImageFormAlgo)) return False elif self.Grid.Type == 'RGZERO': if self.RMA is None or self.RMA.INCA is None: logger.error( 'Grid.Type is "RGZERO", but the RMA.INCA parameter is not populated.\n\t' 'No projection can be done.') return False if self.RMA.INCA.R_CA_SCP is None or self.RMA.INCA.TimeCAPoly is None \ or self.RMA.INCA.DRateSFPoly is None: logger.error( 'Grid.Type is "RGZERO", but the parameters\n\t' 'R_CA_SCP, TimeCAPoly, or DRateSFPoly of RMA.INCA parameter are not populated.\n\t' 'No projection can be done.') return False elif self.Grid.Type in ['XRGYCR', 'XCTYAT', 'PLANE']: if self.Grid.Row.UVectECF is None or self.Grid.Col.UVectECF is None: logger.error( 'Grid.Type is one of ["XRGYCR", "XCTYAT", "PLANE"], but the UVectECF parameter of ' 'Grid.Row or Grid.Col is not populated.\n\t' 'No projection can be formulated.') return False else: logger.error('Unhandled Grid.Type {},\n\t' 'unclear how to formulate a projection.'.format( self.Grid.Type)) return False # logger.info('Consider calling sicd.define_coa_projection if the sicd structure is defined.') return True def define_coa_projection(self, delta_arp=None, delta_varp=None, range_bias=None, adj_params_frame='ECF', overide=True): """ Define the COAProjection object. Parameters ---------- delta_arp : None|numpy.ndarray|list|tuple ARP position adjustable parameter (ECF, m). Defaults to 0 in each coordinate. delta_varp : None|numpy.ndarray|list|tuple VARP position adjustable parameter (ECF, m/s). Defaults to 0 in each coordinate. range_bias : float|int Range bias adjustable parameter (m), defaults to 0. adj_params_frame : str One of ['ECF', 'RIC_ECF', 'RIC_ECI'], specifying the coordinate frame used for expressing `delta_arp` and `delta_varp` parameters. overide : bool should we redefine, if it is previously defined? Returns ------- None """ if not self.can_project_coordinates(): logger.error('The COAProjection object cannot be defined.') return if self._coa_projection is not None and not overide: return self._coa_projection = point_projection.COAProjection.from_sicd( self, delta_arp=delta_arp, delta_varp=delta_varp, range_bias=range_bias, adj_params_frame=adj_params_frame) def project_ground_to_image(self, coords, **kwargs): """ Transforms a 3D ECF point to pixel (row/column) coordinates. This is implemented in accordance with the SICD Image Projections Description Document. **Really Scene-To-Image projection.**" Parameters ---------- coords : numpy.ndarray|tuple|list ECF coordinate to map to scene coordinates, of size `N x 3`. kwargs The keyword arguments for the :func:`sarpy.geometry.point_projection.ground_to_image` method. Returns ------- Tuple[numpy.ndarray, float, int] * `image_points` - the determined image point array, of size `N x 2`. Following the SICD convention, he upper-left pixel is [0, 0]. * `delta_gpn` - residual ground plane displacement (m). * `iterations` - the number of iterations performed. See Also -------- sarpy.geometry.point_projection.ground_to_image """ if 'use_structure_coa' not in kwargs: kwargs['use_structure_coa'] = True return point_projection.ground_to_image(coords, self, **kwargs) def project_ground_to_image_geo(self, coords, ordering='latlong', **kwargs): """ Transforms a 3D Lat/Lon/HAE point to pixel (row/column) coordinates. This is implemented in accordance with the SICD Image Projections Description Document. **Really Scene-To-Image projection.**" Parameters ---------- coords : numpy.ndarray|tuple|list ECF coordinate to map to scene coordinates, of size `N x 3`. ordering : str If 'longlat', then the input is `[longitude, latitude, hae]`. Otherwise, the input is `[latitude, longitude, hae]`. Passed through to :func:`sarpy.geometry.geocoords.geodetic_to_ecf`. kwargs The keyword arguments for the :func:`sarpy.geometry.point_projection.ground_to_image_geo` method. Returns ------- Tuple[numpy.ndarray, float, int] * `image_points` - the determined image point array, of size `N x 2`. Following the SICD convention, he upper-left pixel is [0, 0]. * `delta_gpn` - residual ground plane displacement (m). * `iterations` - the number of iterations performed. See Also -------- sarpy.geometry.point_projection.ground_to_image_geo """ if 'use_structure_coa' not in kwargs: kwargs['use_structure_coa'] = True return point_projection.ground_to_image_geo(coords, self, ordering=ordering, **kwargs) def project_image_to_ground(self, im_points, projection_type='HAE', **kwargs): """ Transforms image coordinates to ground plane ECF coordinate via the algorithm(s) described in SICD Image Projections document. Parameters ---------- im_points : numpy.ndarray|list|tuple the image coordinate array projection_type : str One of `['PLANE', 'HAE', 'DEM']`. Type `DEM` is a work in progress. kwargs The keyword arguments for the :func:`sarpy.geometry.point_projection.image_to_ground` method. Returns ------- numpy.ndarray Ground Plane Point (in ECF coordinates) corresponding to the input image coordinates. See Also -------- sarpy.geometry.point_projection.image_to_ground """ if 'use_structure_coa' not in kwargs: kwargs['use_structure_coa'] = True return point_projection.image_to_ground( im_points, self, projection_type=projection_type, **kwargs) def project_image_to_ground_geo(self, im_points, ordering='latlong', projection_type='HAE', **kwargs): """ Transforms image coordinates to ground plane WGS-84 coordinate via the algorithm(s) described in SICD Image Projections document. Parameters ---------- im_points : numpy.ndarray|list|tuple the image coordinate array projection_type : str One of `['PLANE', 'HAE', 'DEM']`. Type `DEM` is a work in progress. ordering : str Determines whether return is ordered as `[lat, long, hae]` or `[long, lat, hae]`. Passed through to :func:`sarpy.geometry.geocoords.ecf_to_geodetic`. kwargs The keyword arguments for the :func:`sarpy.geometry.point_projection.image_to_ground_geo` method. Returns ------- numpy.ndarray Ground Plane Point (in ECF coordinates) corresponding to the input image coordinates. See Also -------- sarpy.geometry.point_projection.image_to_ground_geo """ if 'use_structure_coa' not in kwargs: kwargs['use_structure_coa'] = True return point_projection.image_to_ground_geo( im_points, self, ordering=ordering, projection_type=projection_type, **kwargs) def populate_rniirs(self, signal=None, noise=None, override=False): """ Given the signal and noise values (in sigma zero power units), calculate and populate an estimated RNIIRS value. Parameters ---------- signal : None|float noise : None|float override : bool Override the value, if present. Returns ------- None """ from sarpy.processing.rgiqe import populate_rniirs_for_sicd populate_rniirs_for_sicd(self, signal=signal, noise=noise, override=override) def get_suggested_name(self, product_number=1): """ Get the suggested name stem for the sicd and derived data. Returns ------- str """ sugg_name = get_sicd_name(self, product_number) if sugg_name is not None: return sugg_name elif self.CollectionInfo.CoreName is not None: return self.CollectionInfo.CoreName return 'Unknown_Sicd{}'.format(product_number) def get_des_details(self, check_version1_compliance=False): """ Gets the correct current SICD DES subheader details. Parameters ---------- check_version1_compliance : bool If true and structure is compatible, the version 1.1 information will be returned. Otherwise, the most recent supported version will be returned . Returns ------- dict """ if check_version1_compliance and ( (self.ImageFormation is None or self.ImageFormation.permits_version_1_1()) and (self.RadarCollection is None or self.RadarCollection.permits_version_1_1())): spec_version = _SICD_SPECIFICATION_VERSION_1_1 spec_date = _SICD_SPECIFICATION_DATE_1_1 spec_ns = _SICD_SPECIFICATION_NAMESPACE_1_1 else: spec_version = _SICD_SPECIFICATION_VERSION_1_2 spec_date = _SICD_SPECIFICATION_DATE_1_2 spec_ns = _SICD_SPECIFICATION_NAMESPACE_1_2 return OrderedDict([('DESSHSI', _SICD_SPECIFICATION_IDENTIFIER), ('DESSHSV', spec_version), ('DESSHSD', spec_date), ('DESSHTN', spec_ns)]) def copy(self): """ Provides a deep copy. Returns ------- SICDType """ out = super(SICDType, self).copy() out._NITF = deepcopy(self._NITF) return out def to_xml_bytes(self, urn=None, tag='SICD', check_validity=False, strict=DEFAULT_STRICT): if urn is None: urn = _SICD_SPECIFICATION_NAMESPACE_1_2 return super(SICDType, self).to_xml_bytes(urn=urn, tag=tag, check_validity=check_validity, strict=strict) def to_xml_string(self, urn=None, tag='SICD', check_validity=False, strict=DEFAULT_STRICT): return self.to_xml_bytes(urn=urn, tag=tag, check_validity=check_validity, strict=strict).decode('utf-8') def create_subset_structure(self, row_bounds=None, column_bounds=None): """ Create a version of the SICD structure for a given subset. Parameters ---------- row_bounds : None|tuple column_bounds : None|tuple Returns ------- sicd : SICDType The sicd row_bounds : tuple Vetted tuple of the form `(min row, max row)`. column_bounds : tuple Vetted tuple of the form `(min column, max column)`. """ sicd = self.copy() num_rows = self.ImageData.NumRows num_cols = self.ImageData.NumCols if row_bounds is not None: start_row = int(row_bounds[0]) end_row = int(row_bounds[1]) if not (0 <= start_row < end_row <= num_rows): raise ValueError( 'row bounds ({}, {}) are not sensible for NumRows {}'. format(start_row, end_row, num_rows)) sicd.ImageData.FirstRow = sicd.ImageData.FirstRow + start_row sicd.ImageData.NumRows = (end_row - start_row) out_row_bounds = (start_row, end_row) else: out_row_bounds = (0, num_rows) if column_bounds is not None: start_col = int(column_bounds[0]) end_col = int(column_bounds[1]) if not (0 <= start_col < end_col <= num_cols): raise ValueError( 'column bounds ({}, {}) are not sensible for NumCols {}'. format(start_col, end_col, num_cols)) sicd.ImageData.FirstCol = sicd.ImageData.FirstCol + start_col sicd.ImageData.NumCols = (end_col - start_col) out_col_bounds = (start_col, end_col) else: out_col_bounds = (0, num_cols) sicd.define_geo_image_corners(override=True) return sicd, out_row_bounds, out_col_bounds
class ExploitationFeaturesProductType(Serializable): """ Metadata regarding the product. """ _fields = ('Resolution', 'Ellipticity', 'Polarizations', 'North', 'Extensions') _required = ('Resolution', 'Ellipticity', 'Polarizations') _collections_tags = { 'Polarizations': { 'array': False, 'child_tag': 'Polarization' }, 'Extensions': { 'array': False, 'child_tag': 'Extension' } } _numeric_format = {'Ellipticity': '0.16G', 'North': '0.16G'} # Descriptor Resolution = SerializableDescriptor( 'Resolution', RowColDoubleType, _required, strict=DEFAULT_STRICT, docstring= 'Uniformly-weighted resolution projected into the Earth Tangent ' 'Plane (ETP).') # type: RowColDoubleType Ellipticity = FloatDescriptor( 'Ellipticity', _required, strict=DEFAULT_STRICT, docstring= "Ellipticity of the 2D-IPR at the ORP, measured in the *Earth Geodetic " "Tangent Plane (EGTP)*. Ellipticity is the ratio of the IPR ellipse's " "major axis to minor axis.") # type: float Polarizations = SerializableListDescriptor( 'Polarizations', ProcTxRcvPolarizationType, _collections_tags, _required, strict=DEFAULT_STRICT, docstring= 'Describes the processed transmit and receive polarizations for the ' 'product.') # type: List[ProcTxRcvPolarizationType] North = FloatModularDescriptor( 'North', 180.0, _required, strict=DEFAULT_STRICT, docstring= 'Counter-clockwise angle from increasing row direction to north at the center ' 'of the image.') # type: float Extensions = ParametersDescriptor( 'Extensions', _collections_tags, _required, strict=DEFAULT_STRICT, docstring='Exploitation feature extension related to geometry for a ' 'single input image.') # type: ParametersCollection def __init__(self, Resolution=None, Ellipticity=None, Polarizations=None, North=None, Extensions=None, **kwargs): """ Parameters ---------- Resolution : RowColDoubleType|numpy.ndarray|list|tuple Ellipticity : float Polarizations : List[ProcTxRcvPolarizationType] North : None|float Extensions : None|ParametersCollection|dict kwargs """ if '_xml_ns' in kwargs: self._xml_ns = kwargs['_xml_ns'] if '_xml_ns_key' in kwargs: self._xml_ns_key = kwargs['_xml_ns_key'] self.Resolution = Resolution self.Ellipticity = Ellipticity self.Polarizations = Polarizations self.North = North self.Extensions = Extensions super(ExploitationFeaturesProductType, self).__init__(**kwargs) @classmethod def from_calculator(cls, calculator, sicd): """ Construct from a sicd element. Parameters ---------- calculator : ExploitationCalculator sicd : SICDType Returns ------- ExploitationFeaturesProductType """ if not isinstance(sicd, SICDType): raise TypeError('Requires SICDType instance, got type {}'.format( type(sicd))) row_ground, col_ground = sicd.get_ground_resolution() ellipticity = row_ground / col_ground if row_ground >= col_ground else col_ground / row_ground return cls(Resolution=(row_ground, col_ground), Ellipticity=ellipticity, Polarizations=[ ProcTxRcvPolarizationType.from_sicd_value( sicd.ImageFormation.TxRcvPolarizationProc), ], North=calculator.North)
class CRSDType(Serializable): """ The Compensated Received Signal Data definition. """ _fields = ('CollectionID', 'Global', 'SceneCoordinates', 'Data', 'Channel', 'PVP', 'SupportArray', 'Dwell', 'ReferenceGeometry', 'Antenna', 'ErrorParameters', 'ProductInfo', 'GeoInfo', 'MatchInfo') _required = ('CollectionID', 'Global', 'Data', 'Channel', 'PVP', 'ReferenceGeometry') _collections_tags = {'GeoInfo': {'array': 'False', 'child_tag': 'GeoInfo'}} # descriptors CollectionID = SerializableDescriptor( 'CollectionID', CollectionIDType, _required, strict=DEFAULT_STRICT, docstring='General information about the collection.' ) # type: CollectionIDType Global = SerializableDescriptor( 'Global', GlobalType, _required, strict=DEFAULT_STRICT, docstring='Global parameters that apply to metadata components and CRSD ' 'signal arrays.') # type: GlobalType SceneCoordinates = SerializableDescriptor( 'SceneCoordinates', SceneCoordinatesType, _required, strict=DEFAULT_STRICT, docstring='Parameters that define geographic coordinates of the imaged ' 'scene.') # type: Union[None, SceneCoordinatesType] Data = SerializableDescriptor( 'Data', DataType, _required, strict=DEFAULT_STRICT, docstring='Parameters that describe binary data components contained in ' 'the product.') # type: DataType Channel = SerializableDescriptor( 'Channel', ChannelType, _required, strict=DEFAULT_STRICT, docstring='Parameters that describe the data channels contained in the ' 'product.') # type: ChannelType PVP = SerializableDescriptor( 'PVP', PVPType, _required, strict=DEFAULT_STRICT, docstring= 'Structure used to specify the Per Vector Parameters provided for ' 'each channel of a given product.') # type: PVPType SupportArray = SerializableDescriptor( 'SupportArray', SupportArrayType, _required, strict=DEFAULT_STRICT, docstring= 'Parameters that describe the binary support array(s) content and ' 'grid coordinates.') # type: Union[None, SupportArrayType] Dwell = SerializableDescriptor( 'Dwell', DwellType, _required, strict=DEFAULT_STRICT, docstring= 'Parameters that specify the SAR dwell time supported by the signal ' 'arrays contained in the CRSD product.' ) # type: Union[None, DwellType] ReferenceGeometry = SerializableDescriptor( 'ReferenceGeometry', ReferenceGeometryType, _required, strict=DEFAULT_STRICT, docstring= 'Parameters that describe the collection geometry for the reference ' 'vector of the reference channel.') # type: ReferenceGeometryType Antenna = SerializableDescriptor( 'Antenna', AntennaType, _required, strict=DEFAULT_STRICT, docstring='Parameters that describe the antennas antennas used ' 'to collect the signal array(s).') # type: Union[None, AntennaType] ErrorParameters = SerializableDescriptor( 'ErrorParameters', ErrorParametersType, _required, strict=DEFAULT_STRICT, docstring= 'Parameters that describe the statistics of errors in measured or estimated parameters ' 'that describe the collection.' ) # type: Union[None, ErrorParametersType] ProductInfo = SerializableDescriptor( 'ProductInfo', ProductInfoType, _required, strict=DEFAULT_STRICT, docstring= 'Parameters that provide general information about the CRSD product ' 'and/or the derived products that may be created ' 'from it.') # type: Union[None, ProductInfoType] MatchInfo = SerializableDescriptor( 'MatchInfo', MatchInfoType, _required, strict=DEFAULT_STRICT, docstring= 'Information about other collections that are matched to the collection from which ' 'this CRSD product was generated.') # type: Union[None, MatchInfoType] def __init__(self, CollectionID=None, Global=None, SceneCoordinates=None, Data=None, Channel=None, PVP=None, SupportArray=None, Dwell=None, ReferenceGeometry=None, Antenna=None, ErrorParameters=None, ProductInfo=None, GeoInfo=None, MatchInfo=None, **kwargs): """ Parameters ---------- CollectionID : CollectionIDType Global : GlobalType SceneCoordinates : None|SceneCoordinatesType Data : DataType Channel : ChannelType PVP : PVPType SupportArray : None|SupportArrayType Dwell : None|DwellType ReferenceGeometry : ReferenceGeometryType Antenna : None|AntennaType ErrorParameters : None|ErrorParametersType ProductInfo : None|ProductInfoType GeoInfo : None|List[GeoInfoType]|GeoInfoType MatchInfo : None|MatchInfoType kwargs """ if '_xml_ns' in kwargs: self._xml_ns = kwargs['_xml_ns'] if '_xml_ns_key' in kwargs: self._xml_ns_key = kwargs['_xml_ns_key'] self.CollectionID = CollectionID self.Global = Global self.SceneCoordinates = SceneCoordinates self.Data = Data self.Channel = Channel self.PVP = PVP self.SupportArray = SupportArray self.Dwell = Dwell self.ReferenceGeometry = ReferenceGeometry self.Antenna = Antenna self.ErrorParameters = ErrorParameters self.ProductInfo = ProductInfo self.MatchInfo = MatchInfo self._GeoInfo = [] if GeoInfo is None: pass elif isinstance(GeoInfo, GeoInfoType): self.addGeoInfo(GeoInfo) elif isinstance(GeoInfo, (list, tuple)): for el in GeoInfo: self.addGeoInfo(el) else: raise ValueError('GeoInfo got unexpected type {}'.format( type(GeoInfo))) super(CRSDType, self).__init__(**kwargs) @property def GeoInfo(self): """ List[GeoInfoType]: Parameters that describe a geographic feature. """ return self._GeoInfo def getGeoInfo(self, key): """ Get GeoInfo(s) with name attribute == `key`. Parameters ---------- key : str Returns ------- List[GeoInfoType] """ return [entry for entry in self._GeoInfo if entry.name == key] def addGeoInfo(self, value): """ Add the given GeoInfo to the GeoInfo list. Parameters ---------- value : GeoInfoType Returns ------- None """ if isinstance(value, ElementTree.Element): gi_key = self._child_xml_ns_key.get('GeoInfo', self._xml_ns_key) value = GeoInfoType.from_node(value, self._xml_ns, ns_key=gi_key) elif isinstance(value, dict): value = GeoInfoType.from_dict(value) if isinstance(value, GeoInfoType): self._GeoInfo.append(value) else: raise TypeError( 'Trying to set GeoInfo element with unexpected type {}'.format( type(value))) @classmethod def from_node(cls, node, xml_ns, ns_key=None, kwargs=None): if kwargs is None: kwargs = OrderedDict() gi_key = cls._child_xml_ns_key.get('GeoInfo', ns_key) kwargs['GeoInfo'] = find_children(node, 'GeoInfo', xml_ns, gi_key) return super(CRSDType, cls).from_node(node, xml_ns, ns_key=ns_key, kwargs=kwargs) def to_node(self, doc, tag, ns_key=None, parent=None, check_validity=False, strict=DEFAULT_STRICT, exclude=()): node = super(CRSDType, self).to_node(doc, tag, ns_key=ns_key, parent=parent, check_validity=check_validity, strict=strict, exclude=exclude + ('GeoInfo', )) # slap on the GeoInfo children if self._GeoInfo is not None and len(self._GeoInfo) > 0: for entry in self._GeoInfo: entry.to_node(doc, 'GeoInfo', ns_key=ns_key, parent=node, strict=strict) return node def to_dict(self, check_validity=False, strict=DEFAULT_STRICT, exclude=()): out = super(CRSDType, self).to_dict(check_validity=check_validity, strict=strict, exclude=exclude + ('GeoInfo', )) # slap on the GeoInfo children if len(self.GeoInfo) > 0: out['GeoInfo'] = [ entry.to_dict(check_validity=check_validity, strict=strict) for entry in self._GeoInfo ] return out def to_xml_bytes(self, urn=None, tag='CRSD', check_validity=False, strict=DEFAULT_STRICT): if urn is None: urn = _CRSD_SPECIFICATION_NAMESPACE return super(CRSDType, self).to_xml_bytes(urn=urn, tag=tag, check_validity=check_validity, strict=strict) def to_xml_string(self, urn=None, tag='CRSD', check_validity=False, strict=DEFAULT_STRICT): return self.to_xml_bytes(urn=urn, tag=tag, check_validity=check_validity, strict=strict).decode('utf-8') def make_file_header(self, xml_offset=1024): """ Forms a CRSD file header consistent with the information in the Data and CollectionID nodes. Parameters ---------- xml_offset : int, optional Offset in bytes to the first byte of the XML block. If the provided value is not large enough to account for the length of the file header string, a larger value is chosen. Returns ------- header : sarpy.io.phase_history.cphd1_elements.CRSD.CRSDHeader """ _kvps = OrderedDict() def _align(val): align_to = 64 return int(numpy.ceil(float(val) / align_to) * align_to) _kvps['XML_BLOCK_SIZE'] = len(self.to_xml_string()) _kvps['XML_BLOCK_BYTE_OFFSET'] = xml_offset block_end = _kvps['XML_BLOCK_BYTE_OFFSET'] + _kvps[ 'XML_BLOCK_SIZE'] + len(_CRSD_SECTION_TERMINATOR) if self.Data.NumSupportArrays > 0: _kvps[ 'SUPPORT_BLOCK_SIZE'] = self.Data.calculate_support_block_size( ) _kvps['SUPPORT_BLOCK_BYTE_OFFSET'] = _align(block_end) block_end = _kvps['SUPPORT_BLOCK_BYTE_OFFSET'] + _kvps[ 'SUPPORT_BLOCK_SIZE'] _kvps['PVP_BLOCK_SIZE'] = self.Data.calculate_pvp_block_size() _kvps['PVP_BLOCK_BYTE_OFFSET'] = _align(block_end) block_end = _kvps['PVP_BLOCK_BYTE_OFFSET'] + _kvps['PVP_BLOCK_SIZE'] _kvps['SIGNAL_BLOCK_SIZE'] = self.Data.calculate_signal_block_size() _kvps['SIGNAL_BLOCK_BYTE_OFFSET'] = _align(block_end) _kvps['CLASSIFICATION'] = self.CollectionID.Classification _kvps['RELEASE_INFO'] = self.CollectionID.ReleaseInfo header = CRSDHeader(**_kvps) header_str = header.to_string() min_xml_offset = len(header_str) + len(_CRSD_SECTION_TERMINATOR) if _kvps['XML_BLOCK_BYTE_OFFSET'] < min_xml_offset: header = self.make_file_header(xml_offset=_align(min_xml_offset + 32)) return header def get_pvp_dtype(self): """ Gets the dtype for the corresponding PVP structured array. Note that they must all have homogeneous dtype. Returns ------- numpy.dtype This will be a compound dtype for a structured array. """ if self.PVP is None: raise ValueError('No PVP defined.') return self.PVP.get_vector_dtype() @classmethod def from_xml_file(cls, file_path): """ Construct the crsd object from a stand-alone xml file path. Parameters ---------- file_path : str Returns ------- CRSDType """ root_node, xml_ns = parse_xml_from_file(file_path) ns_key = 'default' if 'default' in xml_ns else None return cls.from_node(root_node, xml_ns=xml_ns, ns_key=ns_key) @classmethod def from_xml_string(cls, xml_string): """ Construct the crsd object from an xml string. Parameters ---------- xml_string : str|bytes Returns ------- CRSDType """ root_node, xml_ns = parse_xml_from_string(xml_string) ns_key = 'default' if 'default' in xml_ns else None return cls.from_node(root_node, xml_ns=xml_ns, ns_key=ns_key)
class AntennaType(Serializable): """Parameters that describe the antenna illumination patterns during the collection.""" _fields = ('Tx', 'Rcv', 'TwoWay') _required = () # descriptors Tx = SerializableDescriptor( 'Tx', AntParamType, _required, strict=DEFAULT_STRICT, docstring='The transmit antenna parameters.') # type: AntParamType Rcv = SerializableDescriptor( 'Rcv', AntParamType, _required, strict=DEFAULT_STRICT, docstring='The receive antenna parameters.') # type: AntParamType TwoWay = SerializableDescriptor( 'TwoWay', AntParamType, _required, strict=DEFAULT_STRICT, docstring='The bidirectional transmit/receive antenna parameters.' ) # type: AntParamType def __init__(self, Tx=None, Rcv=None, TwoWay=None, **kwargs): """ Parameters ---------- Tx : AntParamType Rcv : AntParamType TwoWay : AntParamType kwargs : dict """ if '_xml_ns' in kwargs: self._xml_ns = kwargs['_xml_ns'] if '_xml_ns_key' in kwargs: self._xml_ns_key = kwargs['_xml_ns_key'] self.Tx, self.Rcv, self.TwoWay = Tx, Rcv, TwoWay super(AntennaType, self).__init__(**kwargs) def _apply_reference_frequency(self, reference_frequency): """ If the reference frequency is used, adjust the necessary fields accordingly. Expected to be called by SICD parent. Parameters ---------- reference_frequency : float The reference frequency. Returns ------- None """ if self.Tx is not None: # noinspection PyProtectedMember self.Tx._apply_reference_frequency(reference_frequency) if self.Rcv is not None: # noinspection PyProtectedMember self.Rcv._apply_reference_frequency(reference_frequency) if self.TwoWay is not None: # noinspection PyProtectedMember self.TwoWay._apply_reference_frequency(reference_frequency)
class RMAType(Serializable): """Parameters included when the image is formed using the Range Migration Algorithm.""" _fields = ('RMAlgoType', 'ImageType', 'RMAT', 'RMCR', 'INCA') _required = ('RMAlgoType', 'ImageType') _choice = ({'required': True, 'collection': ('RMAT', 'RMCR', 'INCA')}, ) # class variables _RM_ALGO_TYPE_VALUES = ('OMEGA_K', 'CSA', 'RG_DOP') # descriptors RMAlgoType = StringEnumDescriptor( 'RMAlgoType', _RM_ALGO_TYPE_VALUES, _required, strict=DEFAULT_STRICT, docstring=r""" Identifies the type of migration algorithm used: * `OMEGA_K` - Algorithms that employ Stolt interpolation of the Kxt dimension. :math:`Kx = \sqrt{Kf^2 - Ky^2}` * `CSA` - Wave number algorithm that process two-dimensional chirp signals. * `RG_DOP` - Range-Doppler algorithms that employ *RCMC* in the compressed range domain. """) # type: str RMAT = SerializableDescriptor( 'RMAT', RMRefType, _required, strict=DEFAULT_STRICT, docstring='Parameters for *RMA with Along Track (RMAT)* motion compensation.') # type: RMRefType RMCR = SerializableDescriptor( 'RMCR', RMRefType, _required, strict=DEFAULT_STRICT, docstring='Parameters for *RMA with Cross Range (RMCR)* motion compensation.') # type: RMRefType INCA = SerializableDescriptor( 'INCA', INCAType, _required, strict=DEFAULT_STRICT, docstring='Parameters for *Imaging Near Closest Approach (INCA)* image description.') # type: INCAType def __init__(self, RMAlgoType=None, RMAT=None, RMCR=None, INCA=None, **kwargs): """ Parameters ---------- RMAlgoType : str RMAT : RMRefType RMCR : RMRefType INCA : INCAType kwargs : dict """ if '_xml_ns' in kwargs: self._xml_ns = kwargs['_xml_ns'] if '_xml_ns_key' in kwargs: self._xml_ns_key = kwargs['_xml_ns_key'] self.RMAlgoType = RMAlgoType self.RMAT = RMAT self.RMCR = RMCR self.INCA = INCA super(RMAType, self).__init__(**kwargs) @property def ImageType(self): # type: () -> Union[None, str] """ str: READ ONLY attribute. Identifies the specific RM image type / metadata type supplied. This is determined by returning the (first) attribute among `'RMAT', 'RMCR', 'INCA'` which is populated. `None` will be returned if none of them are populated. """ for attribute in self._choice[0]['collection']: if getattr(self, attribute) is not None: return attribute return None def _derive_parameters(self, SCPCOA, Position, RadarCollection, ImageFormation): """ Expected to be called from SICD parent. Parameters ---------- SCPCOA : sarpy.io.complex.sicd_elements.SCPCOA.SCPCOAType Position : sarpy.io.complex.sicd_elements.Position.PositionType RadarCollection : sarpy.io.complex.sicd_elements.RadarCollection.RadarCollectionType ImageFormation : sarpy.io.complex.sicd_elements.ImageFormation.ImageFormationType Returns ------- None """ if SCPCOA is None: return scp = None if SCPCOA.ARPPos is None else SCPCOA.ARPPos.get_array() im_type = self.ImageType if im_type in ['RMAT', 'RMCR']: rm_ref = getattr(self, im_type) # type: RMRefType if rm_ref.PosRef is None and SCPCOA.ARPPos is not None: rm_ref.PosRef = SCPCOA.ARPPos.copy() if rm_ref.VelRef is None and SCPCOA.ARPVel is not None: rm_ref.VelRef = SCPCOA.ARPVel.copy() if scp is not None and rm_ref.PosRef is not None and rm_ref.VelRef is not None: pos_ref = rm_ref.PosRef.get_array() vel_ref = rm_ref.VelRef.get_array() uvel_ref = vel_ref/norm(vel_ref) ulos = (scp - pos_ref) # it absolutely could be that scp = pos_ref ulos_norm = norm(ulos) if ulos_norm > 0: ulos /= ulos_norm if rm_ref.DopConeAngRef is None: rm_ref.DopConeAngRef = numpy.rad2deg(numpy.arccos(numpy.dot(uvel_ref, ulos))) elif im_type == 'INCA': if scp is not None and self.INCA.TimeCAPoly is not None and \ Position is not None and Position.ARPPoly is not None: t_zero = self.INCA.TimeCAPoly.Coefs[0] ca_pos = Position.ARPPoly(t_zero) if self.INCA.R_CA_SCP is None: self.INCA.R_CA_SCP = norm(ca_pos - scp) if self.INCA.FreqZero is None: self.INCA.FreqZero = _get_center_frequency(RadarCollection, ImageFormation) def _apply_reference_frequency(self, reference_frequency): """ If the reference frequency is used, adjust the necessary fields accordingly. Expected to be called by SICD parent. Parameters ---------- reference_frequency : float The reference frequency. Returns ------- None """ if self.INCA is not None: # noinspection PyProtectedMember self.INCA._apply_reference_frequency(reference_frequency)
class ImageDataType(Serializable): """The image pixel data.""" _collections_tags = { 'AmpTable': {'array': True, 'child_tag': 'Amplitude'}, 'ValidData': {'array': True, 'child_tag': 'Vertex'}, } _fields = ( 'PixelType', 'AmpTable', 'NumRows', 'NumCols', 'FirstRow', 'FirstCol', 'FullImage', 'SCPPixel', 'ValidData') _required = ('PixelType', 'NumRows', 'NumCols', 'FirstRow', 'FirstCol', 'FullImage', 'SCPPixel') _numeric_format = {'AmpTable': '0.16G'} _PIXEL_TYPE_VALUES = ("RE32F_IM32F", "RE16I_IM16I", "AMP8I_PHS8I") # descriptors PixelType = StringEnumDescriptor( 'PixelType', _PIXEL_TYPE_VALUES, _required, strict=True, docstring="The PixelType attribute which specifies the interpretation of the file data.") # type: str AmpTable = FloatArrayDescriptor( 'AmpTable', _collections_tags, _required, strict=DEFAULT_STRICT, minimum_length=256, maximum_length=256, docstring="The amplitude look-up table. This is required if " "`PixelType == 'AMP8I_PHS8I'`") # type: numpy.ndarray NumRows = IntegerDescriptor( 'NumRows', _required, strict=True, docstring='The number of Rows in the product. May include zero rows.') # type: int NumCols = IntegerDescriptor( 'NumCols', _required, strict=True, docstring='The number of Columns in the product. May include zero rows.') # type: int FirstRow = IntegerDescriptor( 'FirstRow', _required, strict=DEFAULT_STRICT, docstring='Global row index of the first row in the product. ' 'Equal to 0 in full image product.') # type: int FirstCol = IntegerDescriptor( 'FirstCol', _required, strict=DEFAULT_STRICT, docstring='Global column index of the first column in the product. ' 'Equal to 0 in full image product.') # type: int FullImage = SerializableDescriptor( 'FullImage', FullImageType, _required, strict=DEFAULT_STRICT, docstring='Original full image product.') # type: FullImageType SCPPixel = SerializableDescriptor( 'SCPPixel', RowColType, _required, strict=DEFAULT_STRICT, docstring='Scene Center Point pixel global row and column index. Should be located near the ' 'center of the full image.') # type: RowColType ValidData = SerializableArrayDescriptor( 'ValidData', RowColArrayElement, _collections_tags, _required, strict=DEFAULT_STRICT, minimum_length=3, docstring='Indicates the full image includes both valid data and some zero filled pixels. ' 'Simple polygon encloses the valid data (may include some zero filled pixels for simplification). ' 'Vertices in clockwise order.') # type: Union[SerializableArray, List[RowColArrayElement]] def __init__(self, PixelType=None, AmpTable=None, NumRows=None, NumCols=None, FirstRow=None, FirstCol=None, FullImage=None, SCPPixel=None, ValidData=None, **kwargs): """ Parameters ---------- PixelType : str AmpTable : numpy.ndarray|list|tuple NumRows : int NumCols : int FirstRow : int FirstCol : int FullImage : FullImageType|numpy.ndarray|list|tuple SCPPixel : RowColType|numpy.ndarray|list|tuple ValidData : SerializableArray|List[RowColArrayElement]|numpy.ndarray|list|tuple kwargs : dict """ if '_xml_ns' in kwargs: self._xml_ns = kwargs['_xml_ns'] if '_xml_ns_key' in kwargs: self._xml_ns_key = kwargs['_xml_ns_key'] self.PixelType = PixelType self.AmpTable = AmpTable self.NumRows, self.NumCols = NumRows, NumCols self.FirstRow, self.FirstCol = FirstRow, FirstCol self.FullImage = FullImage self.SCPPixel = SCPPixel self.ValidData = ValidData super(ImageDataType, self).__init__(**kwargs) def _check_valid_data(self): if self.ValidData is None: return True if len(self.ValidData) < 2: return True value = True valid_data = self.ValidData.get_array(dtype='float64') lin_ring = LinearRing(coordinates=valid_data) area = lin_ring.get_area() if area == 0: self.log_validity_error('ValidData encloses no area.') value = False elif area > 0: self.log_validity_error( "ValidData must be traversed in clockwise direction.") value = False for i, entry in enumerate(valid_data): if not ((self.FirstRow <= entry[0] <= self.FirstRow + self.NumRows) and (self.FirstCol <= entry[1] <= self.FirstCol + self.NumCols)): self.log_validity_warning( 'ValidData entry {} is not contained in the image bounds'.format(i)) value = False return value def _basic_validity_check(self): condition = super(ImageDataType, self)._basic_validity_check() if (self.PixelType == 'AMP8I_PHS8I') and (self.AmpTable is None): self.log_validity_error("We have `PixelType='AMP8I_PHS8I'` and `AmpTable` is not defined for ImageDataType.") condition = False if (self.PixelType != 'AMP8I_PHS8I') and (self.AmpTable is not None): self.log_validity_error("We have `PixelType != 'AMP8I_PHS8I'` and `AmpTable` is defined for ImageDataType.") condition = False if (self.ValidData is not None) and (len(self.ValidData) < 3): self.log_validity_error("We have `ValidData` defined with fewer than 3 entries.") condition = False condition &= self._check_valid_data() return condition def get_valid_vertex_data(self, dtype=numpy.int64): """ Gets an array of `[row, col]` indices defining the valid data. If this is not viable, then `None` will be returned. Parameters ---------- dtype : object the data type for the array Returns ------- numpy.ndarray|None """ if self.ValidData is None: return None out = numpy.zeros((self.ValidData.size, 2), dtype=dtype) for i, entry in enumerate(self.ValidData): out[i, :] = entry.get_array(dtype=dtype) return out def get_full_vertex_data(self, dtype=numpy.int64): """ Gets an array of `[row, col]` indices defining the full vertex data. If this is not viable, then `None` will be returned. Parameters ---------- dtype : object the data type for the array Returns ------- numpy.ndarray|None """ if self.NumRows is None or self.NumCols is None: return None return numpy.array( [[0, 0], [0, self.NumCols - 1], [self.NumRows - 1, self.NumCols - 1], [self.NumRows - 1, 0]], dtype=dtype)
class INCAType(Serializable): """Parameters for Imaging Near Closest Approach (INCA) image description.""" _fields = ( 'TimeCAPoly', 'R_CA_SCP', 'FreqZero', 'DRateSFPoly', 'DopCentroidPoly', 'DopCentroidCOA') _required = ('TimeCAPoly', 'R_CA_SCP', 'FreqZero', 'DRateSFPoly') _numeric_format = {'R_CA_SCP': '0.17E', 'FreqZero': '0.17E'} # descriptors TimeCAPoly = SerializableDescriptor( 'TimeCAPoly', Poly1DType, _required, strict=DEFAULT_STRICT, docstring='Polynomial function that yields *Time of Closest Approach* as function of ' 'image column *(azimuth)* coordinate in meters. Time relative to ' 'collection start in seconds.') # type: Poly1DType R_CA_SCP = FloatDescriptor( 'R_CA_SCP', _required, strict=DEFAULT_STRICT, docstring='*Range at Closest Approach (R_CA)* for the *Scene Center Point (SCP)* in meters.') # type: float FreqZero = FloatDescriptor( 'FreqZero', _required, strict=DEFAULT_STRICT, docstring=r'*RF frequency* :\math:`(f_0)` in Hz used for computing ' r'Doppler Centroid values. Typical :math:`f_0` ' r'set equal to center transmit frequency.') # type: float DRateSFPoly = SerializableDescriptor( 'DRateSFPoly', Poly2DType, _required, strict=DEFAULT_STRICT, docstring='Polynomial function that yields *Doppler Rate scale factor (DRSF)* ' 'as a function of image location. Yields `DRSF` as a function of image ' 'range coordinate ``(variable 1)`` and azimuth coordinate ``(variable 2)``. ' 'Used to compute Doppler Rate at closest approach.') # type: Poly2DType DopCentroidPoly = SerializableDescriptor( 'DopCentroidPoly', Poly2DType, _required, strict=DEFAULT_STRICT, docstring='Polynomial function that yields Doppler Centroid value as a ' 'function of image location *(fdop_DC)*. The *fdop_DC* is the ' 'Doppler frequency at the peak signal response. The polynomial is a function ' 'of image range coordinate ``(variable 1)`` and azimuth coordinate ``(variable 2)``. ' '*Note: Only used for Stripmap and Dynamic Stripmap collections.*') # type: Poly2DType DopCentroidCOA = BooleanDescriptor( 'DopCentroidCOA', _required, strict=DEFAULT_STRICT, docstring="""Flag indicating that the COA is at the peak signal :math`fdop_COA = fdop_DC`. * `True` - if Pixel COA at peak signal for all pixels. * `False` otherwise. *Note:* Only used for Stripmap and Dynamic Stripmap.""") # type: bool def __init__(self, TimeCAPoly=None, R_CA_SCP=None, FreqZero=None, DRateSFPoly=None, DopCentroidPoly=None, DopCentroidCOA=None, **kwargs): """ Parameters ---------- TimeCAPoly : Poly1DType|numpy.ndarray|list|tuple R_CA_SCP : float FreqZero : float DRateSFPoly : Poly2DType|numpy.ndarray|list|tuple DopCentroidPoly : Poly2DType|numpy.ndarray|list|tuple DopCentroidCOA : bool kwargs : dict """ if '_xml_ns' in kwargs: self._xml_ns = kwargs['_xml_ns'] if '_xml_ns_key' in kwargs: self._xml_ns_key = kwargs['_xml_ns_key'] self.TimeCAPoly = TimeCAPoly self.R_CA_SCP = R_CA_SCP self.FreqZero = FreqZero self.DRateSFPoly = DRateSFPoly self.DopCentroidPoly = DopCentroidPoly self.DopCentroidCOA = DopCentroidCOA super(INCAType, self).__init__(**kwargs) def _apply_reference_frequency(self, reference_frequency): if self.FreqZero is not None: self.FreqZero += reference_frequency
class ImageFormationType(Serializable): """The image formation process parameters.""" _fields = ('RcvChanProc', 'TxRcvPolarizationProc', 'TStartProc', 'TEndProc', 'TxFrequencyProc', 'SegmentIdentifier', 'ImageFormAlgo', 'STBeamComp', 'ImageBeamComp', 'AzAutofocus', 'RgAutofocus', 'Processings', 'PolarizationCalibration') _required = ('RcvChanProc', 'TxRcvPolarizationProc', 'TStartProc', 'TEndProc', 'TxFrequencyProc', 'ImageFormAlgo', 'STBeamComp', 'ImageBeamComp', 'AzAutofocus', 'RgAutofocus') _collections_tags = { 'Processings': { 'array': False, 'child_tag': 'Processing' } } _numeric_format = {'TStartProc': '0.16G', 'EndProc': '0.16G'} # class variables _IMG_FORM_ALGO_VALUES = ('PFA', 'RMA', 'RGAZCOMP', 'OTHER') _ST_BEAM_COMP_VALUES = ('NO', 'GLOBAL', 'SV') _IMG_BEAM_COMP_VALUES = ('NO', 'SV') _AZ_AUTOFOCUS_VALUES = _ST_BEAM_COMP_VALUES _RG_AUTOFOCUS_VALUES = _ST_BEAM_COMP_VALUES # descriptors RcvChanProc = SerializableDescriptor( 'RcvChanProc', RcvChanProcType, _required, strict=DEFAULT_STRICT, docstring='The received processed channels.') # type: RcvChanProcType TxRcvPolarizationProc = StringEnumDescriptor( 'TxRcvPolarizationProc', DUAL_POLARIZATION_VALUES, _required, strict=DEFAULT_STRICT, docstring= 'The combined transmit/receive polarization processed to form the image.' ) # type: str TStartProc = FloatDescriptor( 'TStartProc', _required, strict=DEFAULT_STRICT, docstring= 'Earliest slow time value for data processed to form the image ' 'from `CollectionStart`.') # type: float TEndProc = FloatDescriptor( 'TEndProc', _required, strict=DEFAULT_STRICT, docstring= 'Latest slow time value for data processed to form the image from `CollectionStart`.' ) # type: float TxFrequencyProc = SerializableDescriptor( 'TxFrequencyProc', TxFrequencyProcType, _required, strict=DEFAULT_STRICT, docstring='The range of transmit frequency processed to form the image.' ) # type: TxFrequencyProcType SegmentIdentifier = StringDescriptor( 'SegmentIdentifier', _required, strict=DEFAULT_STRICT, docstring='Identifier that describes the image that was processed. ' 'Must be included when `SICD.RadarCollection.Area.Plane.SegmentList` is included.' ) # type: str ImageFormAlgo = StringEnumDescriptor('ImageFormAlgo', _IMG_FORM_ALGO_VALUES, _required, strict=DEFAULT_STRICT, docstring=""" The image formation algorithm used: * `PFA` - Polar Format Algorithm * `RMA` - Range Migration (Omega-K, Chirp Scaling, Range-Doppler) * `RGAZCOMP` - Simple range, Doppler compression. """) # type: str STBeamComp = StringEnumDescriptor('STBeamComp', _ST_BEAM_COMP_VALUES, _required, strict=DEFAULT_STRICT, docstring=""" Indicates if slow time beam shape compensation has been applied. * `NO` - No ST beam shape compensation. * `GLOBAL` - Global ST beam shape compensation applied. * `SV` - Spatially variant beam shape compensation applied. """) # type: str ImageBeamComp = StringEnumDescriptor('ImageBeamComp', _IMG_BEAM_COMP_VALUES, _required, strict=DEFAULT_STRICT, docstring=""" Indicates if image domain beam shape compensation has been applied. * `NO` - No image domain beam shape compensation. * `SV` - Spatially variant image domain beam shape compensation applied. """) # type: str AzAutofocus = StringEnumDescriptor( 'AzAutofocus', _AZ_AUTOFOCUS_VALUES, _required, strict=DEFAULT_STRICT, docstring= 'Indicates if azimuth autofocus correction has been applied, with similar ' 'interpretation as `STBeamComp`.') # type: str RgAutofocus = StringEnumDescriptor( 'RgAutofocus', _RG_AUTOFOCUS_VALUES, _required, strict=DEFAULT_STRICT, docstring= 'Indicates if range autofocus correction has been applied, with similar ' 'interpretation as `STBeamComp`.') # type: str Processings = SerializableListDescriptor( 'Processings', ProcessingType, _collections_tags, _required, strict=DEFAULT_STRICT, docstring= 'Parameters to describe types of specific processing that may have been applied ' 'such as additional compensations.' ) # type: Union[None, List[ProcessingType]] PolarizationCalibration = SerializableDescriptor( 'PolarizationCalibration', PolarizationCalibrationType, _required, strict=DEFAULT_STRICT, docstring='The polarization calibration details.' ) # type: PolarizationCalibrationType def __init__(self, RcvChanProc=None, TxRcvPolarizationProc=None, TStartProc=None, TEndProc=None, TxFrequencyProc=None, SegmentIdentifier=None, ImageFormAlgo=None, STBeamComp=None, ImageBeamComp=None, AzAutofocus=None, RgAutofocus=None, Processings=None, PolarizationCalibration=None, **kwargs): """ Parameters ---------- RcvChanProc : RcvChanProcType TxRcvPolarizationProc : str TStartProc : float TEndProc : float TxFrequencyProc : TxFrequencyProcType|numpy.ndarray|list|tuple SegmentIdentifier : str ImageFormAlgo : str STBeamComp : str ImageBeamComp :str AzAutofocus : str RgAutofocus : str Processings : None|List[ProcessingType] PolarizationCalibration : PolarizationCalibrationType kwargs : dict """ if '_xml_ns' in kwargs: self._xml_ns = kwargs['_xml_ns'] if '_xml_ns_key' in kwargs: self._xml_ns_key = kwargs['_xml_ns_key'] self.RcvChanProc = RcvChanProc self.TxRcvPolarizationProc = TxRcvPolarizationProc self.TStartProc, self.TEndProc = TStartProc, TEndProc if isinstance( TxFrequencyProc, (numpy.ndarray, list, tuple)) and len(TxFrequencyProc) >= 2: self.TxFrequencyProc = TxFrequencyProcType( MinProc=TxFrequencyProc[0], MaxProc=TxFrequencyProc[1]) else: self.TxFrequencyProc = TxFrequencyProc self.SegmentIdentifier = SegmentIdentifier self.ImageFormAlgo = ImageFormAlgo self.STBeamComp, self.ImageBeamComp = STBeamComp, ImageBeamComp self.AzAutofocus, self.RgAutofocus = AzAutofocus, RgAutofocus self.Processings = Processings self.PolarizationCalibration = PolarizationCalibration super(ImageFormationType, self).__init__(**kwargs) def _basic_validity_check(self): condition = super(ImageFormationType, self)._basic_validity_check() if self.TStartProc is not None and self.TEndProc is not None and self.TEndProc < self.TStartProc: self.log_validity_error( 'Invalid time processing bounds TStartProc ({}) > TEndProc ({})' .format(self.TStartProc, self.TEndProc)) condition = False return condition def _derive_tx_frequency_proc(self, RadarCollection): """ Populate a default for processed frequency values, based on the assumption that the entire transmitted bandwidth was processed. This is expected to be called by SICD parent. Parameters ---------- RadarCollection : sarpy.io.complex.sicd_elements.RadarCollection.RadarCollectionType Returns ------- None """ if RadarCollection is not None and RadarCollection.TxFrequency is not None and \ RadarCollection.TxFrequency.Min is not None and RadarCollection.TxFrequency.Max is not None: # this is based on the assumption that the entire transmitted bandwidth was processed. if self.TxFrequencyProc is not None: self.TxFrequencyProc = TxFrequencyProcType( MinProc=RadarCollection.TxFrequency.Min, MaxProc=RadarCollection.TxFrequency.Max) # how would it make sense to set only one end? elif self.TxFrequencyProc.MinProc is None: self.TxFrequencyProc.MinProc = RadarCollection.TxFrequency.Min elif self.TxFrequencyProc.MaxProc is None: self.TxFrequencyProc.MaxProc = RadarCollection.TxFrequency.Max def _apply_reference_frequency(self, reference_frequency): """ If the reference frequency is used, adjust the necessary fields accordingly. Expected to be called by SICD parent. Parameters ---------- reference_frequency : float The reference frequency. Returns ------- None """ if self.TxFrequencyProc is not None: # noinspection PyProtectedMember self.TxFrequencyProc._apply_reference_frequency( reference_frequency) def get_polarization(self): """ Gets the transmit/receive polarization. Returns ------- str """ return self.TxRcvPolarizationProc if self.TxRcvPolarizationProc is not None else 'UNKNOWN' def get_polarization_abbreviation(self): """ Gets the transmit/receive polarization abbreviation for the suggested name. Returns ------- str """ pol = self.TxRcvPolarizationProc if pol is None or pol in ('OTHER', 'UNKNOWN'): return 'UN' fp, sp = pol.split(':') return fp[0] + sp[0] def get_transmit_band_name(self): """ Gets the transmit band name. Returns ------- str """ if self.TxFrequencyProc is not None: return self.TxFrequencyProc.get_band_name() else: return 'UN' def permits_version_1_1(self): """ Does this value permit storage in SICD version 1.1? Returns ------- bool """ return is_polstring_version1(self.TxRcvPolarizationProc)
class GeoInfoType(Serializable): """ A geographic feature. """ _fields = ('name', 'Descriptions', 'Point', 'Line', 'Polygon') _required = ('name', ) _set_as_attribute = ('name', ) _choice = ({'required': False, 'collection': ('Point', 'Line', 'Polygon')}, ) _collections_tags = { 'Descriptions': {'array': False, 'child_tag': 'Desc'}, 'Line': {'array': True, 'child_tag': 'Endpoint'}, 'Polygon': {'array': True, 'child_tag': 'Vertex'}, } # descriptors name = StringDescriptor( 'name', _required, strict=DEFAULT_STRICT, docstring='The name.') # type: str Descriptions = ParametersDescriptor( 'Descriptions', _collections_tags, _required, strict=DEFAULT_STRICT, docstring='Descriptions of the geographic feature.') # type: ParametersCollection Point = SerializableDescriptor( 'Point', LatLonRestrictionType, _required, strict=DEFAULT_STRICT, docstring='A geographic point with WGS-84 coordinates.') # type: LatLonRestrictionType Line = SerializableArrayDescriptor( 'Line', LatLonArrayElementType, _collections_tags, _required, strict=DEFAULT_STRICT, minimum_length=2, docstring='A geographic line (array) with WGS-84 coordinates.' ) # type: Union[SerializableArray, List[LatLonArrayElementType]] Polygon = SerializableArrayDescriptor( 'Polygon', LatLonArrayElementType, _collections_tags, _required, strict=DEFAULT_STRICT, minimum_length=3, docstring='A geographic polygon (array) with WGS-84 coordinates.' ) # type: Union[SerializableArray, List[LatLonArrayElementType]] def __init__(self, name=None, Descriptions=None, Point=None, Line=None, Polygon=None, GeoInfos=None, **kwargs): """ Parameters ---------- name : str Descriptions : ParametersCollection|dict Point : LatLonRestrictionType|numpy.ndarray|list|tuple Line : SerializableArray|List[LatLonArrayElementType]|numpy.ndarray|list|tuple Polygon : SerializableArray|List[LatLonArrayElementType]|numpy.ndarray|list|tuple GeoInfos : Dict[GeoInfoTpe] kwargs : dict """ if '_xml_ns' in kwargs: self._xml_ns = kwargs['_xml_ns'] if '_xml_ns_key' in kwargs: self._xml_ns_key = kwargs['_xml_ns_key'] self.name = name self.Descriptions = Descriptions self.Point = Point self.Line = Line self.Polygon = Polygon self._GeoInfos = [] if GeoInfos is None: pass elif isinstance(GeoInfos, GeoInfoType): self.addGeoInfo(GeoInfos) elif isinstance(GeoInfos, (list, tuple)): for el in GeoInfos: self.addGeoInfo(el) else: raise ValueError('GeoInfos got unexpected type {}'.format(type(GeoInfos))) super(GeoInfoType, self).__init__(**kwargs) @property def FeatureType(self): # type: () -> Union[None, str] """ str: READ ONLY attribute. Identifies the feature type among. This is determined by returning the (first) attribute among `Point`, `Line`, `Polygon` which is populated. `None` will be returned if none of them are populated. """ for attribute in self._choice[0]['collection']: if getattr(self, attribute) is not None: return attribute return None @property def GeoInfos(self): """ List[GeoInfoType]: list of GeoInfos. """ return self._GeoInfos def getGeoInfo(self, key): """ Get GeoInfo(s) with name attribute == `key`. Parameters ---------- key : str Returns ------- List[GeoInfoType] """ return [entry for entry in self._GeoInfos if entry.name == key] def addGeoInfo(self, value): """ Add the given GeoInfo to the GeoInfos list. Parameters ---------- value : GeoInfoType Returns ------- None """ if isinstance(value, ElementTree.Element): gi_key = self._child_xml_ns_key.get('GeoInfos', self._xml_ns_key) value = GeoInfoType.from_node(value, self._xml_ns, ns_key=gi_key) elif isinstance(value, dict): value = GeoInfoType.from_dict(value) if isinstance(value, GeoInfoType): self._GeoInfos.append(value) else: raise TypeError('Trying to set GeoInfo element with unexpected type {}'.format(type(value))) def _validate_features(self): if self.Line is not None and self.Line.size < 2: self.log_validity_error('GeoInfo has a Line feature with {} points defined.'.format(self.Line.size)) return False if self.Polygon is not None and self.Polygon.size < 3: self.log_validity_error('GeoInfo has a Polygon feature with {} points defined.'.format(self.Polygon.size)) return False return True def _basic_validity_check(self): condition = super(GeoInfoType, self)._basic_validity_check() return condition & self._validate_features() @classmethod def from_node(cls, node, xml_ns, ns_key=None, kwargs=None): if kwargs is None: kwargs = OrderedDict() gi_key = cls._child_xml_ns_key.get('GeoInfos', ns_key) kwargs['GeoInfos'] = find_children(node, 'GeoInfo', xml_ns, gi_key) return super(GeoInfoType, cls).from_node(node, xml_ns, ns_key=ns_key, kwargs=kwargs) def to_node(self, doc, tag, ns_key=None, parent=None, check_validity=False, strict=DEFAULT_STRICT, exclude=()): node = super(GeoInfoType, self).to_node( doc, tag, ns_key=ns_key, parent=parent, check_validity=check_validity, strict=strict, exclude=exclude) # slap on the GeoInfo children for entry in self._GeoInfos: entry.to_node(doc, tag, ns_key=ns_key, parent=node, strict=strict) return node def to_dict(self, check_validity=False, strict=DEFAULT_STRICT, exclude=()): out = super(GeoInfoType, self).to_dict(check_validity=check_validity, strict=strict, exclude=exclude) # slap on the GeoInfo children if len(self.GeoInfos) > 0: out['GeoInfos'] = [entry.to_dict(check_validity=check_validity, strict=strict) for entry in self._GeoInfos] return out
class ProjectionPerturbationType(Serializable): """ Basic information required for SICD/SIDD projection model perturbation. """ _fields = ('CoordinateFrame', 'DeltaArp', 'DeltaVarp', 'DeltaRange') _required = ('CoordinateFrame', ) _numeric_format = { 'Lat': '0.17G', } CoordinateFrame = StringEnumDescriptor('CoordinateFrame', {'ECF', 'RIC_ECI', 'RIC_ECF'}, _required) # type: str DeltaArp = SerializableDescriptor('DeltaArp', XYZType, _required) # type: XYZType DeltaVarp = SerializableDescriptor('DeltaVarp', XYZType, _required) # type: XYZType DeltaRange = FloatDescriptor('DeltaRange', _required) # type: float def __init__(self, CoordinateFrame=None, DeltaArp=None, DeltaVarp=None, DeltaRange=None, **kwargs): """ Parameters ---------- CoordinateFrame : str DeltaArp : None|XYZType|numpy.ndarray|list|tuple DeltaVarp : None|XYZType|numpy.ndarray|list|tuple DeltaRange : None|float kwargs Other keyword arguments """ if '_xml_ns' in kwargs: self._xml_ns = kwargs['_xml_ns'] if '_xml_ns_key' in kwargs: self._xml_ns_key = kwargs['_xml_ns_key'] self.CoordinateFrame = CoordinateFrame self.DeltaArp = DeltaArp self.DeltaVarp = DeltaVarp self.DeltaRange = DeltaRange super(ProjectionPerturbationType, self).__init__(**kwargs) def set_coa_projection(self, structure): """ Sets the sicd or sidd coa_projection property, as appropriate. Parameters ---------- structure : SICDType|SIDDType1|SIDDType2 """ if not isinstance(structure, (SICDType, SIDDType1, SIDDType2)): raise TypeError( 'Requires input of type SICDType or SIDDType, got {}'.format( type(structure))) structure.define_coa_projection( delta_arp=None if self.DeltaArp is None else self.DeltaArp.get_array(dtype='float64'), delta_varp=None if self.DeltaVarp is None else self.DeltaVarp.get_array(dtype='float64'), range_bias=self.DeltaRange, adj_params_frame=self.CoordinateFrame, overide=True)
class IPPSetType(Serializable): """ The Inter-Pulse Parameter array element container. """ # NOTE that this is simply defined as a child class ("Set") of the TimelineType in the SICD standard # Defining it at root level clarifies the documentation, and giving it a more descriptive name is # appropriate. _fields = ('TStart', 'TEnd', 'IPPStart', 'IPPEnd', 'IPPPoly', 'index') _required = _fields _set_as_attribute = ('index', ) _numeric_format = { 'TStart': '0.16G', 'TEnd': '0.16G', } # descriptors TStart = FloatDescriptor( 'TStart', _required, strict=DEFAULT_STRICT, docstring= 'IPP start time relative to collection start time, i.e. offsets in seconds.' ) # type: float TEnd = FloatDescriptor( 'TEnd', _required, strict=DEFAULT_STRICT, docstring= 'IPP end time relative to collection start time, i.e. offsets in seconds.' ) # type: float IPPStart = IntegerDescriptor( 'IPPStart', _required, strict=True, docstring='Starting IPP index for the period described.') # type: int IPPEnd = IntegerDescriptor( 'IPPEnd', _required, strict=True, docstring='Ending IPP index for the period described.') # type: int IPPPoly = SerializableDescriptor( 'IPPPoly', Poly1DType, _required, strict=DEFAULT_STRICT, docstring= 'IPP index polynomial coefficients yield IPP index as a function of time.' ) # type: Poly1DType index = IntegerDescriptor( 'index', _required, strict=DEFAULT_STRICT, docstring='The element array index.') # type: int def __init__(self, TStart=None, TEnd=None, IPPStart=None, IPPEnd=None, IPPPoly=None, index=None, **kwargs): """ Parameters ---------- TStart : float TEnd : float IPPStart : int IPPEnd : int IPPPoly : Poly1DType|numpy.ndarray|list|tuple index : int kwargs : dict """ if '_xml_ns' in kwargs: self._xml_ns = kwargs['_xml_ns'] if '_xml_ns_key' in kwargs: self._xml_ns_key = kwargs['_xml_ns_key'] self.TStart, self.TEnd = TStart, TEnd self.IPPStart, self.IPPEnd = IPPStart, IPPEnd self.IPPPoly = IPPPoly self.index = index super(IPPSetType, self).__init__(**kwargs) def _basic_validity_check(self): condition = super(IPPSetType, self)._basic_validity_check() if self.TStart >= self.TEnd: self.log_validity_error('TStart ({}) >= TEnd ({})'.format( self.TStart, self.TEnd)) condition = False if self.IPPStart >= self.IPPEnd: self.log_validity_error('IPPStart ({}) >= IPPEnd ({})'.format( self.IPPStart, self.IPPEnd)) condition = False exp_ipp_start = self.IPPPoly(self.TStart) exp_ipp_end = self.IPPPoly(self.TEnd) if abs(exp_ipp_start - self.IPPStart) > 1: self.log_validity_error( 'IPPStart populated as {}, inconsistent with value ({}) ' 'derived from IPPPoly and TStart'.format( exp_ipp_start, self.IPPStart)) if abs(exp_ipp_end - self.IPPEnd) > 1: self.log_validity_error( 'IPPEnd populated as {}, inconsistent with value ({}) ' 'derived from IPPPoly and TEnd'.format(self.IPPEnd, exp_ipp_end)) return condition