Source code for ants.analysis.cover_mapping
# (C) Crown Copyright, Met Office. All rights reserved.
#
# This file is part of ANTS and is released under the BSD 3-Clause license.
# See LICENSE.txt in the root of the repository for full licensing details.
import warnings
import ants
import numpy as np
[docs]
def fetch_lct_slices(source, um_tile_ids):
"""
Fetch the slices corresponding to the specified JULE tile ID.
Given an iris cube, derive a set of slices for this cube corresponding to
this JULE tile ID. That is, indexing the pseudo_level mapped dimension.
Parameters
----------
source : :class:`~iris.cube.Cube`
Source cube which has land cover types as defined by a pseudo level
coordinate.
um_tile_ids : int | list[int]
JULES tile IDs. This corresponds to the pseudo_level value(s) desired.
Returns
-------
tuple
A tuple containing slice objects.
Example
-------
>>> c3_grass = 3
>>> c3_grass_slices = fetch_veg_slice(cube, c3_grass)
>>> c3_grass.data[c3_grass] = ...
"""
um_tile_ids_iter = um_tile_ids
if not hasattr(um_tile_ids, "__iter__"):
um_tile_ids_iter = [um_tile_ids]
pseudo_level = source.coord("pseudo_level")
pseudo_level_points = pseudo_level.points.tolist()
index = [pseudo_level_points.index(um_tile_id) for um_tile_id in um_tile_ids_iter]
# iris requires tuples...
index = tuple(index)
if not hasattr(um_tile_ids, "__iter__"):
# If we supply a single index we don't want to return an iterable slice
# as that means that the object being sliced will have an extra
# dimension which the user would not expect.
index = index[0]
slices = [slice(None)] * source.ndim
ps_dim = source.coord_dims(pseudo_level)
if len(ps_dim) != 1:
msg = (
"Expecting 1D pseudo level coordinate describing JULES "
"classes, got {}D coord."
).format(len(ps_dim))
raise RuntimeError(msg)
slices[source.coord_dims(pseudo_level)[0]] = index
return tuple(slices)
[docs]
def normalise_fractions(source):
"""
Normalisation of fractions, ensuring the sum of the fractions is 1.
Normalisation effectively works by filling missing data by dividing equally
amongst all types where data > 0 such that the ratios between fractions
remain the same. Where there are no class fractions at a given point, it
will remain 0. Fractions outside the range [0, 1] are considered anomalous
and pulled into that range before normalisation occurs.
Parameters
----------
source : :class:`iris.cube.Cube`
Source land cover type fraction, with pseudo-level coordinate
representing the classes.
Warning
-------
Mask is not altered by this function.
"""
# Ensure fractions are within a suitable range
# Ignore numpy invalid value warnings due to presence of nans which we
# expect after a regrid.
with np.errstate(invalid="ignore"):
source.data[source.data < 0] = 0
source.data[source.data > 1] = 1
pseudo_level = source.coord("pseudo_level")
if pseudo_level.ndim != 1:
msg = "Expecting a 1D pseudo_level coordinate not {}D."
raise RuntimeError(msg.format(pseudo_level.ndim))
pdim = source.coord_dims(pseudo_level)[0]
transpose_indx = [pdim] + [x for x in range(source.ndim) if x != pdim]
data = np.asarray(ants.utils.ndarray.transposed_view(source.data, transpose_indx))
with np.errstate(invalid="ignore"):
non_zero = (data > 0).sum(axis=0) > 0
if not non_zero.all():
warnings.warn(
"Locations present with no classification fraction, "
"ignoring such locations."
)
non_zero_data = data[..., non_zero]
source_sum = non_zero_data.sum(axis=0)
adjustments = 1.0 - source_sum
data[..., non_zero] = (
adjustments / (1 - adjustments) * non_zero_data
) + non_zero_data