Пример #1
0
def main(argv=None):
    """Load in arguments and get going."""
    parser = ArgParser(
        description='Apply the requested neighbourhood method via '
        'the NeighbourhoodProcessing plugin to a file '
        'whose data can be loaded as a single iris.cube.Cube.')
    parser.add_argument(
        'neighbourhood_output',
        metavar='NEIGHBOURHOOD_OUTPUT',
        help='The form of the results generated using neighbourhood '
        'processing. If "probabilities" is selected, the mean '
        'probability within a neighbourhood is calculated. If '
        '"percentiles" is selected, then the percentiles are calculated '
        'within a neighbourhood. Calculating percentiles from a '
        'neighbourhood is only supported for a circular neighbourhood. '
        'Options: "probabilities", "percentiles".')
    parser.add_argument('neighbourhood_shape',
                        metavar='NEIGHBOURHOOD_SHAPE',
                        choices=["circular", "square"],
                        help='The shape of the neighbourhood to apply in '
                        'neighbourhood processing. Only a "circular" '
                        'neighbourhood shape is applicable for '
                        'calculating "percentiles" output. '
                        'Options: "circular", "square".')
    group = parser.add_mutually_exclusive_group()
    group.add_argument('--radius',
                       metavar='RADIUS',
                       type=float,
                       help='The radius (in m) for neighbourhood processing.')
    group.add_argument('--radii-by-lead-time',
                       metavar=('RADII_BY_LEAD_TIME', 'LEAD_TIME_IN_HOURS'),
                       nargs=2,
                       help='The radii for neighbourhood processing '
                       'and the associated lead times at which the radii are '
                       'valid. The radii are in metres whilst the lead time '
                       'has units of hours. The radii and lead times are '
                       'expected as individual comma-separated lists with '
                       'the list of radii given first followed by a list of '
                       'lead times to indicate at what lead time each radii '
                       'should be used. For example: 10000,12000,14000 1,2,3 '
                       'where a lead time of 1 hour uses a radius of 10000m, '
                       'a lead time of 2 hours uses a radius of 12000m, etc.')
    parser.add_argument('--degrees_as_complex',
                        action='store_true',
                        default=False,
                        help='Set this flag to process angles,'
                        ' eg wind directions, as complex numbers. Not '
                        'compatible with circular kernel, percentiles or '
                        'recursive filter.')
    parser.add_argument('--weighted_mode',
                        action='store_true',
                        default=False,
                        help='For neighbourhood processing using a circular '
                        'kernel, setting the weighted_mode indicates the '
                        'weighting decreases with radius. '
                        'If weighted_mode is not set, a constant '
                        'weighting is assumed. weighted_mode is only '
                        'applicable for calculating "probability" '
                        'neighbourhood output.')
    parser.add_argument('--sum_or_fraction',
                        default="fraction",
                        choices=["sum", "fraction"],
                        help='The neighbourhood output can either be in the '
                        'form of a sum of the neighbourhood, or a '
                        'fraction calculated by dividing the sum of the '
                        'neighbourhood by the neighbourhood area. '
                        '"fraction" is the default option.')
    parser.add_argument('--re_mask',
                        action='store_true',
                        help='If re_mask is set (i.e. True), the original '
                        'un-neighbourhood processed mask is applied to '
                        'mask out the neighbourhood processed dataset. '
                        'If not set, re_mask defaults to False and the '
                        'original un-neighbourhood processed mask is '
                        'not applied. Therefore, the neighbourhood '
                        'processing may result in values being present '
                        'in areas that were originally masked. ')
    parser.add_argument('--percentiles',
                        metavar='PERCENTILES',
                        default=DEFAULT_PERCENTILES,
                        nargs='+',
                        type=float,
                        help='Calculate values at the specified percentiles '
                        'from the neighbourhood surrounding each grid '
                        'point.')
    parser.add_argument('input_filepath',
                        metavar='INPUT_FILE',
                        help='A path to an input NetCDF file to be processed.')
    parser.add_argument('output_filepath',
                        metavar='OUTPUT_FILE',
                        help='The output path for the processed NetCDF.')
    parser.add_argument('--input_mask_filepath',
                        metavar='INPUT_MASK_FILE',
                        help='A path to an input mask NetCDF file to be '
                        'used to mask the input file. '
                        'This is currently only supported for square '
                        'neighbourhoods. The data should contain 1 for '
                        'usable points and 0 for discarded points, e.g. '
                        'a land-mask.')
    parser.add_argument('--halo_radius',
                        metavar='HALO_RADIUS',
                        default=None,
                        type=float,
                        help='radius in metres of excess halo to clip.'
                        ' Used where a larger'
                        ' grid was defined than the standard grid'
                        ' and we want to clip the grid back to the'
                        ' standard grid e.g. for global data'
                        ' regridded to UK area. Default=None')
    parser.add_argument('--apply-recursive-filter',
                        action='store_true',
                        default=False,
                        help='Option to apply the recursive filter to a '
                        'square neighbourhooded output dataset, '
                        'converting it into a Gaussian-like kernel or '
                        'smoothing over short distances. '
                        'The filter uses an alpha '
                        'parameter (0 < alpha < 1) to control what '
                        'proportion of the probability is passed onto '
                        'the next grid-square in the x and y directions. '
                        'The alpha parameter can be set on a grid-square '
                        'by grid-square basis for the x and y directions '
                        'separately (using two arrays of alpha '
                        'parameters of the same dimensionality as the '
                        'domain). Alternatively a single alpha value can '
                        'be set for each of the x and y directions. These'
                        ' methods can be mixed, e.g. an array for the x '
                        'direction and a float for the y direction and '
                        'vice versa. The recursive filter cannot be '
                        'applied to a circular kernel')
    parser.add_argument('--input_filepath_alphas_x_cube',
                        metavar='ALPHAS_X_FILE',
                        help='A path to a NetCDF file describing the alpha '
                        'factors to be used for smoothing in the x '
                        'direction when applying the recursive filter')
    parser.add_argument('--input_filepath_alphas_y_cube',
                        metavar='ALPHAS_Y_FILE',
                        help='A path to a NetCDF file describing the alpha '
                        'factors to be used for smoothing in the y '
                        'direction when applying the recursive filter')
    parser.add_argument('--alpha_x',
                        metavar='ALPHA_X',
                        default=None,
                        type=float,
                        help='A single alpha factor (0 < alpha_x < 1) to be '
                        'applied to every grid square in the x '
                        'direction when applying the recursive filter')
    parser.add_argument('--alpha_y',
                        metavar='ALPHA_Y',
                        default=None,
                        type=float,
                        help='A single alpha factor (0 < alpha_y < 1) to be '
                        'applied to every grid square in the y '
                        'direction when applying the recursive filter.')
    parser.add_argument('--iterations',
                        metavar='ITERATIONS',
                        default=1,
                        type=int,
                        help='Number of times to apply the filter, default=1 '
                        '(typically < 5)')

    args = parser.parse_args(args=argv)

    if (args.neighbourhood_output == "percentiles"
            and args.neighbourhood_shape == "square"):
        parser.wrong_args_error('square', 'neighbourhood_shape')

    if (args.neighbourhood_output == "percentiles" and args.weighted_mode):
        parser.wrong_args_error('weighted_mode',
                                'neighbourhood_shape=percentiles')

    if (args.neighbourhood_output == "probabilities"
            and args.percentiles != DEFAULT_PERCENTILES):
        parser.wrong_args_error('percentiles',
                                'neighbourhood_shape=probabilities')

    if (args.input_mask_filepath and args.neighbourhood_shape == "circular"):
        parser.wrong_args_error('neighbourhood_shape=circular',
                                'input_mask_filepath')

    if args.degrees_as_complex:
        if args.neighbourhood_output == "percentiles":
            parser.error('Cannot generate percentiles from complex numbers')
        if args.neighbourhood_shape == "circular":
            parser.error('Cannot process complex numbers with circular '
                         'neighbourhoods')
        if args.apply_recursive_filter:
            parser.error('Cannot process complex numbers with recursive '
                         'filter')

    cube = load_cube(args.input_filepath)
    if args.degrees_as_complex:
        # convert cube data into complex numbers
        cube.data = WindDirection.deg_to_complex(cube.data)

    if args.radius:
        radius_or_radii = args.radius
        lead_times = None
    elif args.radii_by_lead_time:
        radius_or_radii = args.radii_by_lead_time[0].split(",")
        lead_times = args.radii_by_lead_time[1].split(",")

    if args.input_mask_filepath:
        mask_cube = load_cube(args.input_mask_filepath)
    else:
        mask_cube = None

    if args.neighbourhood_output == "probabilities":
        result = (NeighbourhoodProcessing(args.neighbourhood_shape,
                                          radius_or_radii,
                                          lead_times=lead_times,
                                          weighted_mode=args.weighted_mode,
                                          sum_or_fraction=args.sum_or_fraction,
                                          re_mask=args.re_mask).process(
                                              cube, mask_cube=mask_cube))
    elif args.neighbourhood_output == "percentiles":
        result = (GeneratePercentilesFromANeighbourhood(
            args.neighbourhood_shape,
            radius_or_radii,
            lead_times=lead_times,
            percentiles=args.percentiles).process(cube))

    # If the '--apply-recursive-filter' option has been specified in the
    # input command, pass the neighbourhooded 'result' cube obtained above
    # through the recursive-filter plugin before saving the output.
    # The recursive filter is only applicable to square neighbourhoods.

    if args.neighbourhood_shape == 'square' and args.apply_recursive_filter:

        alphas_x_cube = None
        alphas_y_cube = None

        if args.input_filepath_alphas_x_cube is not None:
            alphas_x_cube = load_cube(args.input_filepath_alphas_x_cube)
        if args.input_filepath_alphas_y_cube is not None:
            alphas_y_cube = load_cube(args.input_filepath_alphas_y_cube)

        result = RecursiveFilter(alpha_x=args.alpha_x,
                                 alpha_y=args.alpha_y,
                                 iterations=args.iterations,
                                 re_mask=args.re_mask).process(
                                     result,
                                     alphas_x=alphas_x_cube,
                                     alphas_y=alphas_y_cube,
                                     mask_cube=mask_cube)

    elif args.neighbourhood_shape == 'circular' and \
            args.apply_recursive_filter:
        raise ValueError('Recursive filter option is not applicable to '
                         'circular neighbourhoods. ')

    if args.degrees_as_complex:
        # convert neighbourhooded cube back to degrees
        result.data = WindDirection.complex_to_deg(result.data)

    if args.halo_radius is not None:
        result = remove_cube_halo(result, args.halo_radius)

    save_netcdf(result, args.output_filepath)
