def radar_modes(self): radar_modes = RadarModes() radar_mode_flag_list = np.unique(self.waveform.radar_mode) radar_mode_list = [] for radar_mode_flag in radar_mode_flag_list: radar_mode_list.append(radar_modes.name(radar_mode_flag)) return ";".join(radar_mode_list)
def __init__(self, info): self._info = info # Pointer to Metadate object # Attributes self.echo_power_unit = None self.radar_mode_def = RadarModes() # Parameter self._power = None self._range = None self._radar_mode = None self._is_valid = None
def update_waveform_statistics(self): """ Compute waveform metadata attributes """ # waveform property infos (lrm, sar, sarin) radar_modes = RadarModes() radar_mode = self.waveform.radar_mode # Check if radar mode is none # (e.g. if only header information is parsed at this stage) if radar_mode is None: return # Compute the percentage of each radar mode in the l1b object nrecs_fl = float(self.n_records) for flag in range(radar_modes.num): is_this_radar_mode = np.where(radar_mode == flag)[0] radar_mode_percent = 100.*float(len(is_this_radar_mode))/nrecs_fl attribute_name = "%s_mode_percent" % radar_modes.name(flag) self.info.set_attribute(attribute_name, radar_mode_percent)
def __init__(self, *args, **kwargs): """ Initializes the class """ # The result of the surface type classification # NOTE: This instance contains the number codes for each surface type self.surface_type = SurfaceType() # A data container for parameters (usually waveform parameters, but also auxiliary data) # used to classify the surface type self.classifier = ClassifierContainer() # This instance can be used to related the radar mode flag with the name for the radar mode # (which will certainly be used in the processor definition file self.radar_modes = RadarModes() # This list will need to be set by the child class # It should contain a list of surface types that the algorithm will detect # (see SurfaceType.SURFACE_TYPE_DICT for the names) self._classes = []
class L1bWaveforms(object): """ Container for Echo Power Waveforms """ _valid_radar_modes = ["lrm", "sar", "sin"] _parameter_list = ["power", "range", "radar_mode", "is_valid", "classification_flag"] _attribute_list = ["echo_power_unit"] def __init__(self, info): self._info = info # Pointer to Metadate object # Attributes self.echo_power_unit = None self.radar_mode_def = RadarModes() # Parameter self._power = None self._range = None self._radar_mode = None self._is_valid = None self._classification_flag = None @property def power(self): return np.copy(self._power) @property def classification_flag(self): if self._classification_flag is None: return np.full(self.n_records, -1, dtype=int) return np.copy(self._classification_flag) @property def range(self): return np.copy(self._range) @property def radar_mode(self): return np.copy(self._radar_mode) @property def is_valid(self): return np.copy(self._is_valid) @property def parameter_list(self): return list(self._parameter_list) @property def n_range_bins(self): return self._get_wfm_shape(1) @property def n_records(self): return self._get_wfm_shape(0) @property def radar_modes(self): if self._radar_mode is None: return "none" flags = np.unique(self._radar_mode) return [self.radar_mode_def.get_name(flag) for flag in flags] @property def dimdict(self): """ Returns dictionary with dimensions""" shape = np.shape(self._power) dimdict = OrderedDict([("n_records", shape[0]), ("n_bins", shape[1])]) return dimdict def set_waveform_data(self, power, range, radar_mode, classification_flag=None): """ Set the waveform data :param power: :param range: :param radar_mode: :param classification_flag: :return: """ # Validate input if power.shape != range.shape: raise ValueError("power and range must be of same shape", power.shape, range.shape) if len(power.shape) != 2: raise ValueError("power and range arrays must be of dimension (n_records, n_bins)") # Validate number of records self._info.check_n_records(power.shape[0]) # Assign values self._power = power self._range = range # Create radar mode arrays if type(radar_mode) is str and radar_mode in self._valid_radar_modes: mode_flag = self.radar_mode_def.get_flag(radar_mode) self._radar_mode = np.repeat(mode_flag, self.n_records).astype(np.byte) elif len(radar_mode) == self._info.n_records: self._radar_mode = radar_mode.astype(np.int8) else: raise ValueError("Invalid radar_mode: ", radar_mode) # Set valid flag (assumed to be valid for all waveforms) # Flag can be set separately using the set_valid_flag method if self._is_valid is None: self._is_valid = np.ones(shape=self.n_records, dtype=bool) def set_valid_flag(self, valid_flag): # Validate number of records self._info.check_n_records(len(valid_flag)) self._is_valid = valid_flag def set_classification_flag(self, classification_flag): """ Add or update the waveform classification flag :param classification_flag: intarray with shape (n_records, n_range_bins) :return: """ # Validate number of records if classification_flag.shape != self.power.shape: raise ValueError(f"Invalid dimensions: {classification_flag.shape} [{self.power.shape}]") self._classification_flag = classification_flag def append(self, annex): self._power = np.concatenate((self._power, annex.power), axis=0) self._range = np.concatenate((self._range, annex.range), axis=0) self._radar_mode = np.append(self._radar_mode, annex.radar_mode) self._is_valid = np.append(self._is_valid, annex.is_valid) def set_subset(self, subset_list): self._power = self._power[subset_list, :] self._range = self._range[subset_list, :] self._radar_mode = self._radar_mode[subset_list] self._is_valid = self._is_valid[subset_list] def add_range_delta(self, range_delta): """ Add a range delta to all range bins :param range_delta: :return: """ range_delta_reshaped = np.repeat(range_delta, self.n_range_bins) range_delta_reshaped = range_delta_reshaped.reshape(self.n_records, self.n_range_bins) self._range += range_delta_reshaped def fill_gaps(self, corrected_n_records, gap_indices, indices_map): """ API gap filler method. Note: Gaps will be filled with custom values for each parameter, see below""" # is_valid flag: False for gaps is_valid = np.full(corrected_n_records, False) is_valid[indices_map] = self.is_valid self.set_valid_flag(is_valid) # Power/range: set gaps to nan power = np.full((corrected_n_records, self.n_range_bins), np.nan) power[indices_map, :] = self.power range = np.full((corrected_n_records, self.n_range_bins), np.nan) range[indices_map, :] = self.range # Radar map: set gaps to lrm radar_mode = np.full((corrected_n_records), 1, dtype=self.radar_mode.dtype) radar_mode[indices_map] = self.radar_mode # And set new values self.set_waveform_data(power, range, radar_mode) def _get_wfm_shape(self, index): shape = np.shape(self._power) return shape[index]
def __init__(self): self._surface_type = SurfaceType() self._l1b_surface_type = None self._classifier = ClassifierContainer() self._radar_modes = RadarModes()
class SurfaceTypeClassifier(object): """ Parent Class for surface type classifiers """ def __init__(self): self._surface_type = SurfaceType() self._l1b_surface_type = None self._classifier = ClassifierContainer() self._radar_modes = RadarModes() @property def result(self): return self._surface_type def set_options(self, **opt_dict): self._options = TreeDict.fromdict(opt_dict, expand_nested=True) def add_classifiers(self, classifier, name): self._classifier.add_parameter(classifier, name) def set_l1b_surface_type(self, l1b_surface_type): self._l1b_surface_type = l1b_surface_type def set_initial_classification(self, surface_type): """ Overwrite classification""" self._surface_type = surface_type def classify(self, l1b, l2): # Add all classifiers from l1bdata for classifier_name in l1b.classifier.parameter_list: classifier = getattr(l1b.classifier, classifier_name) self.add_classifiers(classifier, classifier_name) # add year/month from L1b self._year = l1b.info.start_time.year self._month = l1b.info.start_time.month # add sea ice concentration self.add_classifiers(l2.sic, "sic") self.add_classifiers(l2.sic, "mss") # add radar mode self.add_classifiers(l1b.waveform.radar_mode, "radar_mode") # Initialize with unkown self.set_unknown_default() # loop over different radar modes # Note: This is necessary for CryoSat-2 with mixed SAR/SIN segments for radar_mode in l1b.waveform.radar_modes: # Obtain radar mode specific options # (with failsaife for older settings files) if radar_mode in self._options: options = self._options[radar_mode] else: options = self._options # get the radar mode flag radar_mode_flag = self._radar_modes.get_flag(radar_mode) # Create mandatory condition self._is_radar_mode = l1b.waveform.radar_mode == radar_mode_flag # Classify self._classify(options) # Keep land information # (also overwrite any potential impossible classifications) self.set_l1b_land_mask(l1b) def has_class(self, name): return name in self._classes def set_unknown_default(self): flag = np.ones(shape=(self._classifier.n_records), dtype=np.bool) self._surface_type.add_flag(flag, "unknown") def set_l1b_land_mask(self, l1b): l1b_land_mask = l1b.surface_type.get_by_name("land") self._surface_type.add_flag(l1b_land_mask.flag, "land")
def _classify_surface_type(self, opt_dict: dict) -> 'ANDCondition': """ Compute the surface type flag from the conditions in the config file for a specific radar mode. The expected structure of `opt_dict` is: [exclude: surface_type_name] (Optional) radar_mode: <radar mode id> conditions: - logical expression with parameter name in brackets {} The expression will be evaluated with `eval()` at run time and merged via AND: flag = condition1 AND condition2 AND condition3 .... If the `exclude` option is set, a condition will be added that the the current surface type cannot be any waveform that was previously (see order in `cfg.options.surface_types`) attributed to another surface type. :param opt_dict: :return: """ # Init the flag surface_type_flag = ANDCondition() # A per radar mode classification is mandatory radar_mode_flag = RadarModes.get_flag(opt_dict["radar_mode"]) surface_type_flag.add(self.classifier.radar_mode == radar_mode_flag) # Some classifiers may use the month_num for choosing thresholds values month_num = self.reference_date.month # Break if no data for current radar mode if surface_type_flag.num == 0: return surface_type_flag # Add the conditions from the config file for expression in opt_dict["conditions"]: # --- Construct and evaluate the expression --- # NOTE: `eval()` uses the run time parameter space. Thus, the # expression is copied and changed to the variable name # `parameter´ # Update the expression if the threshold value is a monthly list. Example: # `{sigma0} <= [16.77, 15.56, 14.44, 14.00, nan, nan, nan, nan, nan, 20.05, 18.10, 16.76]` # -> `{sigma0} <= 16.76` for December (12th item of value list) value_list = re.search(r"\[(.*?)]", expression) if value_list: values = value_list.group(0)[1:-1].split(",") value = values[month_num-1] expression = expression.replace(value_list.group(0), value) # Get the parameter from the classifier container parameter_name = self._get_expr_param(expression) parameter = self.classifier.get(parameter_name) # Update the expression to local namespace expression = str(expression).replace(f"{{{parameter_name}}}", "parameter") # Evaluate the (updated) expression with the level of safety that # is possible with eval() # TODO: Add expression validation flag = eval(expression, {"__builtins__": {"parameter": parameter}}, {}) # Update the surface type flag surface_type_flag.add(flag) # The exclude option can be used as a conditions "is not this surface type" # For this to work the to target surface type needs to be classified before # this one (see order in option.surface_types) if "exclude" in opt_dict.keys(): exclude_surface_type_flag = self.surface_type.get_by_name(opt_dict["exclude"]) surface_type_flag.add(np.logical_not(exclude_surface_type_flag.flag)) # All done, return the flag return surface_type_flag