def _find_sicd(self): self._is_sicd = False self._sicd_meta = None if self.des_subheader_offsets is None: return for i in range(self.des_subheader_offsets.size): subhead_bytes = self.get_des_subheader_bytes(i) if subhead_bytes.startswith(b'DEXML_DATA_CONTENT'): des_header = DataExtensionHeader.from_bytes(subhead_bytes, start=0) des_bytes = self.get_des_bytes(i) # noinspection PyBroadException try: root_node, xml_ns = parse_xml_from_string(des_bytes.decode('utf-8').strip()) if 'SIDD' in root_node.tag: # namespace makes this ugly # NOTE that SIDD files are supposed to have the corresponding # SICD xml as one of the DES AFTER the SIDD xml. # The same basic format is used for both headers. # So, abandon if we find a SIDD xml self._des_index = None self._des_header = None self._is_sicd = False break elif 'SICD' in root_node.tag: # namespace makes this ugly self._des_index = i self._des_header = des_header self._is_sicd = True self._sicd_meta = SICDType.from_node(root_node, xml_ns, ns_key='default') break except Exception: continue elif subhead_bytes.startswith(b'DESIDD_XML'): # This is an old format SIDD and can't be a SICD self._des_index = None self._des_header = None self._is_sicd = False break elif subhead_bytes.startswith(b'DESICD_XML'): # This is an old format SICD des_bytes = self.get_des_bytes(i) try: root_node, xml_ns = parse_xml_from_string(des_bytes) if 'SICD' in root_node.tag: # namespace makes this ugly self._des_index = i self._des_header = None self._is_sicd = True self._sicd_meta = SICDType.from_node(root_node, xml_ns, ns_key='default') break except Exception as e: logging.error('We found an apparent old-style SICD DES header, ' 'but failed parsing with error {}'.format(e)) continue if not self._is_sicd: return self._sicd_meta.derive()
def _find_sidd(self): self._is_sidd = False if self.des_subheader_offsets is None: return self._sidd_meta = [] self._sicd_meta = [] for i in range(self.des_subheader_offsets.size): subhead_bytes = self.get_des_subheader_bytes(i) if subhead_bytes.startswith(b'DEXML_DATA_CONTENT'): des_bytes = self.get_des_bytes(i) try: root_node, xml_ns = parse_xml_from_string(des_bytes) if 'SIDD' in root_node.tag: self._is_sidd = True self._sidd_meta.append(SIDDType.from_node(root_node, xml_ns, ns_key='default')) elif 'SICD' in root_node.tag: self._sicd_meta.append(SICDType.from_node(root_node, xml_ns, ns_key='default')) except Exception as e: logger.error('Failed checking des xml header at index {} with error {}'.format(i, e)) continue elif subhead_bytes.startswith(b'DESIDD_XML'): # This is an old format SIDD header des_bytes = self.get_des_bytes(i) try: root_node, xml_ns = parse_xml_from_string(des_bytes) if 'SIDD' in root_node.tag: self._is_sidd = True self._sidd_meta.append(SIDDType.from_node(root_node, xml_ns, ns_key='default')) except Exception as e: logger.error( 'We found an apparent old-style SIDD DES header at index {},\n\t' 'but failed parsing with error {}'.format(i, e)) continue elif subhead_bytes.startswith(b'DESICD_XML'): # This is an old format SICD header des_bytes = self.get_des_bytes(i) try: root_node, xml_ns = parse_xml_from_string(des_bytes) if 'SICD' in root_node.tag: self._sicd_meta.append(SICDType.from_node(root_node, xml_ns, ns_key='default')) except Exception as e: logger.error( 'We found an apparent old-style SICD DES header at index {},\n\t' 'but failed parsing with error {}'.format(i, e)) continue if not self._is_sidd: return for sicd in self._sicd_meta: sicd.derive()
def update_geodata(sicd: SICDType) -> None: scp_pixel = [ sicd.ImageData.SCPPixel.Row, sicd.ImageData.SCPPixel.Col ] ecf = sicd.project_image_to_ground(scp_pixel, projection_type='HAE') sicd.update_scp(ecf, coord_system='ECF') SCP = sicd.GeoData.SCP.ECF.get_array(dtype='float64') scp_time = sicd.RMA.INCA.TimeCAPoly[0] ca_pos = sicd.Position.ARPPoly(scp_time) RG = SCP - ca_pos sicd.RMA.INCA.R_CA_SCP = numpy.linalg.norm(RG)
def update_image_data( sicd: SICDType, band_name: str) -> (float, float, float, float, int): cols, rows = shape_dict[band_name] # zero doppler time of first/last columns t_az_first_time = band_dict[band_name][ 'Zero Doppler Azimuth First Time'] t_az_last_time = band_dict[band_name][ 'Zero Doppler Azimuth Last Time'] t_ss_az_s = band_dict[band_name]['Line Time Interval'] t_use_sign2 = 1 if h5_dict['Look Side'].upper() == 'LEFT': t_use_sign2 = -1 t_az_first_time, t_az_last_time = t_az_last_time, t_az_first_time # zero doppler time of first row t_rg_first_time = band_dict[band_name][ 'Zero Doppler Range First Time'] # row spacing in range time (seconds) t_ss_rg_s = band_dict[band_name]['Column Time Interval'] sicd.ImageData = ImageDataType(NumRows=rows, NumCols=cols, FirstRow=0, FirstCol=0, FullImage=(rows, cols), PixelType=pixeltype_dict[band_name], SCPPixel=RowColType( Row=int(rows / 2), Col=int(cols / 2))) return t_rg_first_time, t_ss_rg_s, t_az_first_time, t_ss_az_s, t_use_sign2
def update_scp_prelim(sicd: SICDType, band_name: str) -> None: if self._mission_id in ['CSK', 'KMPS']: LLH = band_dict[band_name]['Centre Geodetic Coordinates'] elif self._mission_id == 'CSG': LLH = h5_dict['Scene Centre Geodetic Coordinates'] else: raise ValueError(_unhandled_id_text.format(self._mission_id)) sicd.GeoData = GeoDataType( SCP=SCPType(LLH=LLH)) # EarthModel & ECF will be populated
def validate_sicd_for_writing(sicd_meta: SICDType) -> SICDType: """ Helper method which ensures the provided SICD structure provides enough information to support file writing, as well as ensures a few basic items are populated as appropriate. Parameters ---------- sicd_meta : SICDType Returns ------- SICDType This returns a deep copy of the provided SICD structure, with any necessary modifications. """ if not isinstance(sicd_meta, SICDType): raise ValueError('sicd_meta is required to be an instance of SICDType, got {}'.format(type(sicd_meta))) if sicd_meta.ImageData is None: raise ValueError('The sicd_meta has un-populated ImageData, and nothing useful can be inferred.') if sicd_meta.ImageData.NumCols is None or sicd_meta.ImageData.NumRows is None: raise ValueError('The sicd_meta has ImageData with unpopulated NumRows or NumCols, ' 'and nothing useful can be inferred.') if sicd_meta.ImageData.PixelType is None: logger.warning('The PixelType for sicd_meta is unset, so defaulting to RE32F_IM32F.') sicd_meta.ImageData.PixelType = 'RE32F_IM32F' sicd_meta = sicd_meta.copy() profile = '{} {}'.format(__title__, __version__) if sicd_meta.ImageCreation is None: sicd_meta.ImageCreation = ImageCreationType( Application=profile, DateTime=numpy.datetime64(datetime.now()), Profile=profile) else: sicd_meta.ImageCreation.Profile = profile if sicd_meta.ImageCreation.DateTime is None: sicd_meta.ImageCreation.DateTime = numpy.datetime64(datetime.now()) return sicd_meta
def get_sicd(self): """ Extract the SICD details. Returns ------- SICDType """ if self._sicd is not None: return self._sicd if self._user_data is None: self._read_user_data() if self._caspr_data is None: self._find_caspr_data() # Check if the user data contains a sicd structure. sicd_string = None for nam in ['SICDMETA', 'SICD_META', 'SICD']: if sicd_string is None: sicd_string = self._user_data.get(nam, None) # If so, assume that this SICD is valid and simply present it if sicd_string is not None: root_node, xml_ns = parse_xml_from_string(sicd_string) self._sicd = SICDType.from_node(root_node, xml_ns) self._sicd.derive() else: # otherwise, we populate a really minimal sicd structure num_rows, num_cols = self.data_size self._sicd = SICDType(ImageData=ImageDataType(NumRows=num_rows, NumCols=num_cols, FirstRow=0, FirstCol=0, PixelType=self.pixel_type, FullImage=FullImageType(NumRows=num_rows, NumCols=num_cols), SCPPixel=RowColType(Row=num_rows/2, Col=num_cols/2))) return self._sicd
def check_file(file_name): """ Check the SICD validity for the given file SICD (i.e. appropriately styled NITF) or xml file containing the SICD structure alone. Parameters ---------- file_name : str|SICDDetails Returns ------- bool """ sicd_xml, root_node, xml_ns, urn_string = None, None, None, None if isinstance(file_name, SICDDetails): sicd_xml, root_node, xml_ns = _get_sicd_xml_from_nitf(file_name) else: # check if this is just an xml file with open(file_name, 'rb') as fi: initial_bits = fi.read(100) if initial_bits.startswith(b'<?xml') or initial_bits.startswith(b'<SICD'): sicd_xml = fi.read().decode('utf-8') root_node, xml_ns = parse_xml_from_string(sicd_xml) if sicd_xml is None: # try to first test whether this is SICD file try: sicd_details = SICDDetails(file_name) if not sicd_details.is_sicd: logger.error('File {} is a NITF file, but is apparently not a SICD file.') return False sicd_xml, root_node, xml_ns = _get_sicd_xml_from_nitf(sicd_details) except IOError: pass if sicd_xml is None: logger.error('Could not interpret input {}'.format(file_name)) return False urn_string = xml_ns['default'] valid_xml = evaluate_xml_versus_schema(sicd_xml, urn_string) if valid_xml is None: valid_xml = True the_sicd = SICDType.from_node(root_node, xml_ns=xml_ns) valid_sicd_contents = the_sicd.is_valid(recursive=True, stack=False) return valid_xml & valid_sicd_contents
def _evaluate_xml_string_validity(xml_string): """ Check the validity of the SICD xml, as defined by the given string. Parameters ---------- xml_string : str|bytes Returns ------- (bool, str, SICDType) """ root_node, xml_ns = parse_xml_from_string(xml_string) if xml_ns is None: raise ValueError( 'SICD XML invalid, because no apparent namespace defined in the xml,\n\t' 'which starts `{}...`'.format(xml_string[:15])) if 'default' not in xml_ns: raise ValueError( 'Could not properly interpret the namespace collection from xml\n{}' .format(xml_ns)) sicd_urn = xml_ns['default'] # check that our urn is mapped try: _ = get_urn_details(sicd_urn) check_schema = True except Exception as e: logger.exception('SICD: The SICD namespace has unrecognized value') check_schema = False valid_xml = None if check_schema: valid_xml = evaluate_xml_versus_schema(xml_string, sicd_urn) if valid_xml is None: valid_xml = True # perform the various sicd structure checks the_sicd = SICDType.from_node(root_node, xml_ns=xml_ns) valid_sicd_contents = the_sicd.is_valid(recursive=True, stack=False) return valid_xml & valid_sicd_contents, sicd_urn, the_sicd
def get_sicd_collection(self): """ Gets the list of sicd objects, one per polarimetric entry. Returns ------- Tuple[SICDType] """ nitf, collection_info = self._get_collection_info() image_creation = self._get_image_creation() image_data, geo_data = self._get_image_and_geo_data() position = self._get_position() grid = self._get_grid() radar_collection = self._get_radar_collection() timeline = self._get_timeline() image_formation = self._get_image_formation(timeline, radar_collection) scpcoa = self._get_scpcoa() rma = self._get_rma_adjust_grid(scpcoa, grid, image_data, position, collection_info) radiometric = self._get_radiometric(image_data, grid) base_sicd = SICDType(CollectionInfo=collection_info, ImageCreation=image_creation, GeoData=geo_data, ImageData=image_data, Position=position, Grid=grid, RadarCollection=radar_collection, Timeline=timeline, ImageFormation=image_formation, SCPCOA=scpcoa, RMA=rma, Radiometric=radiometric) if len(nitf) > 0: base_sicd._NITF = nitf self._update_geo_data(base_sicd) base_sicd.derive() # derive all the fields # now, make one copy per polarimetric entry, as appropriate tx_pols, tx_rcv_pols = self._get_polarizations() sicd_list = [] for i, entry in enumerate(tx_rcv_pols): this_sicd = base_sicd.copy() this_sicd.ImageFormation.RcvChanProc.ChanIndices = [ i + 1, ] this_sicd.ImageFormation.TxRcvPolarizationProc = \ this_sicd.RadarCollection.RcvChannels[i].TxRcvPolarization this_sicd.populate_rniirs(override=False) sicd_list.append(this_sicd) return tuple(sicd_list)
def update_radiometric(sicd: SICDType, band_name: str) -> None: if self.mission_id in ['KMPS', 'CSG']: # TODO: skipping for now - strange results for flag == 77. Awaiting gidance - see Wade. return if h5_dict['Range Spreading Loss Compensation Geometry'] != 'NONE': slant_range = h5_dict['Reference Slant Range'] exp = h5_dict['Reference Slant Range Exponent'] sf = slant_range**(2 * exp) rsf = h5_dict['Rescaling Factor'] sf /= rsf * rsf if h5_dict.get('Calibration Constant Compensation Flag', None) == 0: cal = band_dict[band_name]['Calibration Constant'] sf /= cal sicd.Radiometric = RadiometricType(BetaZeroSFPoly=Poly2DType( Coefs=[ [ sf, ], ]))
def get_sicd(self): """ Get the SICD metadata for the image. Returns ------- SICDType """ def convert_string_dict(dict_in): # type: (dict) -> dict dict_out = OrderedDict() for key, val in dict_in.items(): if isinstance(val, str): dict_out[key] = val elif isinstance(val, int): dict_out[key] = str(val) elif isinstance(val, float): dict_out[key] = '{0:0.17G}'.format(val) else: raise TypeError('Got unhandled type {}'.format(type(val))) return dict_out def extract_state_vector(): # type: () -> (numpy.ndarray, numpy.ndarray, numpy.ndarray) vecs = collect['state']['state_vectors'] times = numpy.zeros((len(vecs), ), dtype=numpy.float64) positions = numpy.zeros((len(vecs), 3), dtype=numpy.float64) velocities = numpy.zeros((len(vecs), 3), dtype=numpy.float64) for i, entry in enumerate(vecs): times[i] = get_seconds(parse_timestring(entry['time'], precision='ns'), start_time, precision='ns') positions[i, :] = entry['position'] velocities[i, :] = entry['velocity'] return times, positions, velocities def get_radar_parameter(name): if name in radar: return radar[name] if len(radar_time_varying) > 0: element = radar_time_varying[0] if name in element: return element[name] raise ValueError( 'Unable to determine radar parameter `{}`'.format(name)) def get_collection_info(): # type: () -> CollectionInfoType coll_name = collect['platform'] mode = collect['mode'].strip().lower() if mode == 'stripmap': radar_mode = RadarModeType(ModeType='STRIPMAP', ModeID=mode) elif mode == 'spotlight': radar_mode = RadarModeType(ModeType='SPOTLIGHT', ModeID=mode) elif mode == 'sliding_spotlight': radar_mode = RadarModeType(ModeType='DYNAMIC STRIPMAP', ModeID=mode) else: raise ValueError('Got unhandled radar mode {}'.format(mode)) return CollectionInfoType(CollectorName=coll_name, CoreName=collect['collect_id'], RadarMode=radar_mode, Classification='UNCLASSIFIED', CollectType='MONOSTATIC') def get_image_creation(): # type: () -> ImageCreationType from sarpy.__about__ import __version__ return ImageCreationType( Application=self._tiff_details.tags['Software'], DateTime=parse_timestring( self._img_desc_tags['processing_time'], precision='us'), Profile='sarpy {}'.format(__version__), Site='Unknown') def get_image_data(): # type: () -> ImageDataType rows = int( img['columns']) # capella uses flipped row/column definition? cols = int(img['rows']) if img['data_type'] == 'CInt16': pixel_type = 'RE16I_IM16I' else: raise ValueError('Got unhandled data_type {}'.format( img['data_type'])) scp_pixel = (int(0.5 * rows), int(0.5 * cols)) if radar['pointing'] == 'left': scp_pixel = (rows - scp_pixel[0] - 1, cols - scp_pixel[1] - 1) return ImageDataType(NumRows=rows, NumCols=cols, FirstRow=0, FirstCol=0, PixelType=pixel_type, FullImage=(rows, cols), SCPPixel=scp_pixel) def get_geo_data(): # type: () -> GeoDataType return GeoDataType(SCP=SCPType( ECF=img['center_pixel']['target_position'])) def get_position(): # type: () -> PositionType px, py, pz = fit_position_xvalidation(state_time, state_position, state_velocity, max_degree=8) return PositionType(ARPPoly=XYZPolyType(X=px, Y=py, Z=pz)) def get_grid(): # type: () -> GridType def get_weight(window_dict): window_name = window_dict['name'] if window_name.lower() == 'rectangular': return WgtTypeType(WindowName='UNIFORM'), None elif window_name.lower() == 'avci-nacaroglu': return WgtTypeType( WindowName=window_name.upper(), Parameters=convert_string_dict(window_dict['parameters'])), \ avci_nacaroglu_window(64, alpha=window_dict['parameters']['alpha']) else: return WgtTypeType(WindowName=window_name, Parameters=convert_string_dict( window_dict['parameters'])), None image_plane = 'SLANT' grid_type = 'RGZERO' coa_time = parse_timestring(img['center_pixel']['center_time'], precision='ns') row_bw = img.get('processed_range_bandwidth', bw) row_imp_rsp_bw = 2 * row_bw / speed_of_light row_wgt, row_wgt_funct = get_weight(img['range_window']) row = DirParamType(SS=img['image_geometry']['delta_range_sample'], Sgn=-1, ImpRespBW=row_imp_rsp_bw, ImpRespWid=img['range_resolution'], KCtr=2 * fc / speed_of_light, DeltaK1=-0.5 * row_imp_rsp_bw, DeltaK2=0.5 * row_imp_rsp_bw, DeltaKCOAPoly=[ [ 0.0, ], ], WgtFunct=row_wgt_funct, WgtType=row_wgt) # get timecoa value timecoa_value = get_seconds(coa_time, start_time) # find an approximation for zero doppler spacing - necessarily rough for backprojected images col_ss = img['pixel_spacing_row'] dop_bw = img['processed_azimuth_bandwidth'] col_wgt, col_wgt_funct = get_weight(img['azimuth_window']) col = DirParamType(SS=col_ss, Sgn=-1, ImpRespWid=img['azimuth_resolution'], ImpRespBW=dop_bw * abs(ss_zd_s) / col_ss, KCtr=0, WgtFunct=col_wgt_funct, WgtType=col_wgt) # TODO: # column deltakcoa poly - it's in there at ["image"]["frequency_doppler_centroid_polynomial"] return GridType(ImagePlane=image_plane, Type=grid_type, TimeCOAPoly=[ [ timecoa_value, ], ], Row=row, Col=col) def get_radar_collection(): # type: () -> RadarCollectionType freq_min = fc - 0.5 * bw return RadarCollectionType( TxPolarization=radar['transmit_polarization'], TxFrequency=(freq_min, freq_min + bw), Waveform=[ WaveformParametersType( TxRFBandwidth=bw, TxPulseLength=get_radar_parameter('pulse_duration'), RcvDemodType='CHIRP', ADCSampleRate=radar['sampling_frequency'], TxFreqStart=freq_min) ], RcvChannels=[ ChanParametersType(TxRcvPolarization='{}:{}'.format( radar['transmit_polarization'], radar['receive_polarization'])) ]) def get_timeline(): # type: () -> TimelineType prf = radar['prf'][0]['prf'] return TimelineType(CollectStart=start_time, CollectDuration=duration, IPP=[ IPPSetType(TStart=0, TEnd=duration, IPPStart=0, IPPEnd=duration * prf, IPPPoly=(0, prf)), ]) def get_image_formation(): # type: () -> ImageFormationType algo = img['algorithm'].upper() processings = None if algo == 'BACKPROJECTION': processings = [ ProcessingType(Type='Backprojected to DEM', Applied=True), ] else: logger.warning('Got unexpected algorithm, the results for the ' 'sicd struture might be unexpected') if algo not in ('PFA', 'RMA', 'RGAZCOMP'): logger.warning( 'Image formation algorithm {} not one of the recognized SICD options, ' 'being set to "OTHER".'.format(algo)) algo = 'OTHER' return ImageFormationType( RcvChanProc=RcvChanProcType(NumChanProc=1, PRFScaleFactor=1), ImageFormAlgo=algo, TStartProc=0, TEndProc=duration, TxRcvPolarizationProc='{}:{}'.format( radar['transmit_polarization'], radar['receive_polarization']), TxFrequencyProc=(radar_collection.TxFrequency.Min, radar_collection.TxFrequency.Max), STBeamComp='NO', ImageBeamComp='NO', AzAutofocus='NO', RgAutofocus='NO', Processings=processings) def get_rma(): # type: () -> RMAType img_geometry = img['image_geometry'] near_range = img_geometry['range_to_first_sample'] center_time = parse_timestring(img['center_pixel']['center_time'], precision='us') first_time = parse_timestring(img_geometry['first_line_time'], precision='us') zd_time_scp = get_seconds(center_time, first_time, 'us') r_ca_scp = near_range + image_data.SCPPixel.Row * grid.Row.SS time_ca_poly = numpy.array( [zd_time_scp, -look * ss_zd_s / grid.Col.SS], dtype='float64') timecoa_value = get_seconds(center_time, start_time) arp_velocity = position.ARPPoly.derivative_eval(timecoa_value, der_order=1) vm_ca = numpy.linalg.norm(arp_velocity) inca = INCAType(R_CA_SCP=r_ca_scp, FreqZero=fc, TimeCAPoly=time_ca_poly, DRateSFPoly=[ [1 / (vm_ca * ss_zd_s / grid.Col.SS)], ]) return RMAType(RMAlgoType='RG_DOP', INCA=inca) def get_radiometric(): # type: () -> Union[None, RadiometricType] if img['radiometry'].lower() != 'beta_nought': logger.warning('Got unrecognized Capella radiometry {},\n\t' 'skipping the radiometric metadata'.format( img['radiometry'])) return None return RadiometricType(BetaZeroSFPoly=[ [ img['scale_factor']**2, ], ]) def add_noise(): if sicd.Radiometric is None: return nesz_raw = numpy.array(img['nesz_polynomial']['coefficients'], dtype='float64') test_value = polynomial.polyval(rma.INCA.R_CA_SCP, nesz_raw) if abs(test_value - img['nesz_peak']) > 100: # this polynomial reversed in early versions, so reverse if evaluated results are nonsense nesz_raw = nesz_raw[::-1] nesz_poly_raw = Poly2DType(Coefs=numpy.reshape(nesz_raw, (-1, 1))) noise_coeffs = nesz_poly_raw.shift(-rma.INCA.R_CA_SCP, 1, 0, 1, return_poly=False) # this is in nesz units, so shift to absolute units noise_coeffs[0] -= 10 * numpy.log10( sicd.Radiometric.SigmaZeroSFPoly[0, 0]) sicd.Radiometric.NoiseLevel = NoiseLevelType_( NoiseLevelType='ABSOLUTE', NoisePoly=noise_coeffs) # extract general use information collect = self._img_desc_tags['collect'] img = collect['image'] radar = collect['radar'] radar_time_varying = radar.get('time_varying_parameters', []) start_time = parse_timestring(collect['start_timestamp'], precision='ns') end_time = parse_timestring(collect['stop_timestamp'], precision='ns') duration = get_seconds(end_time, start_time, precision='ns') state_time, state_position, state_velocity = extract_state_vector() bw = get_radar_parameter('pulse_bandwidth') fc = get_radar_parameter('center_frequency') ss_zd_s = img['image_geometry']['delta_line_time'] look = -1 if radar['pointing'] == 'right' else 1 # define the sicd elements collection_info = get_collection_info() image_creation = get_image_creation() image_data = get_image_data() geo_data = get_geo_data() position = get_position() grid = get_grid() radar_collection = get_radar_collection() timeline = get_timeline() image_formation = get_image_formation() rma = get_rma() radiometric = get_radiometric() sicd = SICDType(CollectionInfo=collection_info, ImageCreation=image_creation, ImageData=image_data, GeoData=geo_data, Position=position, Grid=grid, RadarCollection=radar_collection, Timeline=timeline, ImageFormation=image_formation, RMA=rma, Radiometric=radiometric) sicd.derive() add_noise() sicd.populate_rniirs(override=False) return sicd
def extract_sicd(img_header, symmetry, nitf_header=None): """ Extract the best available SICD structure from relevant nitf header structures. Parameters ---------- img_header : ImageSegmentHeader|ImageSegmentHeader0 symmetry : tuple nitf_header : None|NITFHeader|NITFHeader0 Returns ------- SICDType """ def get_collection_info(): # type: () -> CollectionInfoType isorce = img_header.ISORCE.strip() collector_name = None if len(isorce) < 1 else isorce iid2 = img_header.IID2.strip() core_name = img_header.IID1.strip() if len(iid2) < 1 else iid2 class_str = img_header.Security.CLAS if class_str == 'T': classification = 'TOPSECRET' elif class_str == 'S': classification = 'SECRET' elif class_str == 'C': classification = 'CONFIDENTIAL' elif class_str == 'U': classification = 'UNCLASSIFIED' else: classification = '' ctlh = img_header.Security.CTLH.strip() if len(ctlh) < 1: classification += '//' + ctlh code = img_header.Security.CODE.strip() if len(code) < 1: classification += '//' + code return CollectionInfoType( CollectorName=collector_name, CoreName=core_name, Classification=classification) def get_image_data(): # type: () -> ImageDataType pvtype = img_header.PVTYPE if pvtype == 'C': if img_header.NBPP != 64: logger.warning( 'This NITF has complex bands that are not 64-bit.\n\t' 'This is not currently supported.') pixel_type = 'RE32F_IM32F' elif pvtype == 'R': if img_header.NBPP == 64: logger.warning( 'The real/imaginary data in the NITF are stored as 64-bit floating point.\n\t' 'The closest Pixel Type, RE32F_IM32F, will be used,\n\t' 'but there may be overflow issues if converting this file.') pixel_type = 'RE32F_IM32F' elif pvtype == 'SI': pixel_type = 'RE16I_IM16I' else: raise ValueError('Got unhandled PVTYPE {}'.format(pvtype)) if symmetry[2]: rows = img_header.NCOLS cols = img_header.NROWS else: rows = img_header.NROWS cols = img_header.NCOLS return ImageDataType( PixelType=pixel_type, NumRows=rows, NumCols=cols, FirstRow=0, FirstCol=0, FullImage=(rows, cols), SCPPixel=(0.5 * rows, 0.5 * cols)) def append_country_code(cc): if len(cc) > 0: if the_sicd.CollectionInfo is None: the_sicd.CollectionInfo = CollectionInfoType(CountryCodes=[cc, ]) elif the_sicd.CollectionInfo.CountryCodes is None: the_sicd.CollectionInfo.CountryCodes = [cc, ] elif cc not in the_sicd.CollectionInfo.CountryCodes: the_sicd.CollectionInfo.CountryCodes.append(cc) def set_image_corners(icps, override=False): if the_sicd.GeoData is None: the_sicd.GeoData = GeoDataType(ImageCorners=icps) elif the_sicd.GeoData.ImageCorners is None or override: the_sicd.GeoData.ImageCorners = icps def set_arp_position(arp_ecf, override=False): if the_sicd.SCPCOA is None: the_sicd.SCPCOA = SCPCOAType(ARPPos=arp_ecf) elif override: # prioritize this information first - it should be more reliable than other sources the_sicd.SCPCOA.ARPPos = arp_ecf def set_scp(scp_ecf, scp_pixel, override=False): def set_scppixel(): if the_sicd.ImageData is None: the_sicd.ImageData = ImageDataType(SCPPixel=scp_pixel) else: the_sicd.ImageData.SCPPixel = scp_pixel if the_sicd.GeoData is None: the_sicd.GeoData = GeoDataType(SCP=SCPType(ECF=scp_ecf)) set_scppixel() elif the_sicd.GeoData.SCP is None or override: the_sicd.GeoData.SCP = SCPType(ECF=scp_ecf) set_scppixel() def set_collect_start(collect_start, override=False): if the_sicd.Timeline is None: the_sicd.Timeline = TimelineType(CollectStart=collect_start) elif the_sicd.Timeline.CollectStart is None or override: the_sicd.Timeline.CollectStart = collect_start def set_uvects(row_unit, col_unit): if the_sicd.Grid is None: the_sicd.Grid = GridType(Row=DirParamType(UVectECF=row_unit), Col=DirParamType(UVectECF=col_unit)) return if the_sicd.Grid.Row is None: the_sicd.Grid.Row = DirParamType(UVectECF=row_unit) elif the_sicd.Grid.Row.UVectECF is None: the_sicd.Grid.Row.UVectECF = row_unit if the_sicd.Grid.Col is None: the_sicd.Grid.Col = DirParamType(UVectECF=col_unit) elif the_sicd.Grid.Col.UVectECF is None: the_sicd.Grid.Col.UVectECF = col_unit def try_CMETAA(): tre = None if tres is None else tres['CMETAA'] # type: CMETAA if tre is None: return cmetaa = tre.DATA if the_sicd.GeoData is None: the_sicd.GeoData = GeoDataType() if the_sicd.SCPCOA is None: the_sicd.SCPCOA = SCPCOAType() if the_sicd.Grid is None: the_sicd.Grid = GridType() if the_sicd.Timeline is None: the_sicd.Timeline = TimelineType() if the_sicd.RadarCollection is None: the_sicd.RadarCollection = RadarCollectionType() if the_sicd.ImageFormation is None: the_sicd.ImageFormation = ImageFormationType() the_sicd.SCPCOA.SCPTime = 0.5*float(cmetaa.WF_CDP) the_sicd.GeoData.SCP = SCPType(ECF=tre.get_scp()) the_sicd.SCPCOA.ARPPos = tre.get_arp() the_sicd.SCPCOA.SideOfTrack = cmetaa.CG_LD.strip().upper() the_sicd.SCPCOA.SlantRange = float(cmetaa.CG_SRAC) the_sicd.SCPCOA.DopplerConeAng = float(cmetaa.CG_CAAC) the_sicd.SCPCOA.GrazeAng = float(cmetaa.CG_GAAC) the_sicd.SCPCOA.IncidenceAng = 90 - float(cmetaa.CG_GAAC) if hasattr(cmetaa, 'CG_TILT'): the_sicd.SCPCOA.TwistAng = float(cmetaa.CG_TILT) if hasattr(cmetaa, 'CG_SLOPE'): the_sicd.SCPCOA.SlopeAng = float(cmetaa.CG_SLOPE) the_sicd.ImageData.SCPPixel = [int(cmetaa.IF_DC_IS_COL), int(cmetaa.IF_DC_IS_ROW)] img_corners = tre.get_image_corners() if img_corners is not None: the_sicd.GeoData.ImageCorners = img_corners if cmetaa.CMPLX_SIGNAL_PLANE.upper() == 'S': the_sicd.Grid.ImagePlane = 'SLANT' elif cmetaa.CMPLX_SIGNAL_PLANE.upper() == 'G': the_sicd.Grid.ImagePlane = 'GROUND' else: logger.warning( 'Got unexpected CMPLX_SIGNAL_PLANE value {},\n\t' 'setting ImagePlane to SLANT'.format(cmetaa.CMPLX_SIGNAL_PLANE)) the_sicd.Grid.Row = DirParamType( SS=float(cmetaa.IF_RSS), ImpRespWid=float(cmetaa.IF_RGRES), Sgn=1 if cmetaa.IF_RFFTS.strip() == '-' else -1, # opposite sign convention ImpRespBW=float(cmetaa.IF_RFFT_SAMP)/(float(cmetaa.IF_RSS)*float(cmetaa.IF_RFFT_TOT))) the_sicd.Grid.Col = DirParamType( SS=float(cmetaa.IF_AZSS), ImpRespWid=float(cmetaa.IF_AZRES), Sgn=1 if cmetaa.IF_AFFTS.strip() == '-' else -1, # opposite sign convention ImpRespBW=float(cmetaa.IF_AZFFT_SAMP)/(float(cmetaa.IF_AZSS)*float(cmetaa.IF_AZFFT_TOT))) cmplx_weight = cmetaa.CMPLX_WEIGHT.strip().upper() if cmplx_weight == 'UWT': the_sicd.Grid.Row.WgtType = WgtTypeType(WindowName='UNIFORM') the_sicd.Grid.Col.WgtType = WgtTypeType(WindowName='UNIFORM') elif cmplx_weight == 'HMW': the_sicd.Grid.Row.WgtType = WgtTypeType(WindowName='HAMMING') the_sicd.Grid.Col.WgtType = WgtTypeType(WindowName='HAMMING') elif cmplx_weight == 'HNW': the_sicd.Grid.Row.WgtType = WgtTypeType(WindowName='HANNING') the_sicd.Grid.Col.WgtType = WgtTypeType(WindowName='HANNING') elif cmplx_weight == 'TAY': the_sicd.Grid.Row.WgtType = WgtTypeType( WindowName='TAYLOR', Parameters={ 'SLL': '-{0:d}'.format(int(cmetaa.CMPLX_RNG_SLL)), 'NBAR': '{0:d}'.format(int(cmetaa.CMPLX_RNG_TAY_NBAR))}) the_sicd.Grid.Col.WgtType = WgtTypeType( WindowName='TAYLOR', Parameters={ 'SLL': '-{0:d}'.format(int(cmetaa.CMPLX_AZ_SLL)), 'NBAR': '{0:d}'.format(int(cmetaa.CMPLX_AZ_TAY_NBAR))}) else: logger.warning( 'Got unsupported CMPLX_WEIGHT value {}.\n\tThe resulting SICD will ' 'not have valid weight array populated'.format(cmplx_weight)) the_sicd.Grid.Row.define_weight_function() the_sicd.Grid.Col.define_weight_function() # noinspection PyBroadException try: date_str = cmetaa.T_UTC_YYYYMMMDD time_str = cmetaa.T_HHMMSSUTC date_time = _iso_date_format.format( date_str[:4], date_str[4:6], date_str[6:8], time_str[:2], time_str[2:4], time_str[4:6]) the_sicd.Timeline.CollectStart = numpy.datetime64(date_time, 'us') except Exception: logger.info('Failed extracting start time from CMETAA') pass the_sicd.Timeline.CollectDuration = float(cmetaa.WF_CDP) the_sicd.Timeline.IPP = [ IPPSetType(TStart=0, TEnd=float(cmetaa.WF_CDP), IPPStart=0, IPPEnd=numpy.floor(float(cmetaa.WF_CDP)*float(cmetaa.WF_PRF)), IPPPoly=[0, float(cmetaa.WF_PRF)])] the_sicd.RadarCollection.TxFrequency = TxFrequencyType( Min=float(cmetaa.WF_SRTFR), Max=float(cmetaa.WF_ENDFR)) the_sicd.RadarCollection.TxPolarization = cmetaa.POL_TR.upper() the_sicd.RadarCollection.Waveform = [WaveformParametersType( TxPulseLength=float(cmetaa.WF_WIDTH), TxRFBandwidth=float(cmetaa.WF_BW), TxFreqStart=float(cmetaa.WF_SRTFR), TxFMRate=float(cmetaa.WF_CHRPRT)*1e12)] tx_rcv_pol = '{}:{}'.format(cmetaa.POL_TR.upper(), cmetaa.POL_RE.upper()) the_sicd.RadarCollection.RcvChannels = [ ChanParametersType(TxRcvPolarization=tx_rcv_pol)] the_sicd.ImageFormation.TxRcvPolarizationProc = tx_rcv_pol if_process = cmetaa.IF_PROCESS.strip().upper() if if_process == 'PF': the_sicd.ImageFormation.ImageFormAlgo = 'PFA' scp_ecf = tre.get_scp() fpn_ned = numpy.array( [float(cmetaa.CG_FPNUV_X), float(cmetaa.CG_FPNUV_Y), float(cmetaa.CG_FPNUV_Z)], dtype='float64') ipn_ned = numpy.array( [float(cmetaa.CG_IDPNUVX), float(cmetaa.CG_IDPNUVY), float(cmetaa.CG_IDPNUVZ)], dtype='float64') fpn_ecf = ned_to_ecf(fpn_ned, scp_ecf, absolute_coords=False) ipn_ecf = ned_to_ecf(ipn_ned, scp_ecf, absolute_coords=False) the_sicd.PFA = PFAType(FPN=fpn_ecf, IPN=ipn_ecf) elif if_process in ['RM', 'CD']: the_sicd.ImageFormation.ImageFormAlgo = 'RMA' # the remainder of this is guesswork to define required fields the_sicd.ImageFormation.TStartProc = 0 # guess work the_sicd.ImageFormation.TEndProc = float(cmetaa.WF_CDP) the_sicd.ImageFormation.TxFrequencyProc = TxFrequencyProcType( MinProc=float(cmetaa.WF_SRTFR), MaxProc=float(cmetaa.WF_ENDFR)) # all remaining guess work the_sicd.ImageFormation.STBeamComp = 'NO' the_sicd.ImageFormation.ImageBeamComp = 'SV' if cmetaa.IF_BEAM_COMP[0] == 'Y' else 'NO' the_sicd.ImageFormation.AzAutofocus = 'NO' if cmetaa.AF_TYPE[0] == 'N' else 'SV' the_sicd.ImageFormation.RgAutofocus = 'NO' def try_AIMIDA(): tre = None if tres is None else tres['AIMIDA'] if tre is None: return aimida = tre.DATA append_country_code(aimida.COUNTRY.strip()) create_time = datetime.strptime(aimida.CREATION_DATE, '%d%b%y') if the_sicd.ImageCreation is None: the_sicd.ImageCreation = ImageCreationType(DateTime=create_time) elif the_sicd.ImageCreation.DateTime is None: the_sicd.ImageCreation.DateTime = create_time collect_start = datetime.strptime(aimida.MISSION_DATE+aimida.TIME, '%d%b%y%H%M') set_collect_start(collect_start, override=False) def try_AIMIDB(): tre = None if tres is None else tres['AIMIDB'] if tre is None: return aimidb = tre.DATA append_country_code(aimidb.COUNTRY.strip()) if the_sicd.ImageFormation is not None and the_sicd.ImageFormation.SegmentIdentifier is None: the_sicd.ImageFormation.SegmentIdentifier = aimidb.CURRENT_SEGMENT.strip() date_str = aimidb.ACQUISITION_DATE collect_start = numpy.datetime64(_iso_date_format.format( date_str[:4], date_str[4:6], date_str[6:8], date_str[8:10], date_str[10:12], date_str[12:14]), 'us') set_collect_start(collect_start, override=False) def try_ACFT(): if tres is None: return tre = tres['ACFTA'] if tre is None: tre = tres['ACFTB'] if tre is None: return acft = tre.DATA sensor_id = acft.SENSOR_ID.strip() if len(sensor_id) > 1: if the_sicd.CollectionInfo is None: the_sicd.CollectionInfo = CollectionInfoType(CollectorName=sensor_id) elif the_sicd.CollectionInfo.CollectorName is None: the_sicd.CollectionInfo.CollectorName = sensor_id row_ss = float(acft.ROW_SPACING) col_ss = float(acft.COL_SPACING) if hasattr(acft, 'ROW_SPACING_UNITS') and acft.ROW_SPACING_UNITS.strip().lower() == 'f': row_ss *= foot if hasattr(acft, 'COL_SPACING_UNITS') and acft.COL_SPACING_UNITS.strip().lower() == 'f': col_ss *= foot # NB: these values are actually ground plane values, and should be # corrected to slant plane if possible if the_sicd.SCPCOA is not None: if the_sicd.SCPCOA.GrazeAng is not None: col_ss *= numpy.cos(numpy.deg2rad(the_sicd.SCPCOA.GrazeAng)) if the_sicd.SCPCOA.TwistAng is not None: row_ss *= numpy.cos(numpy.deg2rad(the_sicd.SCPCOA.TwistAng)) if the_sicd.Grid is None: the_sicd.Grid = GridType(Row=DirParamType(SS=row_ss), Col=DirParamType(SS=col_ss)) return if the_sicd.Grid.Row is None: the_sicd.Grid.Row = DirParamType(SS=row_ss) elif the_sicd.Grid.Row.SS is None: the_sicd.Grid.Row.SS = row_ss if the_sicd.Grid.Col is None: the_sicd.Grid.Col = DirParamType(SS=col_ss) elif the_sicd.Grid.Col.SS is None: the_sicd.Grid.Col.SS = col_ss def try_BLOCKA(): tre = None if tres is None else tres['BLOCKA'] if tre is None: return blocka = tre.DATA icps = [] for fld_name in ['FRFC_LOC', 'FRLC_LOC', 'LRLC_LOC', 'LRFC_LOC']: value = getattr(blocka, fld_name) # noinspection PyBroadException try: lat_val = float(value[:10]) lon_val = float(value[10:21]) except ValueError: lat_val = lat_lon_parser(value[:10]) lon_val = lat_lon_parser(value[10:21]) icps.append([lat_val, lon_val]) set_image_corners(icps, override=False) def try_MPDSRA(): def valid_array(arr): return numpy.all(numpy.isfinite(arr)) and numpy.any(arr != 0) tre = None if tres is None else tres['MPDSRA'] if tre is None: return mpdsra = tre.DATA scp_ecf = foot*numpy.array( [float(mpdsra.ORO_X), float(mpdsra.ORO_Y), float(mpdsra.ORO_Z)], dtype='float64') if valid_array(scp_ecf): set_scp(scp_ecf, (int(mpdsra.ORP_COLUMN) - 1, int(mpdsra.ORP_ROW) - 1), override=False) arp_pos_ned = foot*numpy.array( [float(mpdsra.ARP_POS_N), float(mpdsra.ARP_POS_E), float(mpdsra.ARP_POS_D)], dtype='float64') arp_vel_ned = foot*numpy.array( [float(mpdsra.ARP_VEL_N), float(mpdsra.ARP_VEL_E), float(mpdsra.ARP_VEL_D)], dtype='float64') arp_acc_ned = foot*numpy.array( [float(mpdsra.ARP_ACC_N), float(mpdsra.ARP_ACC_E), float(mpdsra.ARP_ACC_D)], dtype='float64') arp_pos = ned_to_ecf(arp_pos_ned, scp_ecf, absolute_coords=True) if valid_array(arp_pos_ned) else None set_arp_position(arp_pos, override=False) arp_vel = ned_to_ecf(arp_vel_ned, scp_ecf, absolute_coords=False) if valid_array(arp_vel_ned) else None if the_sicd.SCPCOA.ARPVel is None: the_sicd.SCPCOA.ARPVel = arp_vel arp_acc = ned_to_ecf(arp_acc_ned, scp_ecf, absolute_coords=False) if valid_array(arp_acc_ned) else None if the_sicd.SCPCOA.ARPAcc is None: the_sicd.SCPCOA.ARPAcc = arp_acc if the_sicd.PFA is not None and the_sicd.PFA.FPN is None: # TODO: is this already in meters? fpn_ecf = numpy.array( [float(mpdsra.FOC_X), float(mpdsra.FOC_Y), float(mpdsra.FOC_Z)], dtype='float64') # *foot if valid_array(fpn_ecf): the_sicd.PFA.FPN = fpn_ecf def try_MENSRB(): tre = None if tres is None else tres['MENSRB'] if tre is None: return mensrb = tre.DATA arp_llh = numpy.array( [lat_lon_parser(mensrb.ACFT_LOC[:12]), lat_lon_parser(mensrb.ACFT_LOC[12:25]), foot*float(mensrb.ACFT_ALT)], dtype='float64') scp_llh = numpy.array( [lat_lon_parser(mensrb.RP_LOC[:12]), lat_lon_parser(mensrb.RP_LOC[12:25]), foot*float(mensrb.RP_ELV)], dtype='float64') # TODO: handle the conversion from msl to hae arp_ecf = geodetic_to_ecf(arp_llh) scp_ecf = geodetic_to_ecf(scp_llh) set_arp_position(arp_ecf, override=True) set_scp(scp_ecf, (int(mensrb.RP_COL)-1, int(mensrb.RP_ROW)-1), override=False) row_unit_ned = numpy.array( [float(mensrb.C_R_NC), float(mensrb.C_R_EC), float(mensrb.C_R_DC)], dtype='float64') col_unit_ned = numpy.array( [float(mensrb.C_AZ_NC), float(mensrb.C_AZ_EC), float(mensrb.C_AZ_DC)], dtype='float64') set_uvects(ned_to_ecf(row_unit_ned, scp_ecf, absolute_coords=False), ned_to_ecf(col_unit_ned, scp_ecf, absolute_coords=False)) def try_MENSRA(): tre = None if tres is None else tres['MENSRA'] if tre is None: return mensra = tre.DATA arp_llh = numpy.array( [lat_lon_parser(mensra.ACFT_LOC[:10]), lat_lon_parser(mensra.ACFT_LOC[10:21]), foot*float(mensra.ACFT_ALT)], dtype='float64') scp_llh = numpy.array( [lat_lon_parser(mensra.CP_LOC[:10]), lat_lon_parser(mensra.CP_LOC[10:21]), foot*float(mensra.CP_ALT)], dtype='float64') # TODO: handle the conversion from msl to hae arp_ecf = geodetic_to_ecf(arp_llh) scp_ecf = geodetic_to_ecf(scp_llh) set_arp_position(arp_ecf, override=True) # TODO: is this already zero based? set_scp(geodetic_to_ecf(scp_llh), (int(mensra.CCRP_COL), int(mensra.CCRP_ROW)), override=False) row_unit_ned = numpy.array( [float(mensra.C_R_NC), float(mensra.C_R_EC), float(mensra.C_R_DC)], dtype='float64') col_unit_ned = numpy.array( [float(mensra.C_AZ_NC), float(mensra.C_AZ_EC), float(mensra.C_AZ_DC)], dtype='float64') set_uvects(ned_to_ecf(row_unit_ned, scp_ecf, absolute_coords=False), ned_to_ecf(col_unit_ned, scp_ecf, absolute_coords=False)) def extract_corners(): icps = extract_image_corners(img_header) if icps is None: return # TODO: include symmetry transform issue set_image_corners(icps, override=False) def extract_start(): # noinspection PyBroadException try: date_str = img_header.IDATIM collect_start = numpy.datetime64( _iso_date_format.format( date_str[:4], date_str[4:6], date_str[6:8], date_str[8:10], date_str[10:12], date_str[12:14]), 'us') except Exception: logger.info('failed extracting start time from IDATIM tre') return set_collect_start(collect_start, override=False) # noinspection PyUnresolvedReferences tres = None if img_header.ExtendedHeader.data is None \ else img_header.ExtendedHeader.data # type: Union[None, TREList] collection_info = get_collection_info() image_data = get_image_data() the_sicd = SICDType( CollectionInfo=collection_info, ImageData=image_data) # apply the various tres and associated logic # NB: this should generally be in order of preference try_CMETAA() try_AIMIDB() try_AIMIDA() try_ACFT() try_BLOCKA() try_MPDSRA() try_MENSRA() try_MENSRB() extract_corners() extract_start() return the_sicd
def _get_base_sicd(self, h5_dict, band_dict): # type: (dict, dict) -> SICDType def get_collection_info(): # type: () -> CollectionInfoType mode_type = 'STRIPMAP' if h5_dict['Acquisition Mode'] in \ ['HIMAGE', 'PINGPONG', 'WIDEREGION', 'HUGEREGION'] else 'DYNAMIC STRIPMAP' return CollectionInfoType(Classification='UNCLASSIFIED', CollectorName=h5_dict['Satellite ID'], CoreName=str(h5_dict['Programmed Image ID']), CollectType='MONOSTATIC', RadarMode=RadarModeType(ModeID=h5_dict['Multi-Beam ID'], ModeType=mode_type)) def get_image_creation(): # type: () -> ImageCreationType from sarpy.__about__ import __version__ return ImageCreationType( DateTime=parse_timestring(h5_dict['Product Generation UTC'], precision='ns'), Profile='sarpy {}'.format(__version__)) def get_grid(): # type: () -> GridType if h5_dict['Projection ID'] == 'SLANT RANGE/AZIMUTH': image_plane = 'SLANT' gr_type = 'RGZERO' else: image_plane = 'GROUND' gr_type = None # Row row_window_name = h5_dict['Range Focusing Weighting Function'].rstrip().upper() row_params = None if row_window_name == 'HAMMING': row_params = {'COEFFICIENT': '{0:15f}'.format(h5_dict['Range Focusing Weighting Coefficient'])} row = DirParamType(Sgn=-1, KCtr=2*center_frequency/speed_of_light, DeltaKCOAPoly=Poly2DType(Coefs=[[0, ], ]), WgtType=WgtTypeType(WindowName=row_window_name, Parameters=row_params)) # Col col_window_name = h5_dict['Azimuth Focusing Weighting Function'].rstrip().upper() col_params = None if col_window_name == 'HAMMING': col_params = {'COEFFICIENT': '{0:15f}'.format(h5_dict['Azimuth Focusing Weighting Coefficient'])} col = DirParamType(Sgn=-1, KCtr=0, WgtType=WgtTypeType(WindowName=col_window_name, Parameters=col_params)) return GridType(ImagePlane=image_plane, Type=gr_type, Row=row, Col=col) def get_timeline(): # type: () -> TimelineType return TimelineType(CollectStart=collect_start, CollectDuration=duration, IPP=[IPPSetType(index=0, TStart=0, TEnd=0, IPPStart=0, IPPEnd=0), ]) # NB: IPPEnd must be set, but will be replaced def get_position(): # type: () -> PositionType T = h5_dict['State Vectors Times'] # in seconds relative to ref time T += ref_time_offset Pos = h5_dict['ECEF Satellite Position'] Vel = h5_dict['ECEF Satellite Velocity'] P_x, P_y, P_z = fit_position_xvalidation(T, Pos, Vel, max_degree=8) return PositionType(ARPPoly=XYZPolyType(X=P_x, Y=P_y, Z=P_z)) def get_radar_collection(): # type: () -> RadarCollectionType tx_pols = [] chan_params = [] for i, bdname in enumerate(band_dict): pol = band_dict[bdname]['Polarisation'] tx_pols.append(pol[0]) chan_params.append(ChanParametersType(TxRcvPolarization=self._parse_pol(pol), index=i)) if len(tx_pols) == 1: return RadarCollectionType(RcvChannels=chan_params, TxPolarization=tx_pols[0]) else: return RadarCollectionType(RcvChannels=chan_params, TxPolarization='SEQUENCE', TxSequence=[TxStepType(TxPolarization=pol, index=i+1) for i, pol in enumerate(tx_pols)]) def get_image_formation(): # type: () -> ImageFormationType return ImageFormationType(ImageFormAlgo='RMA', TStartProc=0, TEndProc=duration, STBeamComp='SV', ImageBeamComp='SV', AzAutofocus='NO', RgAutofocus='NO', RcvChanProc=RcvChanProcType(NumChanProc=1, PRFScaleFactor=1)) def get_rma(): # type: () -> RMAType inca = INCAType(FreqZero=center_frequency) return RMAType(RMAlgoType='OMEGA_K', INCA=inca) def get_scpcoa(): # type: () -> SCPCOAType return SCPCOAType(SideOfTrack=h5_dict['Look Side'][0:1].upper()) # some common use parameters center_frequency = h5_dict['Radar Frequency'] # relative times in csk are wrt some reference time - for sicd they should be relative to start time collect_start = parse_timestring(h5_dict['Scene Sensing Start UTC'], precision='ns') collect_end = parse_timestring(h5_dict['Scene Sensing Stop UTC'], precision='ns') duration = get_seconds(collect_end, collect_start, precision='ns') ref_time = parse_timestring(h5_dict['Reference UTC'], precision='ns') ref_time_offset = get_seconds(ref_time, collect_start, precision='ns') # assemble our pieces collection_info = get_collection_info() image_creation = get_image_creation() grid = get_grid() timeline = get_timeline() position = get_position() radar_collection = get_radar_collection() image_formation = get_image_formation() rma = get_rma() scpcoa = get_scpcoa() return SICDType(CollectionInfo=collection_info, ImageCreation=image_creation, Grid=grid, Timeline=timeline, Position=position, RadarCollection=radar_collection, ImageFormation=image_formation, RMA=rma, SCPCOA=scpcoa)
def get_sicd(self): """ Gets the SICD structure. Returns ------- Tuple[SICDType, tuple, tuple] The sicd structure, the data size argument, and the symmetry argument. """ def get_collection_info(): # type: () -> CollectionInfoType return CollectionInfoType( CollectorName=_stringify(hf['satellite_name'][()]), CoreName=_stringify(hf['product_name'][()]), CollectType='MONOSTATIC', Classification='UNCLASSIFIED', RadarMode=RadarModeType( ModeType=_stringify(hf['acquisition_mode'][()]).upper(), ModeID=_stringify(hf['product_type'][()]))) def get_image_creation(): # type: () -> ImageCreationType from sarpy.__about__ import __version__ return ImageCreationType( Application='ICEYE_P_{}'.format(hf['processor_version'][()]), DateTime=_parse_time(hf['processing_time'][()]), Site='Unknown', Profile='sarpy {}'.format(__version__)) def get_image_data(): # type: () -> ImageDataType samp_prec = _stringify(hf['sample_precision'][()]) if samp_prec.upper() == 'INT16': pixel_type = 'RE16I_IM16I' elif samp_prec.upper() == 'FLOAT32': pixel_type = 'RE32F_IM32F' else: raise ValueError( 'Got unhandled sample precision {}'.format(samp_prec)) num_rows = int_func(number_of_range_samples) num_cols = int_func(number_of_azimuth_samples) scp_row = int_func(coord_center[0]) - 1 scp_col = int_func(coord_center[1]) - 1 if 0 < scp_col < num_rows - 1: if look_side == 'left': scp_col = num_cols - scp_col - 1 else: # early ICEYE processing bug led to nonsensical SCP scp_col = int_func(num_cols / 2.0) return ImageDataType(PixelType=pixel_type, NumRows=num_rows, NumCols=num_cols, FirstRow=0, FirstCol=0, FullImage=(num_rows, num_cols), SCPPixel=(scp_row, scp_col)) def get_geo_data(): # type: () -> GeoDataType # NB: the remainder will be derived. return GeoDataType(SCP=SCPType( LLH=[coord_center[2], coord_center[3], avg_scene_height])) def get_timeline(): # type: () -> TimelineType acq_prf = hf['acquisition_prf'][()] return TimelineType(CollectStart=start_time, CollectDuration=duration, IPP=[ IPPSetType(index=0, TStart=0, TEnd=duration, IPPStart=0, IPPEnd=int_func( round(acq_prf * duration)), IPPPoly=[0, acq_prf]), ]) def get_position(): # type: () -> PositionType # fetch the state information times_str = hf['state_vector_time_utc'][:, 0] times = numpy.zeros((times_str.shape[0], ), dtype='float64') positions = numpy.zeros((times.size, 3), dtype='float64') velocities = numpy.zeros((times.size, 3), dtype='float64') for i, entry in enumerate(times_str): times[i] = get_seconds(_parse_time(entry), start_time, precision='us') positions[:, 0], positions[:, 1], positions[:, 2] = hf[ 'posX'][:], hf['posY'][:], hf['posZ'][:] velocities[:, 0], velocities[:, 1], velocities[:, 2] = hf[ 'velX'][:], hf['velY'][:], hf['velZ'][:] # fir the the position polynomial using cross validation P_x, P_y, P_z = fit_position_xvalidation(times, positions, velocities, max_degree=8) return PositionType(ARPPoly=XYZPolyType(X=P_x, Y=P_y, Z=P_z)) def get_radar_collection(): # type : () -> RadarCollection return RadarCollectionType( TxPolarization=tx_pol, TxFrequency=TxFrequencyType(Min=min_freq, Max=max_freq), Waveform=[ WaveformParametersType( TxFreqStart=min_freq, TxRFBandwidth=tx_bandwidth, TxPulseLength=hf['chirp_duration'][()], ADCSampleRate=hf['range_sampling_rate'][()], RcvDemodType='CHIRP', RcvFMRate=0, index=1) ], RcvChannels=[ ChanParametersType(TxRcvPolarization=polarization, index=1) ]) def get_image_formation(): # type: () -> ImageFormationType return ImageFormationType( TxRcvPolarizationProc=polarization, ImageFormAlgo='RMA', TStartProc=0, TEndProc=duration, TxFrequencyProc=TxFrequencyProcType(MinProc=min_freq, MaxProc=max_freq), STBeamComp='NO', ImageBeamComp='SV', AzAutofocus='NO', RgAutofocus='NO', RcvChanProc=RcvChanProcType(NumChanProc=1, PRFScaleFactor=1, ChanIndices=[ 1, ]), ) def get_radiometric(): # type: () -> RadiometricType return RadiometricType(BetaZeroSFPoly=[ [ float(hf['calibration_factor'][()]), ], ]) def calculate_drate_sf_poly(): r_ca_coeffs = numpy.array([r_ca_scp, 1], dtype='float64') dop_rate_coeffs = hf['doppler_rate_coeffs'][:] # Prior to ICEYE 1.14 processor, absolute value of Doppler rate was # provided, not true Doppler rate. Doppler rate should always be negative if dop_rate_coeffs[0] > 0: dop_rate_coeffs *= -1 dop_rate_poly = Poly1DType(Coefs=dop_rate_coeffs) # now adjust to create t_drate_ca_poly = dop_rate_poly.shift(t_0=zd_ref_time - rg_time_scp, alpha=2 / speed_of_light, return_poly=False) return t_drate_ca_poly, -polynomial.polymul( t_drate_ca_poly, r_ca_coeffs) * speed_of_light / ( 2 * center_freq * vm_ca_sq) def calculate_doppler_polys(): # extract doppler centroid coefficients dc_estimate_coeffs = hf['dc_estimate_coeffs'][:] dc_time_str = hf['dc_estimate_time_utc'][:, 0] dc_zd_times = numpy.zeros((dc_time_str.shape[0], ), dtype='float64') for i, entry in enumerate(dc_time_str): dc_zd_times[i] = get_seconds(_parse_time(entry), start_time, precision='us') # create a sampled doppler centroid samples = 49 # copied from corresponding matlab, we just need enough for appropriate refitting # create doppler time samples diff_time_rg = first_pixel_time - zd_ref_time + \ numpy.linspace(0, number_of_range_samples/range_sampling_rate, samples) # doppler centroid samples definition dc_sample_array = numpy.zeros((samples, dc_zd_times.size), dtype='float64') for i, coeffs in enumerate(dc_estimate_coeffs): dc_sample_array[:, i] = polynomial.polyval(diff_time_rg, coeffs) # create arrays for range/azimuth from scp in meters azimuth_scp_m, range_scp_m = numpy.meshgrid( col_ss * (dc_zd_times - zd_time_scp) / ss_zd_s, (diff_time_rg + zd_ref_time - rg_time_scp) * speed_of_light / 2) # fit the doppler centroid sample array x_order = min(3, range_scp_m.shape[0] - 1) y_order = min(3, range_scp_m.shape[1] - 1) t_dop_centroid_poly, residuals, rank, sing_values = two_dim_poly_fit( range_scp_m, azimuth_scp_m, dc_sample_array, x_order=x_order, y_order=y_order, x_scale=1e-3, y_scale=1e-3, rcond=1e-40) logging.info( 'The dop_centroid_poly fit details:\nroot mean square ' 'residuals = {}\nrank = {}\nsingular values = {}'.format( residuals, rank, sing_values)) # define and fit the time coa array doppler_rate_sampled = polynomial.polyval(azimuth_scp_m, drate_ca_poly) time_coa = dc_zd_times + dc_sample_array / doppler_rate_sampled t_time_coa_poly, residuals, rank, sing_values = two_dim_poly_fit( range_scp_m, azimuth_scp_m, time_coa, x_order=x_order, y_order=y_order, x_scale=1e-3, y_scale=1e-3, rcond=1e-40) logging.info( 'The time_coa_poly fit details:\nroot mean square ' 'residuals = {}\nrank = {}\nsingular values = {}'.format( residuals, rank, sing_values)) return t_dop_centroid_poly, t_time_coa_poly def get_rma(): # type: () -> RMAType dop_centroid_poly = Poly2DType(Coefs=dop_centroid_poly_coeffs) dop_centroid_coa = True if collect_info.RadarMode.ModeType == 'SPOTLIGHT': dop_centroid_poly = None dop_centroid_coa = None # NB: DRateSFPoly is defined as a function of only range - reshape appropriately inca = INCAType( R_CA_SCP=r_ca_scp, FreqZero=center_freq, DRateSFPoly=Poly2DType( Coefs=numpy.reshape(drate_sf_poly_coefs, (-1, 1))), DopCentroidPoly=dop_centroid_poly, DopCentroidCOA=dop_centroid_coa, TimeCAPoly=Poly1DType(Coefs=time_ca_poly_coeffs)) return RMAType(RMAlgoType='OMEGA_K', INCA=inca) def get_grid(): # type: () -> GridType time_coa_poly = Poly2DType(Coefs=time_coa_poly_coeffs) if collect_info.RadarMode.ModeType == 'SPOTLIGHT': time_coa_poly = Poly2DType(Coefs=[ [ float(time_coa_poly_coeffs[0, 0]), ], ]) row_win = _stringify(hf['window_function_range'][()]) if row_win == 'NONE': row_win = 'UNIFORM' row = DirParamType(SS=row_ss, Sgn=-1, KCtr=2 * center_freq / speed_of_light, ImpRespBW=2 * tx_bandwidth / speed_of_light, DeltaKCOAPoly=Poly2DType(Coefs=[[ 0, ]]), WgtType=WgtTypeType(WindowName=row_win)) col_win = _stringify(hf['window_function_azimuth'][()]) if col_win == 'NONE': col_win = 'UNIFORM' col = DirParamType( SS=col_ss, Sgn=-1, KCtr=0, ImpRespBW=col_imp_res_bw, WgtType=WgtTypeType(WindowName=col_win), DeltaKCOAPoly=Poly2DType(Coefs=dop_centroid_poly_coeffs * ss_zd_s / col_ss)) return GridType(Type='RGZERO', ImagePlane='SLANT', TimeCOAPoly=time_coa_poly, Row=row, Col=col) def correct_scp(): scp_pixel = sicd.ImageData.SCPPixel.get_array() scp_ecf = sicd.project_image_to_ground(scp_pixel, projection_type='HAE') sicd.update_scp(scp_ecf, coord_system='ECF') with h5py.File(self._file_name, 'r') as hf: # some common use variables look_side = _stringify(hf['look_side'][()]) coord_center = hf['coord_center'][:] avg_scene_height = float(hf['avg_scene_height'][()]) start_time = _parse_time(hf['acquisition_start_utc'][()]) end_time = _parse_time(hf['acquisition_end_utc'][()]) duration = get_seconds(end_time, start_time, precision='us') center_freq = float(hf['carrier_frequency'][()]) tx_bandwidth = float(hf['chirp_bandwidth'][()]) min_freq = center_freq - 0.5 * tx_bandwidth max_freq = center_freq + 0.5 * tx_bandwidth pol_temp = _stringify(hf['polarization'][()]) tx_pol = pol_temp[0] rcv_pol = pol_temp[1] polarization = tx_pol + ':' + rcv_pol first_pixel_time = float(hf['first_pixel_time'][()]) near_range = first_pixel_time * speed_of_light / 2 number_of_range_samples = float(hf['number_of_range_samples'][()]) number_of_azimuth_samples = float( hf['number_of_azimuth_samples'][()]) range_sampling_rate = float(hf['range_sampling_rate'][()]) row_ss = speed_of_light / (2 * range_sampling_rate) # define the sicd elements collect_info = get_collection_info() image_creation = get_image_creation() image_data = get_image_data() geo_data = get_geo_data() timeline = get_timeline() position = get_position() radar_collection = get_radar_collection() image_formation = get_image_formation() radiometric = get_radiometric() # calculate some zero doppler parameters ss_zd_s = float(hf['azimuth_time_interval'][()]) if look_side == 'left': ss_zd_s *= -1 zero_doppler_left = _parse_time(hf['zerodoppler_end_utc'][()]) else: zero_doppler_left = _parse_time( hf['zerodoppler_start_utc'][()]) dop_bw = hf['total_processed_bandwidth_azimuth'][()] zd_time_scp = get_seconds(zero_doppler_left, start_time, precision='us') + \ image_data.SCPPixel.Col*ss_zd_s zd_ref_time = first_pixel_time + number_of_range_samples / ( 2 * range_sampling_rate) vel_scp = position.ARPPoly.derivative_eval(zd_time_scp, der_order=1) vm_ca_sq = numpy.sum(vel_scp * vel_scp) rg_time_scp = first_pixel_time + image_data.SCPPixel.Row / range_sampling_rate r_ca_scp = rg_time_scp * speed_of_light / 2 # calculate the doppler rate sf polynomial drate_ca_poly, drate_sf_poly_coefs = calculate_drate_sf_poly() # calculate some doppler dependent grid parameters col_ss = float( numpy.sqrt(vm_ca_sq) * abs(ss_zd_s) * drate_sf_poly_coefs[0]) col_imp_res_bw = dop_bw * abs(ss_zd_s) / col_ss time_ca_poly_coeffs = [zd_time_scp, ss_zd_s / col_ss] # calculate the doppler polynomials dop_centroid_poly_coeffs, time_coa_poly_coeffs = calculate_doppler_polys( ) # finish definition of sicd elements rma = get_rma() grid = get_grid() sicd = SICDType(CollectionInfo=collect_info, ImageCreation=image_creation, ImageData=image_data, GeoData=geo_data, Timeline=timeline, Position=position, RadarCollection=radar_collection, ImageFormation=image_formation, Radiometric=radiometric, RMA=rma, Grid=grid) # adjust the scp location correct_scp() # derive sicd fields sicd.derive() # TODO: RNIIRS? data_size = (image_data.NumCols, image_data.NumRows) symmetry = (True, False, True) if look_side == 'left' else (False, False, True) return sicd, data_size, symmetry
def _get_base_sicd(self, h5_dict, band_dict): # type: (dict, dict) -> SICDType def get_collection_info(): # type: () -> (dict, CollectionInfoType) acq_mode = h5_dict['Acquisition Mode'].upper() if self.mission_id == 'CSK': if acq_mode in ['HIMAGE', 'PINGPONG']: mode_type = 'STRIPMAP' elif acq_mode in ['WIDEREGION', 'HUGEREGION']: # scansar, processed as stripmap mode_type = 'STRIPMAP' elif acq_mode in ['ENHANCED SPOTLIGHT', 'SMART']: mode_type = 'DYNAMIC STRIPMAP' else: logging.warning( 'Got unexpected acquisition mode {}'.format(acq_mode)) mode_type = 'DYNAMIC STRIPMAP' elif self.mission_id == 'KMPS': if acq_mode in ['STANDARD', 'ENHANCED STANDARD']: mode_type = 'STRIPMAP' elif acq_mode in ['WIDE SWATH', 'ENHANCED WIDE SWATH']: # scansar, processed as stripmap mode_type = 'STRIPMAP' elif acq_mode in [ 'HIGH RESOLUTION', 'ENHANCED HIGH RESOLUTION', 'ULTRA HIGH RESOLUTION' ]: # "spotlight" mode_type = 'DYNAMIC STRIPMAP' else: logging.warning( 'Got unexpected acquisition mode {}'.format(acq_mode)) mode_type = 'DYNAMIC STRIPMAP' elif self.mission_id == 'CSG': if acq_mode.startswith('SPOTLIGHT'): mode_type = 'DYNAMIC STRIPMAP' elif acq_mode in ['STRIPMAP', 'QUADPOL']: mode_type = "STRIPMAP" else: logging.warning( 'Got unhandled acquisition mode {}, setting to DYNAMIC STRIPMAP' .format(acq_mode)) mode_type = 'DYNAMIC STRIPMAP' else: raise ValueError('Unhandled mission id {}'.format( self._mission_id)) start_time_dt = collect_start.astype('datetime64[s]').astype( datetime) date_str = start_time_dt.strftime('%d%b%y').upper() time_str = start_time_dt.strftime('%H%M%S') + 'Z' core_name = '{}_{}_{}'.format(date_str, self.mission_id, time_str) collect_info = CollectionInfoType( Classification='UNCLASSIFIED', CollectorName=h5_dict['Satellite ID'], CoreName=core_name, CollectType='MONOSTATIC', RadarMode=RadarModeType(ModeID=h5_dict['Multi-Beam ID'], ModeType=mode_type)) return collect_info def get_image_creation(): # type: () -> ImageCreationType from sarpy.__about__ import __version__ return ImageCreationType( DateTime=parse_timestring(h5_dict['Product Generation UTC'], precision='ns'), Site=h5_dict['Processing Centre'], Application='L0: `{}`, L1: `{}`'.format( h5_dict.get('L0 Software Version', 'NONE'), h5_dict.get('L1A Software Version', 'NONE')), Profile='sarpy {}'.format(__version__)) def get_grid(): # type: () -> GridType def get_wgt_type(weight_name, coefficient, direction): if weight_name == 'GENERAL_COSINE': # probably only for kompsat? weight_name = 'HAMMING' coefficient = 1 - coefficient if coefficient is None: params = None else: params = {'COEFFICIENT': '{0:0.16G}'.format(coefficient)} out = WgtTypeType(WindowName=weight_name, Parameters=params) if weight_name != 'HAMMING': logging.warning( 'Got unexpected weight scheme {} for {}. The weighting will ' 'not be properly populated.'.format( weight_name, direction)) return out if h5_dict['Projection ID'] == 'SLANT RANGE/AZIMUTH': image_plane = 'SLANT' gr_type = 'RGZERO' else: image_plane = 'GROUND' gr_type = None # Row row_window_name = h5_dict[ 'Range Focusing Weighting Function'].rstrip().upper() row_coefficient = h5_dict.get( 'Range Focusing Weighting Coefficient', None) row_weight = get_wgt_type(row_window_name, row_coefficient, 'Row') row = DirParamType(Sgn=-1, KCtr=2 * center_frequency / speed_of_light, DeltaKCOAPoly=Poly2DType(Coefs=[ [ 0, ], ]), WgtType=row_weight) # Col col_window_name = h5_dict[ 'Azimuth Focusing Weighting Function'].rstrip().upper() col_coefficient = h5_dict.get( 'Azimuth Focusing Weighting Coefficient', None) col_weight = get_wgt_type(col_window_name, col_coefficient, 'Col') col = DirParamType(Sgn=-1, KCtr=0, WgtType=col_weight) return GridType(ImagePlane=image_plane, Type=gr_type, Row=row, Col=col) def get_timeline(): # type: () -> TimelineType # NB: IPPEnd must be set, but will be replaced return TimelineType(CollectStart=collect_start, CollectDuration=duration, IPP=[ IPPSetType(index=0, TStart=0, TEnd=0, IPPStart=0, IPPEnd=0), ]) def get_position(): # type: () -> PositionType T = h5_dict[ 'State Vectors Times'] # in seconds relative to ref time T += ref_time_offset Pos = h5_dict['ECEF Satellite Position'] Vel = h5_dict['ECEF Satellite Velocity'] P_x, P_y, P_z = fit_position_xvalidation(T, Pos, Vel, max_degree=8) return PositionType(ARPPoly=XYZPolyType(X=P_x, Y=P_y, Z=P_z)) def get_radar_collection(): # type: () -> RadarCollectionType tx_pols = [] chan_params = [] for i, bdname in enumerate(band_dict): if 'Polarisation' in band_dict[bdname]: pol = band_dict[bdname]['Polarisation'] elif 'Polarization' in h5_dict: pol = h5_dict['Polarization'] else: raise ValueError( 'Failed finding polarization for file {}\nmission id {}' .format(self._file_name, self._mission_id)) tx_pols.append(pol[0]) chan_params.append( ChanParametersType(TxRcvPolarization=self._parse_pol(pol), index=i)) if len(tx_pols) == 1: return RadarCollectionType(RcvChannels=chan_params, TxPolarization=tx_pols[0]) else: return RadarCollectionType(RcvChannels=chan_params, TxPolarization='SEQUENCE', TxSequence=[ TxStepType(TxPolarization=pol, index=i + 1) for i, pol in enumerate(tx_pols) ]) def get_image_formation(): # type: () -> ImageFormationType return ImageFormationType(ImageFormAlgo='RMA', TStartProc=0, TEndProc=duration, STBeamComp='NO', ImageBeamComp='SV', AzAutofocus='NO', RgAutofocus='NO', RcvChanProc=RcvChanProcType( NumChanProc=1, PRFScaleFactor=1)) def get_rma(): # type: () -> RMAType inca = INCAType(FreqZero=center_frequency) return RMAType(RMAlgoType='OMEGA_K', INCA=inca) def get_scpcoa(): # type: () -> SCPCOAType return SCPCOAType(SideOfTrack=h5_dict['Look Side'][0:1].upper()) # some common use parameters center_frequency = h5_dict['Radar Frequency'] # relative times in csk are wrt some reference time - for sicd they should be relative to start time collect_start = parse_timestring(h5_dict['Scene Sensing Start UTC'], precision='ns') collect_end = parse_timestring(h5_dict['Scene Sensing Stop UTC'], precision='ns') duration = get_seconds(collect_end, collect_start, precision='ns') ref_time = parse_timestring(h5_dict['Reference UTC'], precision='ns') ref_time_offset = get_seconds(ref_time, collect_start, precision='ns') # assemble our pieces collection_info = get_collection_info() image_creation = get_image_creation() grid = get_grid() timeline = get_timeline() position = get_position() radar_collection = get_radar_collection() image_formation = get_image_formation() rma = get_rma() scpcoa = get_scpcoa() sicd = SICDType(CollectionInfo=collection_info, ImageCreation=image_creation, Grid=grid, Timeline=timeline, Position=position, RadarCollection=radar_collection, ImageFormation=image_formation, RMA=rma, SCPCOA=scpcoa) return sicd
def _get_base_sicd(self, hf): """ Defines the base SICD object, to be refined with further details. Returns ------- SICDType """ def get_collection_info(): # type: () -> CollectionInfoType gp = hf['/science/LSAR/identification'] return CollectionInfoType( CollectorName=_stringify(hf.attrs['mission_name']), CoreName='{0:07d}_{1:s}'.format(gp['absoluteOrbitNumber'][()], _stringify(gp['trackNumber'][()])), CollectType='MONOSTATIC', Classification='UNCLASSIFIED', RadarMode=RadarModeType(ModeType='STRIPMAP')) def get_image_creation(): # type: () -> ImageCreationType application = 'ISCE' # noinspection PyBroadException try: application = '{} {}'.format( application, _stringify(hf['/science/LSAR/SLC/metadata/processingInformation/algorithms/ISCEVersion'][()])) except: pass from sarpy.__about__ import __version__ # TODO: DateTime? return ImageCreationType( Application=application, Site='Unknown', Profile='sarpy {}'.format(__version__)) def get_geo_data(): # type: () -> GeoDataType # seeds a rough SCP for projection usage poly_str = _stringify(hf['/science/LSAR/identification/boundingPolygon'][()]) beg_str = 'POLYGON ((' if not poly_str.startswith(beg_str): raise ValueError('Unexpected polygon string {}'.format(poly_str)) parts = poly_str[len(beg_str):-2].strip().split(',') if len(parts) != 5: raise ValueError('Unexpected polygon string parts {}'.format(parts)) lats_lons = numpy.zeros((4, 2), dtype=numpy.float64) for i, part in enumerate(parts[:-1]): spart = part.strip().split() if len(spart) != 2: raise ValueError('Unexpected polygon string parts {}'.format(parts)) lats_lons[i, :] = float(spart[1]), float(spart[0]) llh = numpy.zeros((3, ), dtype=numpy.float64) llh[0:2] = numpy.mean(lats_lons, axis=0) llh[2] = numpy.mean(hf['/science/LSAR/SLC/metadata/processingInformation/parameters/referenceTerrainHeight'][:]) return GeoDataType(SCP=SCPType(LLH=llh)) def get_grid(): # type: () -> GridType # TODO: Future Change Required - JPL states that uniform weighting in data simulated # from UAVSAR is a placeholder, not an accurate description of the data. # At this point, it is not clear what the final weighting description for NISAR # will be. gp = hf['/science/LSAR/SLC/metadata/processingInformation/parameters'] row_wgt = gp['rangeChirpWeighting'][:] win_name = 'UNIFORM' if numpy.all(row_wgt == row_wgt[0]) else 'UNKNOWN' row = DirParamType( Sgn=-1, DeltaKCOAPoly=[[0,]], WgtFunct=numpy.cast[numpy.float64](row_wgt), WgtType=WgtTypeType(WindowName=win_name)) col_wgt = gp['azimuthChirpWeighting'][:] win_name = 'UNIFORM' if numpy.all(col_wgt == col_wgt[0]) else 'UNKNOWN' col = DirParamType( Sgn=-1, KCtr=0, WgtFunct=numpy.cast[numpy.float64](col_wgt), WgtType=WgtTypeType(WindowName=win_name)) return GridType(ImagePlane='SLANT', Type='RGZERO', Row=row, Col=col) def get_timeline(): # type: () -> TimelineType # NB: IPPEnd must be set, but will be replaced return TimelineType( CollectStart=collect_start, CollectDuration=duration, IPP=[IPPSetType(index=0, TStart=0, TEnd=duration, IPPStart=0, IPPEnd=0), ]) def get_position(): # type: () -> PositionType gp = hf['/science/LSAR/SLC/metadata/orbit'] ref_time = _get_ref_time(gp['time'].attrs['units']) T = gp['time'][:] + get_seconds(ref_time, collect_start, precision='ns') Pos = gp['position'][:] Vel = gp['velocity'][:] P_x, P_y, P_z = fit_position_xvalidation(T, Pos, Vel, max_degree=8) return PositionType(ARPPoly=XYZPolyType(X=P_x, Y=P_y, Z=P_z)) def get_scpcoa(): # type: () -> SCPCOAType # remaining fields set later sot = _stringify(hf['/science/LSAR/identification/lookDirection'][()])[0].upper() return SCPCOAType(SideOfTrack=sot) def get_image_formation(): # type: () -> ImageFormationType return ImageFormationType( ImageFormAlgo='RMA', TStartProc=0, TEndProc=duration, STBeamComp='NO', ImageBeamComp='SV', AzAutofocus='NO', RgAutofocus='NO', RcvChanProc=RcvChanProcType(NumChanProc=1, PRFScaleFactor=1)) def get_rma(): # type: () -> RMAType return RMAType(RMAlgoType='OMEGA_K', INCA=INCAType(DopCentroidCOA=True)) collect_start, collect_end, duration = self._get_collection_times(hf) collection_info = get_collection_info() image_creation = get_image_creation() geo_data = get_geo_data() grid = get_grid() timeline = get_timeline() position = get_position() scpcoa = get_scpcoa() image_formation = get_image_formation() rma = get_rma() return SICDType( CollectionInfo=collection_info, ImageCreation=image_creation, GeoData=geo_data, Grid=grid, Timeline=timeline, Position=position, SCPCOA=scpcoa, ImageFormation=image_formation, RMA=rma)
def _get_freq_specific_sicd( gp: h5pyGroup, base_sicd: SICDType) -> Tuple[SICDType, List[str], List[str], float]: """ Gets the frequency specific sicd. Parameters ---------- gp : h5py.Group base_sicd : SICDType Returns ------- sicd: SICDType pol_names : numpy.ndarray pols : List[str] center_frequency : float """ def update_grid() -> None: row_imp_resp_bw = 2*gp['processedRangeBandwidth'][()]/speed_of_light t_sicd.Grid.Row.SS = gp['slantRangeSpacing'][()] t_sicd.Grid.Row.ImpRespBW = row_imp_resp_bw t_sicd.Grid.Row.DeltaK1 = -0.5*row_imp_resp_bw t_sicd.Grid.Row.DeltaK2 = -t_sicd.Grid.Row.DeltaK1 def update_timeline() -> None: prf = gp['nominalAcquisitionPRF'][()] t_sicd.Timeline.IPP[0].IPPEnd = prf*t_sicd.Timeline.CollectDuration t_sicd.Timeline.IPP[0].IPPPoly = [0, prf] def define_radar_collection() -> List[str]: tx_rcv_pol_t = [] tx_pol = [] for entry in pols: tx_rcv_pol_t.append('{}:{}'.format(entry[0], entry[1])) if entry[0] not in tx_pol: tx_pol.append(entry[0]) center_freq_t = gp['acquiredCenterFrequency'][()] bw = gp['acquiredRangeBandwidth'][()] tx_freq = (center_freq_t - 0.5*bw, center_freq_t + 0.5*bw) rcv_chans = [ChanParametersType(TxRcvPolarization=pol) for pol in tx_rcv_pol_t] if len(tx_pol) == 1: tx_sequence = None tx_pol = tx_pol[0] else: tx_sequence = [TxStepType(WFIndex=j+1, TxPolarization=pol) for j, pol in enumerate(tx_pol)] tx_pol = 'SEQUENCE' t_sicd.RadarCollection = RadarCollectionType( TxFrequency=tx_freq, RcvChannels=rcv_chans, TxPolarization=tx_pol, TxSequence=tx_sequence) return tx_rcv_pol_t def update_image_formation() -> float: center_freq_t = gp['processedCenterFrequency'][()] bw = gp['processedRangeBandwidth'][()] t_sicd.ImageFormation.TxFrequencyProc = (center_freq_t - 0.5*bw, center_freq_t + 0.5*bw) return center_freq_t pols = _get_string_list(gp['listOfPolarizations'][:]) t_sicd = base_sicd.copy() update_grid() update_timeline() tx_rcv_pol = define_radar_collection() center_freq = update_image_formation() return t_sicd, pols, tx_rcv_pol, center_freq
def _get_pol_specific_sicd( hf: h5pyFile, ds: h5pyDataset, base_sicd: SICDType, pol_name: str, freq_name: str, j: int, pol: str, r_ca_sampled: numpy.ndarray, zd_time: numpy.ndarray, grid_zd_time: numpy.ndarray, grid_r: numpy.ndarray, doprate_sampled: numpy.ndarray, dopcentroid_sampled: numpy.ndarray, center_freq: float, ss_az_s: float, dop_bw: float, beta0, gamma0, sigma0) -> Tuple[SICDType, Tuple[int, ...], numpy.dtype]: """ Gets the frequency/polarization specific sicd. Parameters ---------- hf : h5py.File ds : h5py.Dataset base_sicd : SICDType pol_name : str freq_name : str j : int pol : str r_ca_sampled : numpy.ndarray zd_time : numpy.ndarray grid_zd_time : numpy.ndarray grid_r : numpy.ndarray doprate_sampled : numpy.ndarray dopcentroid_sampled : numpy.ndarray center_freq : float ss_az_s : float dop_bw : float Returns ------- sicd: SICDType shape : Tuple[int, ...] numpy.dtype """ def define_image_data() -> None: if dtype.name in ('float32', 'complex64'): pixel_type = 'RE32F_IM32F' elif dtype.name == 'int16': pixel_type = 'RE16I_IM16I' else: raise ValueError('Got unhandled dtype {}'.format(dtype)) t_sicd.ImageData = ImageDataType( PixelType=pixel_type, NumRows=shape[1], NumCols=shape[0], FirstRow=0, FirstCol=0, SCPPixel=[0.5*shape[0], 0.5*shape[1]], FullImage=[shape[1], shape[0]]) def update_image_formation() -> None: t_sicd.ImageFormation.RcvChanProc.ChanIndices = [j, ] t_sicd.ImageFormation.TxRcvPolarizationProc = pol def update_inca_and_grid() -> Tuple[numpy.ndarray, numpy.ndarray]: t_sicd.RMA.INCA.R_CA_SCP = r_ca_sampled[t_sicd.ImageData.SCPPixel.Row] scp_ca_time = zd_time[t_sicd.ImageData.SCPPixel.Col] # compute DRateSFPoly # velocity at scp ca time vel_ca = t_sicd.Position.ARPPoly.derivative_eval(scp_ca_time, der_order=1) # squared magnitude vm_ca_sq = numpy.sum(vel_ca*vel_ca) # polynomial coefficient for function representing range as a function of range distance from SCP r_ca_poly = numpy.array([t_sicd.RMA.INCA.R_CA_SCP, 1], dtype=numpy.float64) # closest Doppler rate polynomial to SCP min_ind = numpy.argmin(numpy.absolute(grid_zd_time - scp_ca_time)) # define range coordinate grid coords_rg_m = grid_r - t_sicd.RMA.INCA.R_CA_SCP # determine dop_rate_poly coordinates dop_rate_poly = polynomial.polyfit(coords_rg_m, -doprate_sampled[min_ind, :], 4) # why fourth order? t_sicd.RMA.INCA.FreqZero = center_freq t_sicd.RMA.INCA.DRateSFPoly = Poly2DType(Coefs=numpy.reshape( -numpy.convolve(dop_rate_poly, r_ca_poly)*speed_of_light/(2*center_freq*vm_ca_sq), (-1, 1))) # update Grid.Col parameters t_sicd.Grid.Col.SS = numpy.sqrt(vm_ca_sq)*abs(ss_az_s)*t_sicd.RMA.INCA.DRateSFPoly.Coefs[0, 0] t_sicd.Grid.Col.ImpRespBW = min(abs(dop_bw*ss_az_s), 1)/t_sicd.Grid.Col.SS t_sicd.RMA.INCA.TimeCAPoly = [scp_ca_time, ss_az_s/t_sicd.Grid.Col.SS] #TimeCOAPoly/DopCentroidPoly/DeltaKCOAPoly coords_az_m = (grid_zd_time - scp_ca_time)*t_sicd.Grid.Col.SS/ss_az_s # cerate the 2d grids coords_rg_2d_t, coords_az_2d_t = numpy.meshgrid(coords_rg_m, coords_az_m, indexing='xy') coefs, residuals, rank, sing_values = two_dim_poly_fit( coords_rg_2d_t, coords_az_2d_t, dopcentroid_sampled, x_order=3, y_order=3, x_scale=1e-3, y_scale=1e-3, rcond=1e-40) logger.info( 'The dop_centroid_poly fit details:\n\t' 'root mean square residuals = {}\n\t' 'rank = {}\n\t' 'singular values = {}'.format(residuals, rank, sing_values)) t_sicd.RMA.INCA.DopCentroidPoly = Poly2DType(Coefs=coefs) t_sicd.Grid.Col.DeltaKCOAPoly = Poly2DType(Coefs=coefs*ss_az_s/t_sicd.Grid.Col.SS) timeca_sampled = numpy.outer(grid_zd_time, numpy.ones((grid_r.size, ))) time_coa_sampled = timeca_sampled + (dopcentroid_sampled/doprate_sampled) coefs, residuals, rank, sing_values = two_dim_poly_fit( coords_rg_2d_t, coords_az_2d_t, time_coa_sampled, x_order=3, y_order=3, x_scale=1e-3, y_scale=1e-3, rcond=1e-40) logger.info( 'The time_coa_poly fit details:\n\t' 'root mean square residuals = {}\n\t' 'rank = {}\n\t' 'singular values = {}'.format(residuals, rank, sing_values)) t_sicd.Grid.TimeCOAPoly = Poly2DType(Coefs=coefs) return coords_rg_2d_t, coords_az_2d_t def define_radiometric() -> None: def get_poly(ds: h5pyDataset, name: str) -> Optional[Poly2DType]: array = ds[:] fill = ds.attrs['_FillValue'] boolc = (array != fill) if numpy.any(boolc): array = array[boolc] if numpy.any(array != array[0]): coefs, residuals, rank, sing_values = two_dim_poly_fit( coords_rg_2d[boolc], coords_az_2d[boolc], array, x_order=3, y_order=3, x_scale=1e-3, y_scale=1e-3, rcond=1e-40) logger.info( 'The {} fit details:\n\t' 'root mean square residuals = {}\n\t' 'rank = {}\n\t' 'singular values = {}'.format(name, residuals, rank, sing_values)) else: # it's constant, so just use a constant polynomial coefs = [[array[0], ], ] logger.info('The {} values are constant'.format(name)) return Poly2DType(Coefs=coefs) else: logger.warning('No non-trivial values for {} provided.'.format(name)) return None beta0_poly = get_poly(beta0, 'beta0') gamma0_poly = get_poly(gamma0, 'gamma0') sigma0_poly = get_poly(sigma0, 'sigma0') nesz = hf['/science/LSAR/SLC/metadata/calibrationInformation/frequency{}/{}/nes0'.format(freq_name, pol_name)][:] noise_samples = nesz - (10 * numpy.log10(sigma0_poly.Coefs[0, 0])) coefs, residuals, rank, sing_values = two_dim_poly_fit( coords_rg_2d, coords_az_2d, noise_samples, x_order=3, y_order=3, x_scale=1e-3, y_scale=1e-3, rcond=1e-40) logger.info( 'The noise_poly fit details:\n\t' 'root mean square residuals = {}\n\t' 'rank = {}\n\t' 'singular values = {}'.format( residuals, rank, sing_values)) t_sicd.Radiometric = RadiometricType( BetaZeroSFPoly=beta0_poly, GammaZeroSFPoly=gamma0_poly, SigmaZeroSFPoly=sigma0_poly, NoiseLevel=NoiseLevelType_( NoiseLevelType='ABSOLUTE', NoisePoly=Poly2DType(Coefs=coefs))) def update_geodata() -> None: ecf = point_projection.image_to_ground( [t_sicd.ImageData.SCPPixel.Row, t_sicd.ImageData.SCPPixel.Col], t_sicd) t_sicd.GeoData.SCP = SCPType(ECF=ecf) # LLH will be populated t_sicd = base_sicd.copy() shape = ds.shape dtype = ds.dtype define_image_data() update_image_formation() coords_rg_2d, coords_az_2d = update_inca_and_grid() define_radiometric() update_geodata() t_sicd.derive() t_sicd.populate_rniirs(override=False) return t_sicd, shape, dtype
def __init__(self, file_object: Union[str, BinaryIO], sicd_meta: SICDType, user_data: Optional[Dict[str, str]] = None, check_older_version: bool = False, check_existence: bool = True): """ Parameters ---------- file_object : str|BinaryIO sicd_meta : SICDType user_data : None|Dict[str, str] check_older_version : bool Try to use an older version (1.1) of the SICD standard, for possible application compliance issues? check_existence : bool Should we check if the given file already exists, and raises an exception if so? """ self._data_written = True if isinstance(file_object, str): if check_existence and os.path.exists(file_object): raise SarpyIOError( 'Given file {} already exists, and a new SIO file cannot be created here.' .format(file_object)) file_object = open(file_object, 'wb') if not is_file_like(file_object): raise ValueError( 'file_object requires a file path or BinaryIO object') self._file_object = file_object if is_real_file(file_object): self._file_name = file_object.name self._in_memory = False else: self._file_name = None self._in_memory = True # choose magic number (with user data) and corresponding endian-ness magic_number = 0xFD7F02FF endian = SIODetails.ENDIAN[magic_number] # define basic image details raw_shape = (sicd_meta.ImageData.NumRows, sicd_meta.ImageData.NumCols, 2) pixel_type = sicd_meta.ImageData.PixelType if pixel_type == 'RE32F_IM32F': raw_dtype = numpy.dtype('{}f4'.format(endian)) element_type = 13 element_size = 8 format_function = ComplexFormatFunction(raw_dtype, order='MP', band_dimension=2) elif pixel_type == 'RE16I_IM16I': raw_dtype = numpy.dtype('{}i2'.format(endian)) element_type = 12 element_size = 4 format_function = ComplexFormatFunction(raw_dtype, order='MP', band_dimension=2) else: raw_dtype = numpy.dtype('{}u1'.format(endian)) element_type = 11 element_size = 2 format_function = ComplexFormatFunction( raw_dtype, order='MP', band_dimension=2, magnitude_lookup_table=sicd_meta.ImageData.AmpTable) # construct the sio header header = numpy.array([ magic_number, raw_shape[0], raw_shape[1], element_type, element_size ], dtype='>u4') # construct the user data - must be {str : str} if user_data is None: user_data = {} uh_args = sicd_meta.get_des_details(check_older_version) user_data['SICDMETA'] = sicd_meta.to_xml_string(tag='SICD', urn=uh_args['DESSHTN']) # write the initial things to the buffer self._file_object.seek(0, os.SEEK_SET) self._file_object.write(struct.pack('{}5I'.format(endian), *header)) # write the user data - name size, name, value size, value for name in user_data: name_bytes = name.encode('utf-8') self._file_object.write( struct.pack('{}I'.format(endian), len(name_bytes))) self._file_object.write( struct.pack('{}{}s'.format(endian, len(name_bytes)), name_bytes)) val_bytes = user_data[name].encode('utf-8') self._file_object.write( struct.pack('{}I'.format(endian), len(val_bytes))) self._file_object.write( struct.pack('{}{}s'.format(endian, len(val_bytes)), val_bytes)) self._data_offset = self._file_object.tell() # initialize the single data segment if self._in_memory: underlying_array = numpy.full(raw_shape, fill_value=0, dtype=raw_dtype) data_segment = NumpyArraySegment(underlying_array, 'complex64', raw_shape[:2], format_function=format_function, mode='w') self._data_written = False else: data_segment = NumpyMemmapSegment(self._file_object, self._data_offset, raw_dtype, raw_shape, 'complex64', raw_shape[:2], format_function=format_function, mode='w', close_file=False) self._data_written = True BaseWriter.__init__(self, data_segment)
def get_sicd(self): """ Get the SICD metadata for the image. Returns ------- SICDType """ def convert_string_dict(dict_in): # type: (dict) -> dict dict_out = OrderedDict() for key, val in dict_in.items(): if isinstance(val, string_types): dict_out[key] = val elif isinstance(val, int): dict_out[key] = str(val) elif isinstance(val, float): dict_out[key] = '{0:0.16G}'.format(val) else: raise TypeError('Got unhandled type {}'.format(type(val))) return dict_out def extract_state_vector(): # type: () -> (numpy.ndarray, numpy.ndarray, numpy.ndarray) vecs = collect['state']['state_vectors'] times = numpy.zeros((len(vecs), ), dtype=numpy.float64) positions = numpy.zeros((len(vecs), 3), dtype=numpy.float64) velocities = numpy.zeros((len(vecs), 3), dtype=numpy.float64) for i, entry in enumerate(vecs): times[i] = get_seconds(parse_timestring(entry['time'], precision='ns'), start_time, precision='ns') positions[i, :] = entry['position'] velocities[i, :] = entry['velocity'] return times, positions, velocities def get_collection_info(): # type: () -> CollectionInfoType coll_name = collect['platform'] start_dt = start_time.astype('datetime64[us]').astype(datetime) mode = collect['mode'].strip().lower() if mode == 'stripmap': radar_mode = RadarModeType(ModeType='STRIPMAP') elif mode == 'sliding_spotlight': radar_mode = RadarModeType(ModeType='DYNAMIC STRIPMAP') else: raise ValueError('Got unhandled radar mode {}'.format(mode)) return CollectionInfoType(CollectorName=coll_name, CoreName='{}{}{}'.format( start_dt.strftime('%d%b%y').upper(), coll_name, start_dt.strftime('%H%M%S')), RadarMode=radar_mode, Classification='UNCLASSIFIED', CollectType='MONOSTATIC') def get_image_creation(): # type: () -> ImageCreationType from sarpy.__about__ import __version__ return ImageCreationType( Application=self._tiff_details.tags['Software'], DateTime=parse_timestring( self._img_desc_tags['processing_time'], precision='us'), Profile='sarpy {}'.format(__version__), Site='Unknown') def get_image_data(): # type: () -> ImageDataType img = collect['image'] rows = int( img['columns']) # capella uses flipped row/column definition? cols = int(img['rows']) if img['data_type'] == 'CInt16': pixel_type = 'RE16I_IM16I' else: raise ValueError('Got unhandled data_type {}'.format( img['data_type'])) scp_pixel = (int(0.5 * rows), int(0.5 * cols)) if collect['radar']['pointing'] == 'left': scp_pixel = (rows - scp_pixel[0] - 1, cols - scp_pixel[1] - 1) return ImageDataType(NumRows=rows, NumCols=cols, FirstRow=0, FirstCol=0, PixelType=pixel_type, FullImage=(rows, cols), SCPPixel=scp_pixel) def get_geo_data(): # type: () -> GeoDataType return GeoDataType(SCP=SCPType( ECF=collect['image']['center_pixel']['target_position'])) def get_position(): # type: () -> PositionType px, py, pz = fit_position_xvalidation(state_time, state_position, state_velocity, max_degree=6) return PositionType(ARPPoly=XYZPolyType(X=px, Y=py, Z=pz)) def get_grid(): # type: () -> GridType img = collect['image'] image_plane = 'OTHER' grid_type = 'PLANE' if self._img_desc_tags['product_type'] == 'SLC' and img[ 'algorithm'] != 'backprojection': image_plane = 'SLANT' grid_type = 'RGZERO' coa_time = parse_timestring(img['center_pixel']['center_time'], precision='ns') row_imp_rsp_bw = 2 * bw / speed_of_light row = DirParamType(SS=img['pixel_spacing_column'], ImpRespBW=row_imp_rsp_bw, ImpRespWid=img['range_resolution'], KCtr=2 * fc / speed_of_light, DeltaK1=-0.5 * row_imp_rsp_bw, DeltaK2=0.5 * row_imp_rsp_bw, DeltaKCOAPoly=[ [ 0.0, ], ], WgtType=WgtTypeType( WindowName=img['range_window']['name'], Parameters=convert_string_dict( img['range_window']['parameters']))) # get timecoa value timecoa_value = get_seconds(coa_time, start_time) # TODO: constant? # find an approximation for zero doppler spacing - necessarily rough for backprojected images # find velocity at coatime arp_velocity = position.ARPPoly.derivative_eval(timecoa_value, der_order=1) arp_speed = numpy.linalg.norm(arp_velocity) col_ss = img['pixel_spacing_row'] dop_bw = img['processed_azimuth_bandwidth'] # ss_zd_s = col_ss/arp_speed col = DirParamType(SS=col_ss, ImpRespWid=img['azimuth_resolution'], ImpRespBW=dop_bw / arp_speed, KCtr=0, WgtType=WgtTypeType( WindowName=img['azimuth_window']['name'], Parameters=convert_string_dict( img['azimuth_window']['parameters']))) # TODO: from Wade - account for numeric WgtFunct return GridType(ImagePlane=image_plane, Type=grid_type, TimeCOAPoly=[ [ timecoa_value, ], ], Row=row, Col=col) def get_radar_colection(): # type: () -> RadarCollectionType radar = collect['radar'] freq_min = fc - 0.5 * bw return RadarCollectionType( TxPolarization=radar['transmit_polarization'], TxFrequency=TxFrequencyType(Min=freq_min, Max=freq_min + bw), Waveform=[ WaveformParametersType( TxRFBandwidth=bw, TxPulseLength=radar['pulse_duration'], RcvDemodType='CHIRP', ADCSampleRate=radar['sampling_frequency'], TxFreqStart=freq_min) ], RcvChannels=[ ChanParametersType(TxRcvPolarization='{}:{}'.format( radar['transmit_polarization'], radar['receive_polarization'])) ]) def get_timeline(): # type: () -> TimelineType prf = collect['radar']['prf'][0]['prf'] return TimelineType(CollectStart=start_time, CollectDuration=duration, IPP=[ IPPSetType(TStart=0, TEnd=duration, IPPStart=0, IPPEnd=duration * prf, IPPPoly=(0, prf)), ]) def get_image_formation(): # type: () -> ImageFormationType radar = collect['radar'] algo = collect['image']['algorithm'].upper() processings = None if algo == 'BACKPROJECTION': processings = [ ProcessingType(Type='Backprojected to DEM', Applied=True), ] if algo not in ('PFA', 'RMA', 'RGAZCOMP'): logging.warning( 'Image formation algorithm {} not one of the recognized SICD options, ' 'being set to "OTHER".'.format(algo)) algo = 'OTHER' return ImageFormationType( RcvChanProc=RcvChanProcType(NumChanProc=1, PRFScaleFactor=1), ImageFormAlgo=algo, TStartProc=0, TEndProc=duration, TxRcvPolarizationProc='{}:{}'.format( radar['transmit_polarization'], radar['receive_polarization']), TxFrequencyProc=TxFrequencyProcType( MinProc=radar_collection.TxFrequency.Min, MaxProc=radar_collection.TxFrequency.Max), STBeamComp='NO', ImageBeamComp='NO', AzAutofocus='NO', RgAutofocus='NO', Processings=processings) # TODO: From Wade - Radiometric is not suitable? # extract general use information collect = self._img_desc_tags['collect'] start_time = parse_timestring(collect['start_timestamp'], precision='ns') end_time = parse_timestring(collect['stop_timestamp'], precision='ns') duration = get_seconds(end_time, start_time, precision='ns') state_time, state_position, state_velocity = extract_state_vector() bw = collect['radar']['pulse_bandwidth'] fc = collect['radar']['center_frequency'] # define the sicd elements collection_info = get_collection_info() image_creation = get_image_creation() image_data = get_image_data() geo_data = get_geo_data() position = get_position() grid = get_grid() radar_collection = get_radar_colection() timeline = get_timeline() image_formation = get_image_formation() sicd = SICDType(CollectionInfo=collection_info, ImageCreation=image_creation, ImageData=image_data, GeoData=geo_data, Position=position, Grid=grid, RadarCollection=radar_collection, Timeline=timeline, ImageFormation=image_formation) sicd.derive() # this would be a rough estimate - waiting for radiometric data # sicd.populate_rniirs(override=False) return sicd
def _get_band_specific_sicds(self, base_sicd: SICDType, h5_dict: dict, band_dict: dict, shape_dict: dict, pixeltype_dict: dict) -> Dict[str, SICDType]: def update_scp_prelim(sicd: SICDType, band_name: str) -> None: if self._mission_id in ['CSK', 'KMPS']: LLH = band_dict[band_name]['Centre Geodetic Coordinates'] elif self._mission_id == 'CSG': LLH = h5_dict['Scene Centre Geodetic Coordinates'] else: raise ValueError(_unhandled_id_text.format(self._mission_id)) sicd.GeoData = GeoDataType( SCP=SCPType(LLH=LLH)) # EarthModel & ECF will be populated def update_image_data( sicd: SICDType, band_name: str) -> (float, float, float, float, int): cols, rows = shape_dict[band_name] # zero doppler time of first/last columns t_az_first_time = band_dict[band_name][ 'Zero Doppler Azimuth First Time'] t_az_last_time = band_dict[band_name][ 'Zero Doppler Azimuth Last Time'] t_ss_az_s = band_dict[band_name]['Line Time Interval'] t_use_sign2 = 1 if h5_dict['Look Side'].upper() == 'LEFT': t_use_sign2 = -1 t_az_first_time, t_az_last_time = t_az_last_time, t_az_first_time # zero doppler time of first row t_rg_first_time = band_dict[band_name][ 'Zero Doppler Range First Time'] # row spacing in range time (seconds) t_ss_rg_s = band_dict[band_name]['Column Time Interval'] sicd.ImageData = ImageDataType(NumRows=rows, NumCols=cols, FirstRow=0, FirstCol=0, FullImage=(rows, cols), PixelType=pixeltype_dict[band_name], SCPPixel=RowColType( Row=int(rows / 2), Col=int(cols / 2))) return t_rg_first_time, t_ss_rg_s, t_az_first_time, t_ss_az_s, t_use_sign2 def check_switch_state() -> (int, Poly1DType): use_sign = 1 if t_dop_rate_poly_rg[0] < 0 else -1 return use_sign, Poly1DType(Coefs=use_sign * t_dop_rate_poly_rg) def update_timeline(sicd: SICDType, band_name: str) -> None: prf = band_dict[band_name]['PRF'] duration = sicd.Timeline.CollectDuration ipp_el = sicd.Timeline.IPP[0] ipp_el.IPPEnd = duration * prf ipp_el.TEnd = duration ipp_el.IPPPoly = Poly1DType(Coefs=(0, prf)) def update_radar_collection(sicd: SICDType, band_name: str) -> None: ind = None for the_chan_index, chan in enumerate( sicd.RadarCollection.RcvChannels): if chan.TxRcvPolarization == polarization: ind = the_chan_index break if ind is None: raise ValueError( 'Failed to find receive channel for polarization {}'. format(polarization)) chirp_length = band_dict[band_name]['Range Chirp Length'] chirp_rate = abs(band_dict[band_name]['Range Chirp Rate']) sample_rate = band_dict[band_name]['Sampling Rate'] ref_dechirp_time = band_dict[band_name].get( 'Reference Dechirping Time', 0) # TODO: is this right? win_length = band_dict[band_name]['Echo Sampling Window Length'] rcv_fm_rate = 0 if numpy.isnan(ref_dechirp_time) else chirp_rate band_width = chirp_length * chirp_rate fr_min = center_frequency - 0.5 * band_width fr_max = center_frequency + 0.5 * band_width sicd.RadarCollection.TxFrequency = (fr_min, fr_max) sicd.RadarCollection.Waveform = [ WaveformParametersType(index=0, TxPulseLength=chirp_length, TxRFBandwidth=band_width, TxFreqStart=fr_min, TxFMRate=chirp_rate, ADCSampleRate=sample_rate, RcvFMRate=rcv_fm_rate, RcvWindowLength=win_length / sample_rate), ] sicd.ImageFormation.RcvChanProc.ChanIndices = [ ind + 1, ] sicd.ImageFormation.TxFrequencyProc = (fr_min, fr_max) def update_rma_and_grid(sicd: SICDType, band_name: str) -> None: rg_scp_time = rg_first_time + (ss_rg_s * sicd.ImageData.SCPPixel.Row) az_scp_time = az_first_time + (use_sign2 * ss_az_s * sicd.ImageData.SCPPixel.Col) r_ca_scp = rg_scp_time * speed_of_light / 2 sicd.RMA.INCA.R_CA_SCP = r_ca_scp # compute DRateSFPoly scp_ca_time = az_scp_time + ref_time_offset vel_poly = sicd.Position.ARPPoly.derivative(der_order=1, return_poly=True) vel_ca_vec = vel_poly(scp_ca_time) vel_ca_sq = numpy.sum(vel_ca_vec * vel_ca_vec) vel_ca = numpy.sqrt(vel_ca_sq) r_ca = numpy.array([r_ca_scp, 1.], dtype=numpy.float64) dop_rate_poly_rg_shifted = dop_rate_poly_rg.shift( rg_ref_time - rg_scp_time, alpha=ss_rg_s / row_ss, return_poly=False) drate_sf_poly = -(polynomial.polymul(dop_rate_poly_rg_shifted, r_ca) * speed_of_light / (2 * center_frequency * vel_ca_sq)) # update grid.row sicd.Grid.Row.SS = row_ss sicd.Grid.Row.ImpRespBW = row_bw sicd.Grid.Row.DeltaK1 = -0.5 * row_bw sicd.Grid.Row.DeltaK2 = 0.5 * row_bw # update grid.col col_ss = abs(vel_ca * ss_az_s * drate_sf_poly[0]) sicd.Grid.Col.SS = col_ss if self.mission_id == 'CSK': col_bw = min( band_dict[band_name] ['Azimuth Focusing Transition Bandwidth'] * ss_az_s, 1) / col_ss elif self.mission_id in ['CSG', 'KMPS']: col_bw = min( band_dict[band_name]['Azimuth Focusing Bandwidth'] * ss_az_s, 1) / col_ss else: raise ValueError('Got unhandled mission_id {}'.format( self.mission_id)) sicd.Grid.Col.ImpRespBW = col_bw # update inca sicd.RMA.INCA.DRateSFPoly = Poly2DType( Coefs=numpy.reshape(drate_sf_poly, (-1, 1))) sicd.RMA.INCA.TimeCAPoly = Poly1DType( Coefs=[scp_ca_time, use_sign2 * ss_az_s / col_ss]) # compute DopCentroidPoly & DeltaKCOAPoly dop_centroid_poly = numpy.zeros( (dop_poly_rg.order1 + 1, dop_poly_az.order1 + 1), dtype=numpy.float64) dop_centroid_poly[0, 0] = dop_poly_rg(rg_scp_time-rg_ref_time) + \ dop_poly_az(az_scp_time-az_ref_time) - \ 0.5*(dop_poly_rg[0] + dop_poly_az[0]) dop_poly_rg_shifted = dop_poly_rg.shift(rg_ref_time - rg_scp_time, alpha=ss_rg_s / row_ss) dop_poly_az_shifted = dop_poly_az.shift(az_ref_time - az_scp_time, alpha=ss_az_s / col_ss) dop_centroid_poly[1:, 0] = dop_poly_rg_shifted[1:] dop_centroid_poly[0, 1:] = dop_poly_az_shifted[1:] sicd.RMA.INCA.DopCentroidPoly = Poly2DType(Coefs=dop_centroid_poly) sicd.RMA.INCA.DopCentroidCOA = True sicd.Grid.Col.DeltaKCOAPoly = Poly2DType( Coefs=use_sign * dop_centroid_poly * ss_az_s / col_ss) # fit TimeCOAPoly sicd.Grid.TimeCOAPoly = fit_time_coa_polynomial( sicd.RMA.INCA, sicd.ImageData, sicd.Grid, dop_rate_poly_rg_shifted, poly_order=2) if csk_addin is not None: csk_addin.check_sicd(sicd, self.mission_id, h5_dict) def update_radiometric(sicd: SICDType, band_name: str) -> None: if self.mission_id in ['KMPS', 'CSG']: # TODO: skipping for now - strange results for flag == 77. Awaiting gidance - see Wade. return if h5_dict['Range Spreading Loss Compensation Geometry'] != 'NONE': slant_range = h5_dict['Reference Slant Range'] exp = h5_dict['Reference Slant Range Exponent'] sf = slant_range**(2 * exp) rsf = h5_dict['Rescaling Factor'] sf /= rsf * rsf if h5_dict.get('Calibration Constant Compensation Flag', None) == 0: cal = band_dict[band_name]['Calibration Constant'] sf /= cal sicd.Radiometric = RadiometricType(BetaZeroSFPoly=Poly2DType( Coefs=[ [ sf, ], ])) def update_geodata(sicd: SICDType) -> None: scp_pixel = [ sicd.ImageData.SCPPixel.Row, sicd.ImageData.SCPPixel.Col ] ecf = sicd.project_image_to_ground(scp_pixel, projection_type='HAE') sicd.update_scp(ecf, coord_system='ECF') SCP = sicd.GeoData.SCP.ECF.get_array(dtype='float64') scp_time = sicd.RMA.INCA.TimeCAPoly[0] ca_pos = sicd.Position.ARPPoly(scp_time) RG = SCP - ca_pos sicd.RMA.INCA.R_CA_SCP = numpy.linalg.norm(RG) out = {} center_frequency = h5_dict['Radar Frequency'] # relative times in csk are wrt some reference time - for sicd they should be relative to start time collect_start = parse_timestring(h5_dict['Scene Sensing Start UTC'], precision='ns') ref_time = parse_timestring(h5_dict['Reference UTC'], precision='ns') ref_time_offset = get_seconds(ref_time, collect_start, precision='ns') for bd_name in band_dict: polarization = self._parse_pol( self._get_polarization(h5_dict, band_dict, bd_name)) az_ref_time, rg_ref_time, t_dop_poly_az, t_dop_poly_rg, t_dop_rate_poly_rg = \ self._get_dop_poly_details(h5_dict, band_dict, bd_name) dop_poly_az = Poly1DType(Coefs=t_dop_poly_az) dop_poly_rg = Poly1DType(Coefs=t_dop_poly_rg) t_sicd = base_sicd.copy() t_sicd.ImageFormation.TxRcvPolarizationProc = polarization row_bw = band_dict[bd_name][ 'Range Focusing Bandwidth'] * 2 / speed_of_light row_ss = band_dict[bd_name]['Column Spacing'] rg_first_time, ss_rg_s, az_first_time, ss_az_s, use_sign2 = update_image_data( t_sicd, bd_name) use_sign, dop_rate_poly_rg = check_switch_state() update_timeline(t_sicd, bd_name) update_radar_collection(t_sicd, bd_name) update_rma_and_grid(t_sicd, bd_name) update_radiometric(t_sicd, bd_name) update_scp_prelim( t_sicd, bd_name ) # set preliminary value for SCP (required for projection) update_geodata(t_sicd) t_sicd.derive() # t_sicd.populate_rniirs(override=False) out[bd_name] = t_sicd return out