Пример #2
0
def main(argv=None):
    """Load in arguments and ensure they are set correctly.
       Then load in the data to blend and calculate default weights
       using the method chosen before carrying out the blending."""
    parser = ArgParser(
        description='Calculate the default weights to apply in weighted '
        'blending plugins using the ChooseDefaultWeightsLinear or '
        'ChooseDefaultWeightsNonLinear plugins. Then apply these '
        'weights to the dataset using the BasicWeightedAverage plugin.'
        ' Required for ChooseDefaultWeightsLinear: y0val and ynval.'
        ' Required for ChooseDefaultWeightsNonLinear: cval.'
        ' Required for ChooseWeightsLinear with dict: wts_dict.')

    parser.add_argument('--wts_calc_method',
                        metavar='WEIGHTS_CALCULATION_METHOD',
                        choices=['linear', 'nonlinear', 'dict'],
                        default='linear',
                        help='Method to use to calculate '
                        'weights used in blending. "linear" (default): '
                        'calculate linearly varying blending weights. '
                        '"nonlinear": calculate blending weights that decrease'
                        ' exponentially with increasing blending coordinate. '
                        '"dict": calculate weights using a dictionary passed '
                        'in as a command line argument.')

    parser.add_argument('coordinate',
                        type=str,
                        metavar='COORDINATE_TO_AVERAGE_OVER',
                        help='The coordinate over which the blending '
                        'will be applied.')
    parser.add_argument('--coordinate_unit',
                        metavar='UNIT_STRING',
                        default='hours since 1970-01-01 00:00:00',
                        help='Units for blending coordinate. Default= '
                        'hours since 1970-01-01 00:00:00')
    parser.add_argument('--calendar',
                        metavar='CALENDAR',
                        help='Calendar for time coordinate. Default=gregorian')
    parser.add_argument('--cycletime',
                        metavar='CYCLETIME',
                        type=str,
                        help='The forecast reference time to be used after '
                        'blending has been applied, in the format '
                        'YYYYMMDDTHHMMZ. If not provided, the blended file '
                        'will take the latest available forecast reference '
                        'time from the input cubes supplied.')
    parser.add_argument('--model_id_attr',
                        metavar='MODEL_ID_ATTR',
                        type=str,
                        default="mosg__model_configuration",
                        help='The name of the netCDF file attribute to be '
                        'used to identify the source model for '
                        'multi-model blends. Default assumes Met Office '
                        'model metadata. Must be present on all input '
                        'files if blending over models.')
    parser.add_argument('--spatial_weights_from_mask',
                        action='store_true',
                        default=False,
                        help='If set this option will result in the generation'
                        ' of spatially varying weights based on the'
                        ' masks of the data we are blending. The'
                        ' one dimensional weights are first calculated '
                        ' using the chosen weights calculation method,'
                        ' but the weights will then be adjusted spatially'
                        ' based on where there is masked data in the data'
                        ' we are blending. The spatial weights are'
                        ' calculated using the'
                        ' SpatiallyVaryingWeightsFromMask plugin.')
    parser.add_argument('weighting_mode',
                        metavar='WEIGHTED_BLEND_MODE',
                        choices=['weighted_mean', 'weighted_maximum'],
                        help='The method used in the weighted blend. '
                        '"weighted_mean": calculate a normal weighted'
                        ' mean across the coordinate. '
                        '"weighted_maximum": multiplies the values in the'
                        ' coordinate by the weights, and then takes the'
                        ' maximum.')

    parser.add_argument('input_filepaths',
                        metavar='INPUT_FILES',
                        nargs="+",
                        help='Paths to input files to be blended.')
    parser.add_argument('output_filepath',
                        metavar='OUTPUT_FILE',
                        help='The output path for the processed NetCDF.')

    spatial = parser.add_argument_group(
        'Spatial weights from mask options',
        'Options for calculating the spatial weights using the '
        'SpatiallyVaryingWeightsFromMask plugin.')
    spatial.add_argument('--fuzzy_length',
                         metavar='FUZZY_LENGTH',
                         type=float,
                         default=20000,
                         help='When calculating spatially varying weights we'
                         ' can smooth the weights so that areas close to'
                         ' areas that are masked have lower weights than'
                         ' those further away. This fuzzy length controls'
                         ' the scale over which the weights are smoothed.'
                         ' The fuzzy length is in terms of m, the'
                         ' default is 20km. This distance is then'
                         ' converted into a number of grid squares,'
                         ' which does not have to be an integer. Assumes'
                         ' the grid spacing is the same in the x and y'
                         ' directions, and raises an error if this is not'
                         ' true. See SpatiallyVaryingWeightsFromMask for'
                         ' more detail.')

    linear = parser.add_argument_group(
        'linear weights options', 'Options for the linear weights '
        'calculation in '
        'ChooseDefaultWeightsLinear')
    linear.add_argument('--y0val',
                        metavar='LINEAR_STARTING_POINT',
                        type=float,
                        help='The relative value of the weighting start point '
                        '(lowest value of blend coord) for choosing default '
                        'linear weights. This must be a positive float or 0.')
    linear.add_argument('--ynval',
                        metavar='LINEAR_END_POINT',
                        type=float,
                        help='The relative value of the weighting '
                        'end point (highest value of blend coord) for choosing'
                        ' default linear weights. This must be a positive '
                        'float or 0.  Note that if blending over forecast '
                        'reference time, ynval >= y0val would normally be '
                        'expected (to give greater weight to the more recent '
                        'forecast).')

    nonlinear = parser.add_argument_group(
        'nonlinear weights options', 'Options for the non-linear '
        'weights calculation in '
        'ChooseDefaultWeightsNonLinear')
    nonlinear.add_argument('--cval',
                           metavar='NON_LINEAR_FACTOR',
                           type=float,
                           help='Factor used to determine how skewed the '
                           'non linear weights will be. '
                           'A value of 1 implies equal weighting. If not '
                           'set, a default value of cval=0.85 is set.')

    wts_dict = parser.add_argument_group(
        'dict weights options', 'Options for linear weights to be '
        'calculated based on parameters '
        'read from a json file dict')
    wts_dict.add_argument('--wts_dict',
                          metavar='WEIGHTS_DICTIONARY',
                          help='Path to json file containing dictionary from '
                          'which to calculate blending weights. Dictionary '
                          'format is as specified in the improver.blending.'
                          'weights.ChooseWeightsLinear plugin.')
    wts_dict.add_argument('--weighting_coord',
                          metavar='WEIGHTING_COORD',
                          default='forecast_period',
                          help='Name of '
                          'coordinate over which linear weights should be '
                          'scaled. This coordinate must be avilable in the '
                          'weights dictionary.')

    args = parser.parse_args(args=argv)

    # if the linear weights method is called with non-linear args or vice
    # versa, exit with error
    if (args.wts_calc_method == "linear") and args.cval:
        parser.wrong_args_error('cval', 'linear')
    if ((args.wts_calc_method == "nonlinear")
            and np.any([args.y0val, args.ynval])):
        parser.wrong_args_error('y0val, ynval', 'non-linear')
    if (args.wts_calc_method == "dict") and not args.wts_dict:
        parser.error('Dictionary is required if --wts_calc_method="dict"')

    # set blending coordinate units
    if "time" in args.coordinate:
        coord_unit = Unit(args.coordinate_unit, args.calendar)
    elif args.coordinate_unit != 'hours since 1970-01-01 00:00:00.':
        coord_unit = args.coordinate_unit
    else:
        coord_unit = 'no_unit'

    # For blending across models, only blending across "model_id" is directly
    # supported. This is because the blending coordinate must be sortable, in
    # order to ensure that the data cube and the weights cube have coordinates
    # in the same order for blending. Whilst the model_configuration is
    # sortable itself, as it is associated with model_id, which is the
    # dimension coordinate, sorting the model_configuration coordinate can
    # result in the model_id coordinate becoming non-monotonic. As dimension
    # coordinates must be monotonic, this leads to the model_id coordinate
    # being demoted to an auxiliary coordinate. Therefore, for simplicity
    # model_id is used as the blending coordinate, instead of
    # model_configuration.
    # TODO: Support model_configuration as a blending coordinate directly.
    if args.coordinate == "model_configuration":
        blend_coord = "model_id"
        dict_coord = "model_configuration"
    else:
        blend_coord = args.coordinate
        dict_coord = args.coordinate

    # load cubes to be blended
    cubelist = load_cubelist(args.input_filepaths)

    # determine whether or not to equalise forecast periods for model
    # blending weights calculation
    weighting_coord = (args.weighting_coord
                       if args.weighting_coord else "forecast_period")

    # prepare cubes for weighted blending
    merger = MergeCubesForWeightedBlending(blend_coord,
                                           weighting_coord=weighting_coord,
                                           model_id_attr=args.model_id_attr)
    cube = merger.process(cubelist, cycletime=args.cycletime)

    # if the coord for blending does not exist or has only one value,
    # update metadata only
    coord_names = [coord.name() for coord in cube.coords()]
    if (blend_coord not in coord_names) or (len(
            cube.coord(blend_coord).points) == 1):
        result = cube.copy()
        conform_metadata(result, cube, blend_coord, cycletime=args.cycletime)
        # raise a warning if this happened because the blend coordinate
        # doesn't exist
        if blend_coord not in coord_names:
            warnings.warn('Blend coordinate {} is not present on input '
                          'data'.format(blend_coord))

    # otherwise, calculate weights and blend across specified dimension
    else:
        weights = calculate_blending_weights(
            cube,
            blend_coord,
            args.wts_calc_method,
            wts_dict=args.wts_dict,
            weighting_coord=args.weighting_coord,
            coord_unit=coord_unit,
            y0val=args.y0val,
            ynval=args.ynval,
            cval=args.cval,
            dict_coord=dict_coord)

        if args.spatial_weights_from_mask:
            check_if_grid_is_equal_area(cube)
            grid_cells_x, _ = convert_distance_into_number_of_grid_cells(
                cube, args.fuzzy_length, int_grid_cells=False)
            SpatialWeightsPlugin = SpatiallyVaryingWeightsFromMask(
                grid_cells_x)
            weights = SpatialWeightsPlugin.process(cube, weights, blend_coord)

        # blend across specified dimension
        BlendingPlugin = WeightedBlendAcrossWholeDimension(
            blend_coord, args.weighting_mode, cycletime=args.cycletime)
        result = BlendingPlugin.process(cube, weights=weights)

    save_netcdf(result, args.output_filepath)
