Example #1
0
 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)
Example #2
0
 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
Example #3
0
    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)
Example #4
0
    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 = []
Example #5
0
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]
Example #6
0
 def __init__(self):
     self._surface_type = SurfaceType()
     self._l1b_surface_type = None
     self._classifier = ClassifierContainer()
     self._radar_modes = RadarModes()
Example #7
0
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")
Example #8
0
    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