def reform_waveform(self, mds_ra2, mds_wfm18hz): # Relevant field names wfm_tag = "average_wfm_if_corr_ku" tracker_range_tag = "18hz_tracker_range_no_doppler_ku" doppler_tag = "18Hz_ku_range_doppler" slope_tag = "18Hz_ku_range_doppler_slope" # First get the echo power n_range_bins = 128 n = self.n_records * self.n_blocks self.power = np.ndarray(shape=(n, n_range_bins), dtype=np.float32) for dsd in range(self.n_records): for block in range(self.n_blocks): i = dsd*self.n_blocks + block self.power[i, :] = mds_wfm18hz[dsd].wfm[block][wfm_tag] # Calculate the window delay for each 18hz waveform range_info = get_structarr_attr(mds_ra2, "range_information") range_corr = get_structarr_attr(mds_ra2, "range_correction") tracker_range = get_structarr_attr( range_info, tracker_range_tag, flat=True) doppler_correction = get_structarr_attr( range_corr, doppler_tag, flat=True) slope_correction = get_structarr_attr( range_corr, slope_tag, flat=True) # Compute the window delay (range to first range bin) # given in meter (not in seconds) # XXX: Add the instrumental range correction for ku? self.window_delay_m = get_envisat_window_delay( tracker_range, doppler_correction, slope_correction) # Compute the range value for each range bin of the 18hz waveform # XXX: Might want to set the range bins automatically self.range = get_envisat_wfm_range(self.window_delay_m, n_range_bins)
def _transfer_timeorbit(self): # Transfer the orbit position longitude = get_structarr_attr(self.cs2l1b.time_orbit, "longitude") latitude = get_structarr_attr(self.cs2l1b.time_orbit, "latitude") altitude = get_structarr_attr(self.cs2l1b.time_orbit, "altitude") self.l1b.time_orbit.set_position(longitude, latitude, altitude) # Transfer the timestamp tai_objects = get_structarr_attr(self.cs2l1b.time_orbit, "tai_timestamp") tai_timestamp = get_tai_datetime_from_timestamp(tai_objects) # Convert the TAI timestamp to UTC # XXX: Note, the leap seconds are only corrected based on the # first timestamp to avoid having identical timestamps. # In the unlikely case this will cause problems in orbits that # span over a leap seconds change, set check_all=True converter = UTCTAIConverter() utc_timestamp = converter.tai2utc(tai_timestamp, check_all=False) self.l1b.time_orbit.timestamp = utc_timestamp # Set antenna pitch, roll, yaw (dummy values for now) dummy_val = np.full(longitude.shape, np.nan) self.l1b.time_orbit.set_antenna_attitude(dummy_val, dummy_val, dummy_val)
def reform_surface_type_flags(self, mds): flag = get_structarr_attr(mds, "flag") surface_type = get_structarr_attr(flag, "altimeter_surface_type") radiometer_flag = get_structarr_attr(flag, "radiometer_land_ocean") sea_ice_flag = get_structarr_attr(flag, "sea_ice") self.surface_type = np.repeat(surface_type, self.n_blocks) self.radiometer_flag = np.repeat(radiometer_flag, self.n_blocks) self.sea_ice_flag = np.repeat(sea_ice_flag, self.n_blocks)
def _transfer_range_corrections(self): # Transfer all the correction in the list # TODO: This is too complicated. The unification of grc names should be handled in the l1p config files for key in self.cs2l1b.corrections[0].keys(): if key in self._config.CORRECTION_LIST: self.l1b.correction.set_parameter( key, get_structarr_attr(self.cs2l1b.corrections, key)) # CryoSat-2 specific: Two different sources of ionospheric corrections options = self._config.get_mission_defaults(self._mission) key = options["ionospheric_correction_source"] ionospheric = get_structarr_attr(self.cs2l1b.corrections, key) self.l1b.correction.set_parameter("ionospheric", ionospheric)
def reform_geophysical_corrections(self, mds, grc_targets): """ Automatically extract the corrections and replicate 1Hz => 18 Hz grc_targets is defined in config/mission_def.yaml -> envisat.settings.geophysical_correction_targets """ self.sgdr_geophysical_correction_list = [] for key in grc_targets.keys(): mds_group = get_structarr_attr(mds, key) for correction_name in grc_targets[key]: correction = get_structarr_attr(mds_group, correction_name) setattr(self, correction_name, np.repeat(correction, self.n_blocks)) self.sgdr_geophysical_correction_list.append(correction_name)
def _transfer_surface_type_data(self): # L1b surface type flag word surface_type = get_structarr_attr(self.cs2l1b.corrections, "surface_type") for key in ESA_SURFACE_TYPE_DICT.keys(): flag = surface_type == ESA_SURFACE_TYPE_DICT[key] self.l1b.surface_type.add_flag(flag, key)
def reform_timestamp(self, mds): """ Creates an array of datetime objects for each 18Hz record """ # XXX: Current no microsecond correction timestamp = np.ndarray(shape=(self.n_records), dtype=object) mdsr_timestamp = get_structarr_attr(mds, "utc_timestamp") for i in range(self.n_records): timestamp[i] = mdsr_timestamp_to_datetime(mdsr_timestamp[i]) self.timestamp = np.repeat(timestamp, self.n_blocks)
def reform_position(self, mds): # 0) Get the relevant data blocks time_orbit = get_structarr_attr(mds, "time_orbit") range_block = get_structarr_attr(mds, "range_information") # 1) get the 1Hz data from the time_orbit group longitude = get_structarr_attr(time_orbit, "longitude") latitude = get_structarr_attr(time_orbit, "latitude") altitude = get_structarr_attr(time_orbit, "altitude") # 2) Get the increments lon_inc = get_structarr_attr(range_block, "18hz_longitude_differences") lat_inc = get_structarr_attr(range_block, "18hz_latitude_differences") alt_inc = get_structarr_attr(time_orbit, "18hz_altitude_differences") # 3) Expand the 1Hz position arrays self.longitude = np.repeat(longitude, self.n_blocks) self.latitude = np.repeat(latitude, self.n_blocks) self.altitude = np.repeat(altitude, self.n_blocks) # 4) Apply the increments # XXX: Current version of get_struct_arr returns datatype objects # for listcontainers => manually set dtype self._apply_18Hz_increment(self.longitude, lon_inc.astype(np.float32)) self._apply_18Hz_increment(self.latitude, lat_inc.astype(np.float32)) self._apply_18Hz_increment(self.altitude, alt_inc.astype(np.float32))
def reform_flags(self, mds): """ Retrieves a set of paramaters from the Envisat mds, that are needed as flags for waveform flagging and surface type filtering. Get the following parameters for the waveform is_valid flag: 1) MCD flags (time_orbit.measurement_confidence_data) mcd[0]: packet_length_error mcd[1]: obdh_invalid mcd[4]: agc_fault mcd[5]: rx_delay_fault mcd[6]: waveform_fault 2) average_ku_chirp_band (must be 0: 320Mhz) Get the follwing parameter for surface type classification/filtering sea_ice_backscatter: backscatter.18hz_sea_ice_sigma_ku """ time_orbit = get_structarr_attr(mds, "time_orbit") mcd = get_structarr_attr(time_orbit, "measurement_confidence_data") self.flag_packet_length_error = np.repeat( np.array([record.flag[0] for record in mcd], dtype=bool), self.n_blocks) self.flag_obdh_invalid = np.repeat( np.array([record.flag[1] for record in mcd], dtype=bool), self.n_blocks) self.flag_agc_fault = np.repeat( np.array([record.flag[4] for record in mcd], dtype=bool), self.n_blocks) self.flag_rx_delay_fault = np.repeat( np.array([record.flag[5] for record in mcd], dtype=bool), self.n_blocks) self.flag_waveform_fault = np.repeat( np.array([record.flag[6] for record in mcd], dtype=bool), self.n_blocks) flags = get_structarr_attr(mds, "flag") self.ku_chirp_band_id = np.repeat( get_structarr_attr(flags, "average_ku_chirp_band"), self.n_blocks) # This needed for surface type classifiation backscatter = get_structarr_attr(mds, "backscatter") self.sea_ice_backscatter = np.array(get_structarr_attr( backscatter, "18hz_sea_ice_sigma_ku", flat=True), dtype=np.float32)
def _transfer_classifiers(self): # Add L1b beam parameter group beam_parameter_list = [ "stack_standard_deviation", "stack_centre", "stack_scaled_amplitude", "stack_skewness", "stack_kurtosis" ] for beam_parameter_name in beam_parameter_list: recs = get_structarr_attr(self.cs2l1b.waveform, "beam") beam_parameter = [rec[beam_parameter_name] for rec in recs] self.l1b.classifier.add(beam_parameter, beam_parameter_name) # Calculate Parameters from waveform counts # XXX: This is a legacy of the CS2AWI IDL processor # Threshold defined for waveform counts not power in dB wfm = get_structarr_attr(self.cs2l1b.waveform, "wfm") # Calculate the OCOG Parameter (CryoSat-2 notation) ocog = CS2OCOGParameter(wfm) self.l1b.classifier.add(ocog.width, "ocog_width") self.l1b.classifier.add(ocog.amplitude, "ocog_amplitude") # Calculate the Peakiness (CryoSat-2 notation) pulse = CS2PulsePeakiness(wfm) self.l1b.classifier.add(pulse.peakiness, "peakiness") self.l1b.classifier.add(pulse.peakiness_r, "peakiness_r") self.l1b.classifier.add(pulse.peakiness_l, "peakiness_l") # fmi version: Calculate the LTPP ltpp = CS2LTPP(wfm) self.l1b.classifier.add(ltpp.ltpp, "late_tail_to_peak_power") # Add the peak power (in Watts) # (use l1b waveform power array that is already in physical units) peak_power = get_waveforms_peak_power(self.l1b.waveform.power, dB=True) self.l1b.classifier.add(peak_power, "peak_power_db") # Compute the leading edge width (requires TFMRA retracking) wfm = self.l1b.waveform.power rng = self.l1b.waveform.range radar_mode = self.l1b.waveform.radar_mode is_ocean = self.l1b.surface_type.get_by_name("ocean").flag # fmi version: add of LEW width = TFMRALeadingEdgeWidth(rng, wfm, radar_mode, is_ocean) lew = width.get_width_from_thresholds(0.05, 0.95) lew1 = width.get_width_from_thresholds(0.05, 0.5) lew2 = width.get_width_from_thresholds(0.5, 0.95) self.l1b.classifier.add(lew, "leading_edge_width") self.l1b.classifier.add(lew1, "leading_edge_width_first_half") self.l1b.classifier.add(lew2, "leading_edge_width_second_half") self.l1b.classifier.add(width.fmi, "first_maximum_index") # Compute sigma nought peak_power = get_waveforms_peak_power(self.l1b.waveform.power) tx_power = get_structarr_attr(self.cs2l1b.measurement, "tx_power") altitude = self.l1b.time_orbit.altitude v = get_structarr_attr(self.cs2l1b.time_orbit, "satellite_velocity") vx2, vy2, vz2 = v[:, 0]**2., v[:, 1]**2., v[:, 2]**2 vx2, vy2, vz2 = vx2.astype(float), vy2.astype(float), vz2.astype(float) velocity = np.sqrt(vx2 + vy2 + vz2) sigma0 = get_sar_sigma0(peak_power, tx_power, altitude, velocity) self.l1b.classifier.add(sigma0, "sigma0")