Пример #3
0
def main(argv=None):
    """Load in arguments and get going."""
    parser = ArgParser(
        description="Calculate the threshold truth value of input data "
        "relative to the provided threshold value. By default data are "
        "tested to be above the thresholds, though the --below_threshold "
        "flag enables testing below thresholds. A fuzzy factor or fuzzy "
        "bounds may be provided to capture data that is close to the "
        "threshold.")
    parser.add_argument("input_filepath",
                        metavar="INPUT_FILE",
                        help="A path to an input NetCDF file to be processed")
    parser.add_argument("output_filepath",
                        metavar="OUTPUT_FILE",
                        help="The output path for the processed NetCDF")
    parser.add_argument("threshold_values",
                        metavar="THRESHOLD_VALUES",
                        nargs="*",
                        type=float,
                        help="Threshold value or values about which to "
                        "calculate the truth values; e.g. 270 300. "
                        "Must be omitted if --threshold_config is used.")
    parser.add_argument("--threshold_config",
                        metavar="THRESHOLD_CONFIG",
                        type=str,
                        help="Threshold configuration JSON file containing "
                        "thresholds and (optionally) fuzzy bounds. Best used "
                        "in combination  with --threshold_units. "
                        "It should contain a dictionary of strings that can "
                        "be interpreted as floats with the structure: "
                        " \"THRESHOLD_VALUE\": [LOWER_BOUND, UPPER_BOUND] "
                        "e.g: {\"280.0\": [278.0, 282.0], "
                        "\"290.0\": [288.0, 292.0]}, or with structure "
                        " \"THRESHOLD_VALUE\": \"None\" (no fuzzy bounds). "
                        "Repeated thresholds with different bounds are not "
                        "handled well. Only the last duplicate will be used.")
    parser.add_argument("--threshold_units",
                        metavar="THRESHOLD_UNITS",
                        default=None,
                        type=str,
                        help="Units of the threshold values. If not provided "
                        "the units are assumed to be the same as those of the "
                        "input dataset. Specifying the units here will allow "
                        "a suitable conversion to match the input units if "
                        "possible.")
    parser.add_argument("--comparison_operator",
                        metavar="COMPARISON_OPERATOR",
                        default='>',
                        choices=['>', '>=', '<', '<=', 'gt', 'ge', 'lt', 'le'],
                        help="Indicates the comparison_operator to use with "
                        "the threshold. e.g. 'ge' or '>=' to evaluate data "
                        ">= threshold or '<' to evaluate data < threshold. "
                        "When using fuzzy thresholds, there is no difference "
                        "between < and <= or > and >=."
                        "Default is >. Valid choices: > >= < <= gt ge lt le.")
    parser.add_argument("--fuzzy_factor",
                        metavar="FUZZY_FACTOR",
                        default=None,
                        type=float,
                        help="A decimal fraction defining the factor about "
                        "the threshold value(s) which should be treated as "
                        "fuzzy. Data which fail a test against the hard "
                        "threshold value may return a fractional truth value "
                        "if they fall within this fuzzy factor region. Fuzzy "
                        "factor must be in the range 0-1, with higher values "
                        "indicating a narrower fuzzy factor region / sharper "
                        "threshold. NB A fuzzy factor cannot be used with a "
                        "zero threshold or a threshold_config file.")
    parser.add_argument("--collapse-coord",
                        type=str,
                        metavar="COLLAPSE-COORD",
                        default="None",
                        help="An optional ability to set which coordinate "
                        "we want to collapse over. The default is set "
                        "to None.")
    parser.add_argument("--vicinity",
                        type=float,
                        default=None,
                        help="If set,"
                        " distance in metres used to define the vicinity "
                        "within which to search for an occurrence.")

    args = parser.parse_args(args=argv)

    # Deal with mutual-exclusions that ArgumentParser can't handle:
    if args.threshold_values and args.threshold_config:
        raise parser.error("--threshold_config option is not compatible "
                           "with THRESHOLD_VALUES list.")
    if args.fuzzy_factor and args.threshold_config:
        raise parser.error("--threshold_config option is not compatible "
                           "with --fuzzy_factor option.")

    # Load Cube
    cube = load_cube(args.input_filepath)
    threshold_dict = None
    if args.threshold_config:
        try:
            # Read in threshold configuration from JSON file.
            with open(args.threshold_config, 'r') as input_file:
                threshold_dict = json.load(input_file)
        except ValueError as err:
            # Extend error message with hint for common JSON error.
            raise type(err)("{} in JSON file {}. \nHINT: Try "
                            "adding a zero after the decimal point.".format(
                                err, args.threshold_config))
        except Exception as err:
            # Extend any errors with message about WHERE this occurred.
            raise type(err)("{} in JSON file {}".format(
                err, args.threshold_config))

    # Process Cube
    result = process(cube, args.threshold_values, threshold_dict,
                     args.threshold_units, args.comparison_operator,
                     args.fuzzy_factor, args.collapse_coord, args.vicinity)
    # Save Cube
    save_netcdf(result, args.output_filepath)
Пример #4
0
def main(argv=None):
    """Load in arguments and ensure they are set correctly.
       Then load in the data to blend and calculate default weights
       using the method chosen before carrying out the blending."""
    parser = ArgParser(
        description='Calculate the default weights to apply in weighted '
        'blending plugins using the ChooseDefaultWeightsLinear or '
        'ChooseDefaultWeightsNonLinear plugins. Then apply these '
        'weights to the dataset using the BasicWeightedAverage plugin.'
        ' Required for ChooseDefaultWeightsLinear: y0val and ynval.'
        ' Required for ChooseDefaultWeightsNonLinear: cval.'
        ' Required for ChooseWeightsLinear with dict: wts_dict.')

    parser.add_argument('--wts_calc_method',
                        metavar='WEIGHTS_CALCULATION_METHOD',
                        choices=['linear', 'nonlinear', 'dict'],
                        default='linear',
                        help='Method to use to calculate '
                        'weights used in blending. "linear" (default): '
                        'calculate linearly varying blending weights. '
                        '"nonlinear": calculate blending weights that decrease'
                        ' exponentially with increasing blending coordinate. '
                        '"dict": calculate weights using a dictionary passed '
                        'in as a command line argument.')

    parser.add_argument('coordinate',
                        type=str,
                        metavar='COORDINATE_TO_AVERAGE_OVER',
                        help='The coordinate over which the blending '
                        'will be applied.')
    parser.add_argument('--cycletime',
                        metavar='CYCLETIME',
                        type=str,
                        help='The forecast reference time to be used after '
                        'blending has been applied, in the format '
                        'YYYYMMDDTHHMMZ. If not provided, the blended file '
                        'will take the latest available forecast reference '
                        'time from the input cubes supplied.')
    parser.add_argument('--model_id_attr',
                        metavar='MODEL_ID_ATTR',
                        type=str,
                        default=None,
                        help='The name of the netCDF file attribute to be '
                        'used to identify the source model for '
                        'multi-model blends. Default is None. '
                        'Must be present on all input '
                        'files if blending over models.')
    parser.add_argument('--spatial_weights_from_mask',
                        action='store_true',
                        default=False,
                        help='If set this option will result in the generation'
                        ' of spatially varying weights based on the'
                        ' masks of the data we are blending. The'
                        ' one dimensional weights are first calculated '
                        ' using the chosen weights calculation method,'
                        ' but the weights will then be adjusted spatially'
                        ' based on where there is masked data in the data'
                        ' we are blending. The spatial weights are'
                        ' calculated using the'
                        ' SpatiallyVaryingWeightsFromMask plugin.')

    parser.add_argument('input_filepaths',
                        metavar='INPUT_FILES',
                        nargs="+",
                        help='Paths to input files to be blended.')
    parser.add_argument('output_filepath',
                        metavar='OUTPUT_FILE',
                        help='The output path for the processed NetCDF.')

    spatial = parser.add_argument_group(
        'Spatial weights from mask options',
        'Options for calculating the spatial weights using the '
        'SpatiallyVaryingWeightsFromMask plugin.')
    spatial.add_argument('--fuzzy_length',
                         metavar='FUZZY_LENGTH',
                         type=float,
                         default=20000,
                         help='When calculating spatially varying weights we'
                         ' can smooth the weights so that areas close to'
                         ' areas that are masked have lower weights than'
                         ' those further away. This fuzzy length controls'
                         ' the scale over which the weights are smoothed.'
                         ' The fuzzy length is in terms of m, the'
                         ' default is 20km. This distance is then'
                         ' converted into a number of grid squares,'
                         ' which does not have to be an integer. Assumes'
                         ' the grid spacing is the same in the x and y'
                         ' directions, and raises an error if this is not'
                         ' true. See SpatiallyVaryingWeightsFromMask for'
                         ' more detail.')

    linear = parser.add_argument_group(
        'linear weights options', 'Options for the linear weights '
        'calculation in '
        'ChooseDefaultWeightsLinear')
    linear.add_argument('--y0val',
                        metavar='LINEAR_STARTING_POINT',
                        type=float,
                        help='The relative value of the weighting start point '
                        '(lowest value of blend coord) for choosing default '
                        'linear weights. This must be a positive float or 0.')
    linear.add_argument('--ynval',
                        metavar='LINEAR_END_POINT',
                        type=float,
                        help='The relative value of the weighting '
                        'end point (highest value of blend coord) for choosing'
                        ' default linear weights. This must be a positive '
                        'float or 0.  Note that if blending over forecast '
                        'reference time, ynval >= y0val would normally be '
                        'expected (to give greater weight to the more recent '
                        'forecast).')

    nonlinear = parser.add_argument_group(
        'nonlinear weights options', 'Options for the non-linear '
        'weights calculation in '
        'ChooseDefaultWeightsNonLinear')
    nonlinear.add_argument('--cval',
                           metavar='NON_LINEAR_FACTOR',
                           type=float,
                           help='Factor used to determine how skewed the '
                           'non linear weights will be. A value of 1 '
                           'implies equal weighting.')

    wts_dict = parser.add_argument_group(
        'dict weights options', 'Options for linear weights to be '
        'calculated based on parameters '
        'read from a json file dict')
    wts_dict.add_argument('--wts_dict',
                          metavar='WEIGHTS_DICTIONARY',
                          help='Path to json file containing dictionary from '
                          'which to calculate blending weights. Dictionary '
                          'format is as specified in the improver.blending.'
                          'weights.ChooseWeightsLinear plugin.')
    wts_dict.add_argument('--weighting_coord',
                          metavar='WEIGHTING_COORD',
                          default='forecast_period',
                          help='Name of '
                          'coordinate over which linear weights should be '
                          'scaled. This coordinate must be available in the '
                          'weights dictionary.')

    args = parser.parse_args(args=argv)

    # reject incorrect argument combinations
    if (args.wts_calc_method == "linear") and args.cval:
        parser.wrong_args_error('cval', 'linear')
    if ((args.wts_calc_method == "nonlinear")
            and np.any([args.y0val, args.ynval])):
        parser.wrong_args_error('y0val, ynval', 'non-linear')

    if (args.wts_calc_method == "dict") and not args.wts_dict:
        parser.error('Dictionary is required if --wts_calc_method="dict"')

    weights_dict = load_json_or_none(args.wts_dict)

    # Load cubes to be blended.
    cubelist = load_cubelist(args.input_filepaths)

    result = process(cubelist, args.wts_calc_method, args.coordinate,
                     args.cycletime, args.weighting_coord, weights_dict,
                     args.y0val, args.ynval, args.cval, args.model_id_attr,
                     args.spatial_weights_from_mask, args.fuzzy_length)

    save_netcdf(result, args.output_filepath)
Пример #5
0
def main(argv=None):
    """Load in arguments and get going."""
    parser = ArgParser(
        description="Calculate the threshold truth value of input data "
        "relative to the provided threshold value. By default data are "
        "tested to be above the thresholds, though the --below_threshold "
        "flag enables testing below thresholds. A fuzzy factor or fuzzy "
        "bounds may be provided to capture data that is close to the "
        "threshold.")
    parser.add_argument("input_filepath",
                        metavar="INPUT_FILE",
                        help="A path to an input NetCDF file to be processed")
    parser.add_argument("output_filepath",
                        metavar="OUTPUT_FILE",
                        help="The output path for the processed NetCDF")
    parser.add_argument("threshold_values",
                        metavar="THRESHOLD_VALUES",
                        nargs="*",
                        type=float,
                        help="Threshold value or values about which to "
                        "calculate the truth values; e.g. 270 300. "
                        "Must be omitted if --threshold_config is used.")
    parser.add_argument("--threshold_config",
                        metavar="THRESHOLD_CONFIG",
                        type=str,
                        help="Threshold configuration JSON file containing "
                        "thresholds and (optionally) fuzzy bounds. Best used "
                        "in combination  with --threshold_units. "
                        "It should contain a dictionary of strings that can "
                        "be interpreted as floats with the structure: "
                        " \"THRESHOLD_VALUE\": [LOWER_BOUND, UPPER_BOUND] "
                        "e.g: {\"280.0\": [278.0, 282.0], "
                        "\"290.0\": [288.0, 292.0]}, or with structure "
                        " \"THRESHOLD_VALUE\": \"None\" (no fuzzy bounds). "
                        "Repeated thresholds with different bounds are not "
                        "handled well. Only the last duplicate will be used.")
    parser.add_argument("--threshold_units",
                        metavar="THRESHOLD_UNITS",
                        default=None,
                        type=str,
                        help="Units of the threshold values. If not provided "
                        "the units are assumed to be the same as those of the "
                        "input dataset. Specifying the units here will allow "
                        "a suitable conversion to match the input units if "
                        "possible.")
    parser.add_argument("--below_threshold",
                        default=False,
                        action='store_true',
                        help="By default truth values of 1 are returned for "
                        "data ABOVE the threshold value(s). Using this flag "
                        "changes this behaviour to return 1 for data below "
                        "the threshold values.")
    parser.add_argument("--fuzzy_factor",
                        metavar="FUZZY_FACTOR",
                        default=None,
                        type=float,
                        help="A decimal fraction defining the factor about "
                        "the threshold value(s) which should be treated as "
                        "fuzzy. Data which fail a test against the hard "
                        "threshold value may return a fractional truth value "
                        "if they fall within this fuzzy factor region. Fuzzy "
                        "factor must be in the range 0-1, with higher values "
                        "indicating a narrower fuzzy factor region / sharper "
                        "threshold. NB A fuzzy factor cannot be used with a "
                        "zero threshold or a threshold_config file.")
    parser.add_argument("--collapse-coord",
                        type=str,
                        metavar="COLLAPSE-COORD",
                        default="None",
                        help="An optional ability to set which coordinate "
                        "we want to collapse over. The default is set "
                        "to None.")
    parser.add_argument("--vicinity",
                        type=float,
                        default=None,
                        help="If set,"
                        " distance in metres used to define the vicinity "
                        "within which to search for an occurrence.")

    args = parser.parse_args(args=argv)

    # Deal with mutual-exclusions that ArgumentParser can't handle:
    if args.threshold_values and args.threshold_config:
        raise parser.error("--threshold_config option is not compatible "
                           "with THRESHOLD_VALUES list.")
    if args.fuzzy_factor and args.threshold_config:
        raise parser.error("--threshold_config option is not compatible "
                           "with --fuzzy_factor option.")

    cube = load_cube(args.input_filepath)

    if args.threshold_config:
        try:
            # Read in threshold configuration from JSON file.
            with open(args.threshold_config, 'r') as input_file:
                thresholds_from_file = json.load(input_file)
            thresholds = []
            fuzzy_bounds = []
            is_fuzzy = True
            for key in thresholds_from_file.keys():
                thresholds.append(float(key))
                if is_fuzzy:
                    # If the first threshold has no bounds, fuzzy_bounds is
                    # set to None and subsequent bounds checks are skipped
                    if thresholds_from_file[key] == "None":
                        is_fuzzy = False
                        fuzzy_bounds = None
                    else:
                        fuzzy_bounds.append(tuple(thresholds_from_file[key]))
        except ValueError as err:
            # Extend error message with hint for common JSON error.
            raise type(err)(err + " in JSON file {}. \nHINT: Try "
                            "adding a zero after the decimal point.".format(
                                args.threshold_config))
        except Exception as err:
            # Extend any errors with message about WHERE this occurred.
            raise type(err)(err +
                            " in JSON file {}".format(args.threshold_config))
    else:
        thresholds = args.threshold_values
        fuzzy_bounds = None

    result_no_collapse_coord = BasicThreshold(
        thresholds,
        fuzzy_factor=args.fuzzy_factor,
        fuzzy_bounds=fuzzy_bounds,
        threshold_units=args.threshold_units,
        below_thresh_ok=args.below_threshold).process(cube)

    if args.vicinity is not None:
        # smooth thresholded occurrences over local vicinity
        result_no_collapse_coord = OccurrenceWithinVicinity(
            args.vicinity).process(result_no_collapse_coord)

        new_cube_name = in_vicinity_name_format(
            result_no_collapse_coord.name())

        result_no_collapse_coord.rename(new_cube_name)

    if args.collapse_coord == "None":
        save_netcdf(result_no_collapse_coord, args.output_filepath)
    else:
        # Raise warning if result_no_collapse_coord is masked array
        if np.ma.isMaskedArray(result_no_collapse_coord.data):
            warnings.warn("Collapse-coord option not fully tested with "
                          "masked data.")
        # This is where we fix values for y0val, slope and weighting_mode.
        # In this case they are fixed to the values required for realization
        # collapse. This can be changed if other functionality needs to be
        # implemented.
        weights = ChooseDefaultWeightsLinear(y0val=1.0, slope=0.0).process(
            result_no_collapse_coord, args.collapse_coord)

        BlendingPlugin = WeightedBlendAcrossWholeDimension(
            args.collapse_coord, weighting_mode='weighted_mean')
        result_collapse_coord = BlendingPlugin.process(
            result_no_collapse_coord, weights)

        save_netcdf(result_collapse_coord, args.output_filepath)