Source code for vacumm.misc.grid.regridding

# -*- coding: utf8 -*-
"""Regridding utilities

.. seealso::

    Tutorials: :ref:`user.tut.misc.grid.regridding`
"""
# Copyright or © or Copr. Actimar/IFREMER (2010-2018)
#
# This software is a computer program whose purpose is to provide
# utilities for handling oceanographic and atmospheric data,
# with the ultimate goal of validating the MARS model from IFREMER.
#
# This software is governed by the CeCILL license under French law and
# abiding by the rules of distribution of free software.  You can  use,
# modify and/ or redistribute the software under the terms of the CeCILL
# license as circulated by CEA, CNRS and INRIA at the following URL
# "http://www.cecill.info".
#
# As a counterpart to the access to the source code and  rights to copy,
# modify and redistribute granted by the license, users are provided only
# with a limited warranty  and the software's author,  the holder of the
# economic rights,  and the successive licensors  have only  limited
# liability.
#
# In this respect, the user's attention is drawn to the risks associated
# with loading,  using,  modifying and/or developing or reproducing the
# software by the user in light of its specific status of free software,
# that may mean  that it is complicated to manipulate,  and  that  also
# therefore means  that it is reserved for developers  and  experienced
# professionals having in-depth computer knowledge. Users are therefore
# encouraged to load and test the software's suitability as regards their
# requirements in conditions enabling the security of their systems and/or
# data to be ensured and,  more generally, to use and operate it in the
# same conditions as regards security.
#
# The fact that you are presently reading this means that you have had
# knowledge of the CeCILL license and that you accept its terms.
#
import gc, os, subprocess
import re
import warnings
from collections import OrderedDict
import tempfile, shutil
from copy import deepcopy

import numpy as N, cdms2,  MV2,  regrid2
from cdms2.axis import TransientAxis
import cdtime
import genutil
from _geoslib import Point, Polygon

from ...__init__ import VACUMMError, VACUMMWarning, vacumm_warn, vcwarn
from .kriging import krig as _krig_
from .misc import (axis1d_from_bounds, get_xy, isgrid, t2uvgrids, get_grid,
    set_grid, bounds1d, bounds2d, get_axis,
    meshgrid, create_grid, resol, meshcells, curv2rect, merge_axis_slice,
    get_axis_slices, get_axis, transect_specs, create_axes2d,
    get_distances,
    get_tri, get_grid_type)
from .. import axes as A
from ...misc.misc import (cp_atts, intersect, kwfilter, get_atts, set_atts, closeto,
    splitidx, MV2_concatenate)
from ...misc.atime import are_same_units, ch_units, time_split, lindates
from .basemap import get_proj

MV=MV2
cdms=cdms2

# Python functions
__all__ = ['fill1d', 'regular', 'regular_fill1d', 'cellave1d', 'spline_interp1d',
    'refine', 'GridData', 'griddata', 'cargen', 'fill2d', 'regrid2d',
    'regrid1d', 'interp1d', 'nearest1d', 'cubic1d', 'regrid2dold',
    'xy2grid', 'grid2xy', 'fill1d', 'GriddedMerger', 'regrid_method',
    'cellave2d', 'interp2d', 'xy2xy', 'shift1d', 'shift2d',
    'shiftgrid',  'transect', 'CDATRegridder', 'extend1d', 'extend2d',
    'extendgrid', 'regrid2d_method_name', 'fill1d2', 'krig', 'CurvedInterpolator',
    'regrid1dold', 'regrid2d_tool_name', 'regrid2dnew', 'regrid1d_method_name',
    'cellerr1d']
__all__.sort()

# Fortran functions
_interp_funcs = ['interp1d', 'interp1dx', 'interp1dxx',
    'remap1d', 'remap1dx', 'remap1dxx', 'nearest2d', 'bilin', 'dstwgt',
    'mbilin2d', 'mixt2dx', 'cargen', 'extrap1d', 'curv2rel',
    'nearest2dto1d', 'nearest2dto1dc', 'nearest2dto1dc_reduc',
    'bilin2dto1d', 'bilin2dto1dc', 'bilin2dto1dc_reduc',
    'dstwgt2dto1d', 'dstwgt2dto1dc', 'dstwgt2dto1dc_reduc',
    'cellerr1d', 'cellerr1dx', 'cellerr1dxx', 'linear4dto1dxx',
    'nearest4dto1dxx',
    ]

# Load fortran
_interp_funcs = ['%s as _%s_'%(ff, ff) for ff in _interp_funcs]
_interp_funcs = ', '.join(_interp_funcs)
import_interp = "from _interp_ import %s" % (_interp_funcs, )
try:
    exec  import_interp
except Exception, e:
    print e
    print 'Trying to build it...'
    import subprocess
    cmd = ["make"] # Compilation of all vacumm extensions from the root of sources
    out = subprocess.Popen(cmd, cwd=os.path.dirname(__file__), stdout=subprocess.PIPE,
        stderr=subprocess.PIPE).communicate()
    if out[1]!='':
        raise ImportError("Can't build _interp_ for importation:\n%s"%('\n'.join(out)))
    exec import_interp


# Interpolation methods
_griddata_methods = ['nearest', 'linear', 'cubic', 'krig', 'carg']
_cellave_methods = ['conservative', 'remap', 'cellave', 'conserv']
_cdat_methods = _cellave_methods+['bilinear', 'patch']
_regrid2d_methods = ['nearest', 'mixt', 'interp', 'bining'] + _cdat_methods
_interp1d_methods = ['nearest', 'linear', 'cubic', 'hermit']
_regrid1d_methods = _interp1d_methods+_cellave_methods+['cellerr']

[docs]def regrid1d_method_name(method, raiseerr=True): """Check the method name and return its generic name""" if method is None or method.lower()=='auto': return 'auto' method = method.lower() if method.startswith('cons'): return 'conserv' if method.startswith('cella') or method.startswith('remap'): return 'cellave' if method.startswith('celle'): return 'cellerr' if 'lin' in method or method=='interp': return 'linear' # if method.startswith('bin'): return 'bining' if method.startswith('near'): return 'nearest' if method.startswith('cub'): return 'cubic' if method.startswith('her'): return 'hermit' if raiseerr: raise VACUMMError('Invalid regrid1d method. Please use for example one of these: '+ ', '.join(_regrid1d_methods)) else: return method
[docs]def regrid1dold(vari, axo, method='auto', axis=None, xmap=None, xmapper=None, mask_thres=.5, extrap=0): """Interpolation along one axis :Params: - **vari**: Input cdms array. - **axo**: Output cdms axis. - **method**: - ``"nearest"``: Nearest neighbor - ``"linear"``: Linear interpolation - ``"cubic"``: Cubic interpolation - ``"cellave"``: Cell averaging - ``"conserv"``: Conservative cel averaging (like ``cellave`` but with integral preserved) - **axis**, optional: Axis (int) on which to operate. If not specified, it is guessed from the input and output axis types, or set to ``0``. - **xmap**, optional: Integer or tuple that specify on which axes input axis is varying. - **xmapper**, optional: Array that specify values of input axis along axes specified by ``xmap``. It is an array of size ``(...,len(var.getAxis(xmap[-2])), len(var.getAxis(xmap[-1])), len(var.getAxis(axis))]``. - **mask_thres**, optional: Time steps when interpolated mask is greater than this value are masked. - **extrap**, optional: Extrapolate outside input grid when the "nearest" method is used: - ``0`` or ``False``: No extrapolation. - ``-1`` or ``"min"``, or ``"bottom"``, or ``"lower"``, or ``"first"``: Extrapolate toward first values of the axis. - ``1`` or ``"max"``, or ``"top"``, or ``"upper"``, or ``"last"``: Extrapolate toward last values of the axis. - ``2`` or ``"both"``: Extrapolate toward both first and last values. .. note:: Cubic method, use "linear" interpolation when less than 4 valid points are available. Linear interpolation uses "nearest" interpolation when less than 2 points are available. """ # Input specs assert cdms.isVariable(vari), 'Works only with cdms variables' A.check_axes(vari) order = vari.getOrder() axes = vari.getAxisList() grid = vari.getGrid() missing_value = vari.getMissing() if missing_value is None: vari.setMissing(1.e20) missing_value = vari.getMissing() # On which axis? if not A.isaxis(axo): axo = N.asarray(axo) if axo.ndim == 2: axo = cdms.createAxis(axo) # if A.isaxis(axo): A.check_axis(axo) if axis is None:# Guess it if A.isaxis(axo): # From axis type axis = order.find(A.axis_type(axo)) else: # From axis length try: axis = list(vari.shape).index(axo.shape[0]) except ValueError: axis = -1 # Not found if axis == -1: axis = 0 elif axis == -1: # Last axis axis = vari.ndim-1 else: # Failed assert axis >= 0 and axis < vari.ndim, 'Wrong axis' if axis in [vari.ndim-2, vari.ndim-1]: grid = None # Method if method == 'auto' or method == None: method = regrid_method(vari.getAxis(axis), axo) method = regrid1d_method_name(method) if method in _cellave_methods: conserv = method=='conserv' # conserv = method == 'conservative' method = -1 elif isinstance(method, basestring): if method=='cellerr': method = 4 elif method in _interp1d_methods: method = _interp1d_methods.index(method) else: method = -2 # Wrong method else: method = int(method) assert method >= -1 and method < 5, 'Wrong method' if method == 2: method = 3 # Hermit is always better if xmap is None: interp_func = _interp1d_ else: interp_func = _interp1dx_ if method == -1: if xmap is None: regrid_func = _remap1d_ else: regrid_func = _remap1dx_ args = [conserv] else: regrid_func = interp_func args = [method] # Extrapolation if isinstance(extrap, basestring): extrap = extrap.lower() if extrap in ['both', 'all']: extrap = 2 elif extrap in ['bottom', 'min', 'lower', 'first']: extrap = -1 elif extrap in ['top', 'max', 'upper', 'last']: extrap = 1 else: extrap = 0 elif not isinstance(extrap, int): extrap = int(bool(extrap)) # Convert to numeric varin = vari.filled() # Translate axes if xmap is None: # Simple case if axis != vari.ndim-1: varis = varin.swapaxes(axis, -1) del varin else: varis = varin else: # Extended case # Check xmap form if isinstance(xmap, int): xmap = [xmap] else: xmap = list(xmap) for ix in xrange(len(xmap)): if xmap[ix] == -1: xmap[ix] = vari.ndim-1 else: assert xmap[ix] >= 0 and xmap[ix] < vari.ndim, 'Wrong xmap axis' # Roll axes oldmap = range(varin.ndim) newmap = xmap+[axis] for iax in oldmap[::-1]: if iax not in newmap: newmap = [iax]+newmap if oldmap != newmap: # backmap = [oldmap.index(iax) for iax in newmap] backmap = [newmap.index(iax) for iax in oldmap] varis = varin.transpose(*newmap) del varin else: backmap = None varis = varin # Size of extension block nxb = N.multiply.reduce(varis.shape[:-1]) xshape = (nxb, varis.shape[-1]) nyi = varis.shape[-1] # Axes content if xmap is None: # Simple case # Input axis axi = axes[axis] # Special case for time axis: must have same units ! if (A.axis_type(axo), order[axis]) == ('t', 't') and \ hasattr(axi, 'units') and hasattr(axo, 'units') and \ not are_same_units(axi.units, axo.units): axi = ch_units(axi, axo.units, copy=True) else: # Extended case axi = xmapper.reshape(xshape, order='F') # Reshape var to get a 2D array if varis.ndim != 2: vari2d = varis.reshape(varis.size/nyi, nyi, order='F') else: vari2d = varis # First guess varo2d = regrid_func(vari2d, axi[:], axo[:], missing_value, *args) # Mask maski2d = vari2d==missing_value if method and N.any(maski2d): # Float mask maski2df = maski2d.astype('f') masko2d = regrid_func(maski2df, axi[:], axo[:], 1.e20, *args) masko2d[masko2d==1.e20] = 1. # Masking if method == -1: # Cell case: threshold varo2d[:] = N.where(masko2d>mask_thres, missing_value, varo2d) else: # Lower method varolow = interp_func(vari2d, axi[:], axo[:], missing_value, min(abs(method), 2)-1, extrap=extrap) # Cubic case: lower order again if method >= 2: masko2dc = interp_func(maski2df, axi[:], axo[:], 1.e20, 1) masko2dc[masko2dc==1.e20] = 1. varolowc = interp_func(vari2d, axi[:], axo[:], missing_value, 0) varolow[:] = N.where(masko2dc!=0., varolowc, varolow) del masko2dc, varolowc # Select between nearest and linear, or linear and cubic varo2d[:] = N.where(masko2d!=0., varolow, varo2d) del varolow del maski2df, masko2d # Reshape back if varis.ndim != 2: varos = varo2d.reshape(varis.shape[:-1]+(len(axo), ), order='F') del varo2d, vari2d else: varos = varo2d del varis # Retranslate axes back if xmap is not None and backmap is not None: varon = varos.transpose(*backmap) del varos elif axis != vari.ndim-1: varon = varos.swapaxes(-1, axis) del varos else: varon = varos # Convert to cdms varo = MV.masked_values(varon, missing_value) cp_atts(vari, varo, id=True) varo.setMissing(missing_value) axes[axis] = axo varo.setAxisList(axes) varo.setGrid(grid) gc.collect() return varo
def _subshape_(bigshape, subshape, axis=None): """Get the index of a unique of occurence of subshape in bigshape or None :Params: - **bigshape**: Tuple or big array. - **subshape**: Tuple or smaller array. - **axis**: Axis index of bigshape that is allowed to differ in subshape. """ if hasattr(bigshape, 'shape'): bigshape = bigshape.shape if hasattr(subshape, 'shape'): subshape = subshape.shape bigshape = list(bigshape) subshape = list(subshape) nb = len(bigshape) ns = len(subshape) assert nb>=ns # TODO: make it more generic istart = None for i in xrange(nb-ns+1): subbigshape = bigshape[i:i+ns] if axis is not None and axis>=i and axis<i+ns: i0 = axis-i subbigshape[i0] = subshape[i0] if subbigshape == subshape: if istart is not None: return # two solutions => no valid solution istart = i return istart def _getiax_(vari, ax, axis): """Guess the index of the target axis of vari in the ax array""" # 1D case if ax.ndim==1: return 0 # A unique sub-tuple iax = _subshape_(vari, ax, axis) if iax is not None: iax = axis - iax if iax>=0: return iax iax = None # A unique axis type if cdms2.isVariable(ax): l = A.axis_type(vari.getAxis(axis)) if l!='-' and l in ax.getOrder(): iax = ax.getOrder().index(l) return iax def _syncshapes_(axi, iaxi, axo, iaxo): """Resize two arrays to have the same shapes except when the second one is 1D :Params: - **axi**: First numpy array. - **iaxi**: Index of pivot (target axis) in first array. - **axo**: Second numpy array. - **iaxo**: Index of pivot (target axis) in second array. """ # Nothing to resize if axo.ndim==1: return axi, iaxi, axo, iaxo # Pivot as a reference nil = iaxi # left, first nir = axi.ndim-iaxi-1 # right, second nol = iaxo # left, first nor = axo.ndim-iaxo-1 # right, second # Right adjusment if nir>nor: # expand the second to the right for ir in xrange(nir-nor): axo = axo.reshape(axo.shape+(1,)) axo = N.repeat(axo, axi.shape[iaxi+nor+ir+1], axis=-1) elif nor>nir: # expand the first to the right for ir in xrange(nor-nir): axi = axi.reshape(axi.shape+(1,)) axi = N.repeat(axi, axo.shape[iaxo+nir+ir+1], axis=-1) # Left adjustment if nil>nol: # expand the second to the left axo = N.resize(axo, axi.shape[:nil-nol]+axo.shape) iaxo += nil-nol elif nol>nil: axi = N.resize(axi, axo.shape[:nol-nil]+axi.shape) iaxi += nol-nil return axi, iaxi, axo, iaxo def _toright_(ar, iax): """Reform an array so the iax dim becomes the last :Params: - **ar**: A numpy array. - **iax**: Index of the dim. :Return: ``newar,bakmap`` such as:: newar.shape[-1] == ar.shape[iax] ar == newarr.translate(*bakmap) """ oldmap = range(ar.ndim) newmap = list(oldmap) newmap.remove(iax) newmap.append(iax) bakmap = [newmap.index(iax) for iax in oldmap] newar = ar.transpose(*newmap) return newar, bakmap
[docs]def regrid1d(vari, axo, method='auto', axis=None, axi=None, iaxo=None, iaxi=None, xmap=None, xmapper=None, mask_thres=.5, extrap=0, erri=None, errl=None, geterr=False): """Interpolation along one axis :Params: - **vari**: Input cdms array. - **axo**: Output cdms axis or array. It can be of any dimensions. - **method**: - ``"nearest"|0``: Nearest neighbor - ``"linear"|2``: Linear interpolation - ``"cubic"|2``: Cubic interpolation (not used, switched to ``3``) - ``"hermit"|3``: Cubic hermit interpolation - ``"cellerr"|4``: Cell averaging based on errors - ``"cellave"|-1``: Cell averaging - ``"conserv"|-1``: Conservative cel averaging (like ``cellave`` but with integral preserved) - **axis**, optional: Dimension (int) on which the interpolation is performed. If not specified, it is guessed from the input and output axis types, or set to ``0``. - **axi**, optional: Input axis. It defaults to the axis-th axis of ``vari``. Like ``axo``, it can be of any dimensions. - **iaxo**, optional: Dimension of ``axo`` on which the interpolation is performed when ``axo`` has more than one dimension. - **iaxi**, optional: Same as ``iaxo`` but for ``axi``. - **mask_thres**, optional: Time steps when interpolated mask is greater than this value are masked. - **extrap**, optional: Extrapolate outside input grid when the "nearest" method is used: - ``0`` or ``False``: No extrapolation. - ``-1`` or ``"min"``, or ``"bottom"``, or ``"lower"``, or ``"first"``: Extrapolate toward first values of the axis. - ``1`` or ``"max"``, or ``"top"``, or ``"upper"``, or ``"last"``: Extrapolate toward last values of the axis. - ``2`` or ``"both"``: Extrapolate toward both first and last values. - **erri**, optional: Input "measurement" errors with the same shape as input variable. - **errl**, optional: Derivative of lag error with respect to lag. Note that the lag must expressed in **days** for time axes. If positive, it based on quadratic errors, else on error itself. Estimate for instance it using the slope of a linear regression. It is usually varying in space and constant in time. - **geterr**, optional: When method is "cellerr", also return the error along with the variable. :Examples: >>> varo = regrid1d(vari, taxis, method='linear') # interpolation in time >>> varo = regrid1d(vari, zo, axis=1) # Z interpolation on second axis >>> varo = regrid1d(vari, zzo, iaxo=1, axi=zzi, iaxi=1) # sigma to sigma .. note:: Cubic method, use "linear" interpolation when less than 4 valid points are available. Linear interpolation uses "nearest" interpolation when less than 2 points are available. """ # Input specs assert cdms.isVariable(vari), 'Works only with cdms variables' if xmap is not None or xmapper is not None: warnings.warn('xmap and xmapper keywords of regrid1d are deprecated. ' 'Please use axi/axo and iaxi/iaxo keywords instead.', VACUMMWarning) A.check_axes(vari) order = vari.getOrder() axes = vari.getAxisList() grid = vari.getGrid() missing_value = vari.getMissing() if missing_value is None or N.isnan(missing_value): vari.setMissing(1.e20) missing_value = vari.getMissing() # Output axis axon = axo[:] if N.ma.isMA(axon): axon = axon.filled(missing_value) # Working data axis if vari.ndim==1: # 1D axis = 0 elif axis is None:# Guess it if A.isaxis(axo): # From axis type axis = order.find(A.axis_type(axo)) elif axon.ndim==1: # From axis length axis = _subshape_(vari, axo) if axis is None: raise VACUMMError('Please, specify the "axis" parameter for interpolation') # Not found, so 0 without verification now (later) if axis == -1 or axis is None: axis = 0 else: if axis<0: axis += vari.ndim if axis < 0 or axis >= vari.ndim: raise VACUMMError('Wrong "axis" parameter for interpolation') if axis in [vari.ndim-2, vari.ndim-1]: grid = None # Input axis # - get it if axi is None: axi = vari.getAxis(axis) # - special case for time axis: must have same units ! dxi2o = 1. if ((A.axis_type(axo), order[axis]) == ('t', 't') and hasattr(axi, 'units') and hasattr(axo, 'units') and not are_same_units(axi.units, axo.units)): axi = ch_units(axi, axo.units, copy=True) if errl is not None: axou = axo.units #.split(1)[0] + axi.units.split(1)[1] dxi2o = (cdtime.reltime(1, axou).value - cdtime.reltime(0, axou).value) dxi2o /= (cdtime.reltime(1, axi.units).value - cdtime.reltime(0, axi.units).value) # - numeric version axin = axi[:] if N.ma.isMA(axin): axin = axin.filled(missing_value) # Subaxes nxo = axon.ndim if nxo>1: if iaxo is None: iaxo = _getiax_(vari, axo, axis) if iaxo is None: raise VACUMMError("Please, specifiy the 'iaxo' parameter") else: iaxo = 0 if iaxo<0: iaxo = nxo+iaxo nxi = axin.ndim if nxi>1: if iaxi is None: iaxi = _getiax_(vari, axi, axis) if iaxi is None: raise VACUMMError("Please, specifiy the 'iaxi' parameter") else: iaxi = 0 if iaxi<0: iaxi = nxi+iaxi # Verifications if vari.shape[axis-iaxi:axis-iaxi+nxi]!=axi.shape: raise VACUMMError("Input axis has a invalid shape: %s (!=%s)"%( axi.shape, vari.shape[axis-iaxi:axis-iaxi+nxi])) vs = list(vari.shape[axis-iaxo:axis-iaxo+nxo]) vs[iaxo] = axo.shape[iaxo] vs = tuple(vs) if vs!=axo.shape: raise VACUMMError("Output axis has a invalid shape: %s (!=%s)"%(axo.shape, vs)) # Homogeneize axi and axo shapes (except for z dim) iaxo_bak = iaxo axin, iaxi, axon, iaxo = _syncshapes_(axin, iaxi, axon, iaxo) # Convert to numeric varin = vari.filled() # Push interpolation dimension to the right and convert to 1D or 2D arrays varis, bakmapv = _toright_(varin, axis) vari2d = varis.reshape(-1, varis.shape[-1]) axind, bakmapi = _toright_(axin, iaxi) if axind.ndim>2: axind = axind.reshape((-1, axin.shape[iaxi])) axond, bakmapo = _toright_(axon, iaxo) if axond.ndim>2: axond = axond.reshape((-1, axon.shape[iaxo])) nxb = vari2d.size/max(axind.size, axond.size) nxi = axind.ndim nxo = axond.ndim # Method if method == 'auto' or method == None: method = regrid_method(axin, axon, iaxi=-1, iaxo=-1) method = regrid1d_method_name(method) if method in _cellave_methods: conserv = int(method.startswith('conserv')) # conserv = method == 'conservative' method = -1 elif isinstance(method, basestring): if method == 'interp': method = 'linear' if method=='cellerr': method = 4 elif method in _interp1d_methods: method = _interp1d_methods.index(method) else: method = -2 # Wrong method else: method = int(method) assert method >= -1 and method < 5, 'Wrong method' if method == 2: method = 3 # Hermit is always better # Cellerr if method==4: # Measurement errors if erri is None: raise VACUMMError('You must provide also erri for the "cellerr" method') if vari.shape!=erri.shape: raise VACUMMError('vari and erro must have the same shape: %s != %s'% (vari.shape, erri.shape)) erris, bakmapv = _toright_(N.ma.asarray(erri).filled(missing_value), axis) errm = erris.reshape(-1, erris.shape[-1]) # Lag errors if errl is None: errl = 0. errl = N.atleast_1d(errl) if N.ma.isMA(errl): errl = errl.filled(missing_value) errl = errl.ravel() * dxi2o if errl.size%nxi: raise VACUMMError('The size of errl (%i) in not a multiple nxi (%i).'% (errl.size, nxi)) # Routine and arguments if nxi==1 and nxo==1: # 1D->1D interp_func = _interp1d_ remap_func = _remap1d_ cellerr_func = _cellerr1d_ elif nxo==1: # 1D->ND interp_func = _interp1dx_ remap_func = _remap1dx_ cellerr_func = _cellerr1dx_ else: # ND->ND interp_func = _interp1dxx_ remap_func = _remap1dxx_ cellerr_func = _cellerr1dxx_ if method == -1: # Cellave regrid_func = remap_func kwargs=dict(conserv=conserv,extrap=0) elif method==4: regrid_func = cellerr_func kwargs=dict(errm=errm, errl=errl) else: # Interp regrid_func = interp_func kwargs=dict(method=method,extrap=0) # Regrid varo2d = regrid_func(vari2d, axind, axond, missing_value, **kwargs) if method==4: varo2d, erro2d = varo2d # Extrapolation if isinstance(extrap, basestring): extrap = extrap.lower() if extrap in ['both', 'all']: extrap = 2 elif extrap in ['bottom', 'min', 'lower', 'first']: extrap = -1 elif extrap in ['top', 'max', 'upper', 'last']: extrap = 1 else: extrap = 0 elif not isinstance(extrap, int): extrap = int(bool(extrap)) if extrap: varo2d = _extrap1d_(varo2d, missing_value, extrap) # Reshape back varos = varo2d.reshape(varis.shape[:-1]+axond.shape[-1:]) ; del varo2d varon = varos.transpose(*bakmapv) ; del varos if geterr and method==4: erros = erro2d.reshape(varis.shape[:-1]+axond.shape[-1:]) ; del erro2d erron = erros.transpose(*bakmapv) ; del erros # Convert to cdms varo = MV2.masked_values(varon, missing_value) cp_atts(vari, varo, id=True) varo.setMissing(missing_value) if geterr and method==4: erro = MV2.masked_values(erron, missing_value) cp_atts(vari, erro, id=True) erro.id = erro.id+'_error' if hasattr(erro, 'long_name'): erro.long_name = erro.long_name+' error' else: erro.long_name = 'Error' erro.setMissing(missing_value) if A.isaxis(axo): axes[axis] = axo elif cdms2.isVariable(axo): axes[axis] = axo.getAxis(iaxo_bak) else: axes[axis] = varo.getAxis(axis) if nxo==1: odlaxis = axes[axis] axes[axis][:] = axo[:] cp_atts(oldaxis, axes[axis]) varo.setAxisList(axes) varo.setGrid(grid) if geterr and method==4: erro.setAxisList(axes) erro.setGrid(grid) return varo, erro return varo
[docs]def nearest1d(vari, axo, **kwargs): """Interpolation along an axes :Params: - **vari**: Input cdms array - **axo**: Output cdms axis - **axis**, optional: Axis on wich to operate - Other keywords are passed to :func:`regrid1d` .. note:: This is an wrapper to :func:`regrid1d` using ``nearest`` as a default method. See its help for more information. """ return regrid1d(vari, axo, 'nearest', **kwargs)
[docs]def interp1d(vari, axo, method='linear', **kwargs): """Linear or cubic interpolation along an axes :Params: - **vari**: Input cdms array - **axo**: Output cdms axis - **axis**, optional: Axis on wich to operate - Other keywords are passed to :func:`regrid1d` .. note:: This is an wrapper to :func:`regrid1d` using ``linear`` as a default method. See its help for more information. """ return regrid1d(vari, axo, method, **kwargs)
[docs]def cubic1d(vari, axo, **kwargs): """Cubic interpolation along an axes :Params: - **vari**: Input cdms array - **axo**: Output cdms axis - **axis**, optional: Axis on wich to operate - Other keywords are passed to :func:`regrid1d` .. note:: This is an wrapper to :func:`regrid1d` using ``cubic`` as a default method. See its help for more information. """ return regrid1d(vari, axo, 'cubic', **kwargs)
[docs]def cellave1d(vari, axo, conserv=False, **kwargs): """Cell averaging or conservative regridding along an axis :Params: - **vari**: Input cdms array - **axo**: Output cdms axis - **axis**, optional: Axis on wich to operate - **conservative**, optional: If True, regridding is conservative - Other keywords are passed to :func:`regrid1d` .. note:: This is an wrapper to :func:`regrid1d` using ``cellave`` or ``conservative`` as a default method. See its help for more information. """ if kwargs.pop('conservative', conserv): method = 'conservative' else: method = 'cellave' return regrid1d(vari, axo, method, **kwargs)
remap1d = cellave1d
[docs]def cellerr1d(vari, axo, erri, errl=None, **kwargs): """Cell averaing with weights based on errors :Params: - **vari**: Input cdms array - **axo**: Output cdms axis - **erri**: Input measurement errors - **errl**, optional: Input lag error relative to lag - **axis**, optional: Axis on wich to operate - Other keywords are passed to :func:`regrid1d` .. note:: This is an wrapper to :func:`regrid1d` using ``cellerr`` method. See its help for more information. """ return regrid1d(vari, axo, method, **kwargs)
[docs]def fill1d2(vi,axis=0, k=1,padding=None,clip=False,min_padding=None, method='linear'): """Fill missing values of a 1D array using spline interpolation. - **vi**: Input cdms variable - *k*: Order of splines [default: 1 = linear] - *padding*: Padding around an gap defining where on which part of the sample we must fit splines [default: max([min_padding,len(gap)*5])] - *min_padding*: See padding [default: k] - *method*: See :func:`interp1d` [default: linear] :Return: Filled :mod:`cdms2` variable """ assert vi.rank() == 1,'Input variable must be of rank 1' import scipy.signal as S if min_padding is None: min_padding = k tc = vi.dtype.char xi = vi.getAxis(0).getValue().astype('d') # Removes missing points mask = MV.getmaskarray(vi) cxi = N.ma.masked_array(xi,mask=mask).compressed() cvi = vi.compressed() cii = N.ma.masked_array(N.arange(len(xi)),mask=mask).compressed() nc = len(cii) cjj = N.arange(nc) # Identify gaps gaps = cii[1:]-cii[:-1] igaps = N.ma.masked_where(gaps == 1,cjj[:-1]).compressed() # Output variable to refill vo = vi.clone() # Loop on gaps for igap in igaps: gap = gaps[igap] if padding is None: pad = max([min_padding,gap*5]) else: pad = max([min_padding,padding]) istart = max([0,igap-pad+1]) iend = min([nc,igap+1+pad]) splines = S.interpolate.splrep(cxi[istart:iend],cvi[istart:iend],k=k) vo[cii[igap]+1:cii[igap+1]] = I.interpolate.splev(xi[cii[igap]+1:cii[igap+1]],splines) return vo
[docs]def fill1d(vari, axis=0, method='linear', maxgap=0): """Fill missing values of a 1D array using interpolation. :Params: - **vari**: Input :mod:`cdms2` variable - *axis*: Axis number on which filling is performed - *method*: Interpolation method (see :func:`interp1d`) - *maxgap*: Maximal size of filled gaps (in steps) :Example: >>> fill1d(vari, axis=2, method='cubic', maxgap=5) :Return: Filled :mod:`cdms2` variable similar to input one """ if vari.mask is MV2.nomask: return vari.clone() iaxis = vari.getAxis(axis) nx = len(iaxis) ny = vari.size/nx if vari.getMissing() is None: vari.setMissing(1.e20) # We need a 2D variable if vari.ndim == 1: vari2d = MV2.reshape(vari, (1, nx)) vari2d.setAxis(1, vari.getAxis(0)) else: # Numeric varin = vari.filled() # Order if axis != vari.ndim-1: varis = N.rollaxis(varin, axis, vari.ndim) else: varis = varin del varin # 2D ndshape = varis.shape if vari.ndim == 2: vari2d = varis else: vari2d = varis.reshape((ny, nx)) # MV2 vari2d = MV2.masked_values(vari2d, vari.getMissing(), copy=0) vari2d.setAxis(-1, iaxis) vari2d.setMissing(vari.getMissing()) del varis # Loop on extra dim vari2dn = vari2d.filled() varo2d = vari2d.clone() if maxgap >=1: ii = N.arange(nx) dd = N.ones(nx-1) keep = N.ones(nx, '?') vari2dm = N.ma.ones(nx) for iy in xrange(ny): # Mask # - base mask xmask = vari2d[iy].mask if xmask is MV2.nomask or xmask.all() or ~xmask.any(): continue # - check gaps keep[:] = ~xmask if maxgap >=1: dd[:] = N.diff(xmask.astype('i')) gapstart = ii[:-1][dd==1]+1 gapend = ii[1:][dd==-1] ie0 = int(gapend[0]<gapstart[0]) is1 = (gapstart[-1]>gapend[-1]) and -1 or nx for istart, iend in zip(gapstart[:is1], gapend[ie0:]): if (iend-istart+1) > maxgap: keep[istart:iend+1] = True del gapstart, gapend # Compressed axis caxis = cdms2.createAxis(iaxis[keep]) cp_atts(iaxis, caxis) # Compressed variable vari2dm[:] = vari2d[iy].asma() vari2dc = MV2.asarray(vari2dm[keep]) vari2dc.setAxis(0, caxis) # Interpolate varo2d[iy] = regrid1d(vari2dc, iaxis, axis=0, method=method) # iaxisc = cdms2.createAxis(iaxis.getValue()[~keep]) # if hasattr(iaxis, 'units'): iaxisc.units = iaxis.units # varo2d[iy][~keep] = regrid1d(vari2dc, iaxisc, axis=0, method=method) del caxis, vari2dc if maxgap >=1: del ii, dd del keep, vari2dm # Back to correct dims varo = vari.clone() if vari.ndim == 1: varo[:] = MV2.where(vari.mask, varo2d[0], varo) del varo2d else: varo2dn = varo2d.filled() del varo2d # 2D if vari.ndim == 2: varon = varo2dn else: varon = varo2dn.reshape(ndshape) del varo2dn # Order if axis != vari.ndim-1: varos = N.rollaxis(varon, vari.ndim-1, axis) else: varos = varon del varon # MV2 varo[:] = MV2.where(vari.mask, MV2.masked_values(varos, vari.getMissing(), copy=0), varo) del varos return varo
[docs]def fill2d(var, xx=None, yy=None, mask=None, copy=True, **kwargs): """Fill missing value of 2D variable using inter/extrapolation - **var**: A cdms 2D variable. - *xx/yy*: Substitutes for axis coordinates [default: None] - Other keywords are passe to :func:`griddata` """ # Checkings if not cdms.isVariable(var): var = cdms.createVariable(var) if copy: var = var.clone() assert var.ndim >= 2, 'Input var must be at least 2D' # Get Axes xo = get_axis(var, -1) yo = get_axis(var, -2) if xx is None: xx = xo.getValue() if yy is None: yy = yo.getValue() if xx.ndim != 2 or yy.ndim !=2: xx, yy = N.meshgrid(xx, yy) xx = xx.astype('d') yy = yy.astype('d') # Var assert xx.shape == var.shape[-2:], '2D axes and variable are not compatible in shape (%s against %s)' %(xx.shape, var.shape) nex = var.size/(var.shape[-1]*var.shape[-2]) if var.ndim != 3: var3d = var.reshape(nex, var.shape[-2], var.shape[-1]) else: var3d = var # Mask if mask is None: mask = var3d.mask elif mask is not MV.nomask: assert mask.shape[-2:] == var3d.shape[-2:] mask = mask.copy() if mask.ndim!=2: mask.shape = var3d.shape # Loop on extra dimensions for iex in xrange(nex): var2d = var3d[iex].asma() if mask is not MV.nomask: if mask.shape==2: mask2d = mask else: mask2d = mask[iex] # Unmasking if mask is MV.nomask or mask2d.all() or ~mask2d.any(): continue good = ~mask2d xi = xx[good] yi = yy[good] zi = var2d[good] # Gridding and filling filled = griddata(xi, yi, zi, (xo, yo), compress=True, **kwargs) var3d[iex] = MV2.where(good, var2d, filled) del filled, xi, yi, good # Shape back if var3d.ndim == 3: var[:] = var3d[:] else: var[:] = var3d.reshape(var.shape[:-2]+(var.shape[-2], var.shape[-1])) return var
[docs]def regular(vi,dx=None,verbose=True,auto_bounds=False): """Fill a variable with missing values when step of first axis is increasing - **vi**: Input array on almost regular axis - *dx*: Force grid step to this. Else, auto evaluated. """ from vacumm.misc import cp_atts # Guess info from input var axes = vi.getAxisList() xi = axes[0] if len(xi) < 2: return vi xid = xi.getValue().astype('d') xtc = xi.dtype.char missing_value = vi.getMissing() sh = vi.shape # Get dx and gaps ddx = (xid[1:]-xid[:-1]) if dx is None: dx = N.minimum.reduce(ddx) gaps = ddx / dx - 1. gaps = N.where(N.less((gaps+1.) % 1.,0.1),N.floor(gaps),gaps) gaps = N.where(N.greater((gaps+1.) % 1.,0.9),N.ceil(gaps),gaps).astype('l') ngaps = N.sum(gaps) gaps = MV.masked_object(gaps,0) mask = MV.getmaskarray(gaps) gaps = gaps.compressed() igaps = MV.masked_array(N.arange(len(xi)-1,typecode='l'),mask=mask).compressed() ng = len(igaps) if not ngaps: return vi if verbose: print 'Filled %i gaps with missing values' % ngaps # Init output var nxi = len(xi) nxo = nxi+ngaps sho = list(sh) ; sho[0] = nxo xo = N.zeros(nxo,typecode='d') vo = MV.resize(vi,sho) #vo.id = vi.id+'_regular' ; vo.name = vo.id # Loop on first axis j = igaps[0]+1 xo[0:j] = xi[0:j] vi[0:j] = vi[0:j] for ig,i in enumerate(igaps): # Fill the gap gap = gaps[ig] xo[j:j+gap] = N.arange(1.,gap+1).astype(xtc)*dx + float(xi[i]) vo[j:j+gap] = missing_value # Fill values after the gap i += 1 j += gap if ig == ng-1: full = nxi-i else: full = igaps[ig+1]-i+1 xo[j:j+full] = xi[i:i+full] vo[j:j+full] = vi[i:i+full] j += full ## xo[-1] = xi[-1] ## vo[-1] = vi[-1] # Axes vo = MV.masked_object(vo,missing_value) xo = cdms.createAxis(xo) cp_atts(xi,xo,id=True) axeso = list(axes) axeso[0] = xo vo.setAxisList(axeso) vo.setGrid(vi.getGrid()) if auto_bounds: vo.getAxis(0).setBounds(bounds1d(vo.getAxis(0))) else: vo.getAxis(0).setBounds(None) return vo
[docs]def regular_fill1d(var,k=1,dx=None): """Combination: fill1d(regular_fill)) (with their parameter)""" return fill1d(regular(var,dx=dx),k=k)
[docs]def spline_interp1d(old_var,new_axis,check_missing=True,k=3,**kwargs): """Backward compatibility function See :func:`regrid1d` """ return regrid1d(old_var,new_axis,method=['nearest', 'linear', 'cubic'][k], **kwargs)
[docs]class GridData(object): """2D interpolator from a randomly spaced sample on a regular grid Possible algorithms: - Natural Neighbor using nat.Natgrid: - http://www.ncarg.ucar.edu//ngmath/natgrid/nnhome.html - http://www.cisl.ucar.edu/zine/98/spring/text/3.natgrid.html - http://dilbert.engr.ucdavis.edu/~suku/nem/nem_intro/node3.html - http://www.ems-i.com/gmshelp/Interpolation/Interpolation_Schemes/Natural_Neighbor_Interpolation.htm - 2D splines using css.css.Cssgrid :Parameters: - **xi**: Input 1D X positions. - **yi**: Input 1D Y positions. - **ggo**: Output grid. Can either (xo,yo), a cdms grid or a cdms variable with a grid. - *method*: Interpolator type, either 'nat' (Natural Neighbors) or 'css' (='splines' using splines) [default: 'nat'] - *nl*: Nonlinear interpolator (usually gives better results) [default: False] - *ext*: Extrapolate value outsite convex hull [**'nat' only**, default: False] - *mask*: Mask to apply to output data [default: None] - *compress*: If ``True``, interolate only unmasked data, and thus does not try guess the best mask (that's more efficient but very bad if data are masked!). - *sub*: Size of blocks for subblocking [**"nat" only**] - *margin*: Margin around ouput grid (or block) to select input data. Value is relative to X and Y extent. - Other keywords are set as attribute to the interpolator instance ; to get the list of parameters: :: >>> import nat ; nat.printParameterTable() >>> import css ; css.printParameterTable() :Example: >>> r = GridData(gridi, grido, method='nat', ext=False, margin=.7) >>> varo1 = r(vari1) >>> varo2 = r(vari2) """ def __init__(self, xi, yi, ggo, nl=False, ext=False, geo=None, method='nat', sub=30, margin=.5, compress=False, **kwargs): vcdwarn('GridData will no longer be supported in future version') # Helper self._GDH = _GridDataHelper_(xi, yi, ggo, geo=geo, compress=compress) # Init interpolator if method in ['css', 'splines']: raise VACUMMError('css/spline method is no longer supported') elif method in ['nat', None, 'natgrid']: from nat import Natgrid self.r = Natgrid self.sub = sub self.method = 'nat' self.margin = margin # Attributes of interpolator kwargs['igr'] = 0 kwargs['ext'] = ext kwargs.setdefault('hor', -1) kwargs.setdefault('ver', -1) self.ratts = kwargs if self.sub is not None: self._GDH.xo.shape = self._GDH.grid.shape self._GDH.yo.shape = self._GDH.grid.shape def __call__(self, zi, missing_value=None, **kwargs): """Interpolate zi on output grid - **zi**: At least a 1D array. """ # Init zi2d, zo3d, mo3d, nex = self._GDH.init_data(zi, missing_value) # Prepare sub-blocks if not self.sub: jbs = xrange(1) ibs = xrange(1) else: jbs = xrange((self._GDH.ny-1)/self.sub+1) ibs = xrange((self._GDH.nx-1)/self.sub+1) # Loop on supplementary dims for iex in xrange(nex): # Get data and mask get = self._GDH.get(zi2d, iex) if get is None: continue xi, yi, zzi, mmi = get constant = N.allclose(zzi, zzi[0]) if len(xi)<4: break # Interpolate by blocks for ib in ibs: for jb in jbs: # Block if self.sub is None: xslice = slice(None) yslice = slice(None) else: xslice = slice(ib*self.sub,(ib+1)*self.sub) yslice = slice(jb*self.sub,(jb+1)*self.sub) xo = self._GDH.xo[yslice, xslice].ravel().astype('d') yo = self._GDH.yo[yslice, xslice].ravel().astype('d') # Restriction of input data around output grid if self.margin > 0: margin = self.margin ngoodi = 0 igoodi = 0 enlarge = .5 while ngoodi < 5: # Loop to get a sufficient number of pts margin *= (1+enlarge*igoodi) dxo = xo.ptp()*margin dyo = yo.ptp()*margin ximin = xo.min()-dxo ximax = xo.max()+dxo yimin = yo.min()-dyo yimax = yo.max()+dyo goodi = xi>ximin goodi &= xi<ximax goodi &= yi>yimin goodi &= yi<yimax ngoodi = goodi.sum() igoodi += 1 if igoodi>100: break if igoodi>100: continue else: goodi = slice(None) # Interpolator interpolator = self.r(xi[goodi], yi[goodi], xo, yo, listOutput = 'yes') for att, val in self.ratts.items(): setattr(interpolator, att, val) interpolator.nul = missing_value # Interpolation if constant: sh = self._GDH.xo[yslice, xslice].shape zzo = N.zeros(sh) zzo += zzi[0] if mmi is not None: mmo = N.zeros(sh) mmo += mmi[0] else: sh = self._GDH.xo[yslice, xslice].shape zzo = interpolator.rgrd(zzi[goodi]).reshape(sh) if mmi is not None: mmo = interpolator.rgrd(mmi[goodi]).reshape(sh) zo3d[iex, yslice, xslice] = zzo if mmi is not None: mo3d[iex, yslice, xslice] = mmo del mmo del zzo if hasattr(zi2d, 'mask') and zi2d[iex].mask is not N.ma.nomask: del zzi, xi, yi gc.collect() del zi2d return self._GDH.format(zo3d, mo3d, **kwargs) rgrd = __call__ regrid = __call__
[docs]def cargen(xi, yi, zi, ggo, mask=None, compress=False, missing_value=None, **kwargs): """Interpolator from IFREMER :Params: - **xi**: Input 1D X positions. - **yi**: Input 1D Y positions. - **ggo**, optional: Output grid. Can either (xo,yo), a cdms grid or a cdms variable with a grid. - **mask**, optional: Mask to apply to output data [default: None] """ # Helper GDH = _GridDataHelper_(xi, yi, ggo, mask=mask, compress=compress, proj=False) assert GDH.grid_type == 'rect', 'cargen works only with rectangular grids' # Init data zo3d, mo3d = GDH.init_data(zi, missing_value) # Loop on supplementary dims for iex in xrange(GDH.nex): # Get data and mask get = GDH.get(iex) if get is None: continue xi, yi, zzi, mmi = get # Interpolate zo3d[iex] = _cargen_(GDH.xi, GDH.yi, zzi, GDH.xo, GDH.yo, GDH.missing_value).T if mmi is not None: mo3d[iex] = _cargen_(GDH.xi, GDH.yi, mmi, GDH.xo, GDH.yo, 1.).T # Format output return GDH.format(zo3d, mo3d, **kwargs)
krigdata = cargen
[docs]def krig(xi, yi, zi, ggo, mask=True, proj=True, missing_value=None, **kwargs): """Kriging interpolator to a grid :Params: - **xi**: Input 1D X positions. - **yi**: Input 1D Y positions. - **zi**: Input N-D with last dim as space. - **ggo**, optional: Output grid. Can either (xo,yo), a cdms grid or a cdms variable with a grid. - **mask**, optional: Mask to apply to output data [default: None] """ # Helper assert proj is not False GDH = _GridDataHelper_(xi, yi, ggo, proj=proj, mask=mask, compress=True) # Init data zo3d, mo3d = GDH.init_data(zi, missing_value) distfunc = 'haversine' if GDH.geo else 'simple' # Get data and mask get = GDH.get(zi2d, Ellipsis, compress, missing_value) if get is not None: gxi, gyi, zzi, mmi = get # Interpolate zo3d[:] = _krig_(gxi, gyi, zzi, GDH.xo, GDH.yo, distfunc=distfunc).reshape(zo3d.shape) del gxi, gyi, zzi, mmi # Format output return GDH.format(zo3d, mo3d, **kwargs)
[docs]def griddata(xi, yi, zi, ggo, method='linear', cgrid=False, cache=None, proj=True, **kwargs): """Interpolation in one single shot using GridData :Params: - **xi**: 1D input x coordinates. - **yi**: 1D input y coordinates (same length as xi). - **zi**: 1D input values (same length as xi). - **method**, optional: Method of interpolation, within ('nat', 'css', 'carg', 'krig') [default: 'carg'] - **cgrid**, optional: Output on a C-grid at U- and V-points deduced from ggo [default: False]. Not available for 'carg' and 'krig' methods. :See also: :class:`GridData` and :func:`cargen` """ if cgrid: return tuple([griddata(xi, yi, zi, gg, method=method, cgrid=False, **kwargs) for gg in t2uvgrids(ggo)]) # Method if method in ('css', 'spline', 'nat'): vcdwarn('Interpolation method other than nearest, linear or cubic' ' will not be supported in future versions') if method == 'nat': method = 'linear' elif method in ('css', 'spline'): method = 'cubic' assert method in _griddata_methods, ('Invalid interpolation method {}.' ' Please choose one of:').format( method, ' '.join(_griddata_methods)) assert proj is not False # Scipy if method in ['nearest', 'linear', 'cubic']: # Helper GDH = _GridDataHelper_(xi, yi, ggo, compress=False, proj=proj) # Data GDH.init_data(zi, initout=False) if GDH.masked and not GDH.compress and method == 'cubic': vcwarn("Can't interpolate masked with cubic method " "without compression. Switching compression on.") GDH.compress = True # Interpolator import scipy.interpolate as SI interpolator = {'nearest': SI.NearestNDInterpolator, 'linear': SI.LinearNDInterpolator, 'cubic': SI.CloughTocher2DInterpolator}[method] # Get stuff xi, yi, zi, mi = GDH.get() xyi = N.array([xi, yi]).T if method != 'nearest': xyi = get_tri(xyi, ttype='scipy', cache=cache) # triangulation xyo = N.array([GDH.xo, GDH.yo]).T # Interpolate kw = {} if method != 'nearest': kw['fill_value'] = GDH.missing_value zo3d = interpolator(xyi, zi.T, **kw)(xyo).T if GDH.masked and not GDH.compress and method != 'nearest': # if method != 'nearest': # kw['fill_value'] = 1. mo3d = interpolator(xyi, mi.T, **kw)(xyo).T else: mo3d = None # Format output return GDH.format(zo3d, mo3d, **kwargs) if method=='krig': return krig(xi, yi, zi, ggo, **kwargs) return cargen(xi, yi, zi, ggo, **kwargs)
class _GridDataHelper_(object): def __init__(self, xi, yi, ggo, mask=None, compress=False, proj=None, gtype=None): # Input grid assert xi.shape == yi.shape and xi.ndim == 1, 'xi and yi must be 1d arrays' xi = N.asarray(xi,dtype='d') yi = N.asarray(yi,dtype='d') proj = get_proj((xi, yi), proj=proj) self.mi = N.ones(len(xi), '?') if proj: self.xi, self.yi = proj(xi, yi) else: self.xi, self.yi = xi, yi # - grid and axes self.ongrid = isgrid(ggo) self.grid = get_grid(ggo, gtype=gtype) self.grid_type = get_grid_type(self.grid) self.x = self.grid.getLongitude() self.y = self.grid.getLatitude() self.xo = self.x[:] self.yo = self.y[:] if self.grid_type == "rect" and proj: self.xo, self.yo = N.meshgrid(self.xo, self.yo) if self.grid_type != 'unstruct': self.xo.shape = -1, self.yo.shape = -1, if proj: self.xo, self.yo = proj(self.xo, self.yo) self.axes = [] self.nx = self.grid.shape[-1] self.ny = self.grid.shape[0] # - mask if mask is True: if hasattr(ggo, 'getMask'): mask = ggo.getMask() elif hasattr(ggo, 'mask'): mask = ggo.mask if mask is False or mask is None: mask = N.ma.nomask if mask.ndim > 2: mask = mask[(0,)*(mask.ndim-2)] self.mask = mask self.compress = compress self.inited = False def init_data(self, zi, missing_value=None, initout=True): """Init input before regridding""" # Convert to right dims self.inited = True self.zi = zi nex = zi.size/zi.shape[-1] if cdms2.isVariable(zi): zi = zi.asma() self.zi2d = zi.reshape(nex, zi.shape[-1]).astype('d').copy() # Masking if missing_value is None: if N.ma.isMA(zi): missing_value = zi.get_fill_value() else: missing_value = N.ma.default_fill_value(zi) self.missing_value = missing_value unmasked = (not hasattr(self.zi2d, 'mask') or self.zi2d.mask is N.ma.nomask or not self.zi2d.mask.any()) self.masked = not unmasked self.nex = nex if not initout: return # Initialize output zo3d = N.zeros(self.zi2d.shape[:1]+self.grid.shape, zi.dtype) + missing_value if unmasked: compress = False else: compress = self.compress if compress or unmasked: mo3d = None else: mo3d = zo3d*0. del unmasked return zo3d, mo3d def get(self, iex=None): """Get a slice""" if not self.inited: raise VACUMMError('Please call .init_data() before calling .get()') if iex is None: iex = Ellipsis # Remove compress values or fill them good = self.mi # 1D mmi = None if self.masked and self.zi2d[iex].mask.any(): if not self.compress: # No compression => interpolation of mask mmi = self.zi2d[iex].mask.astype('f') self.zi2d[iex] = self.zi2d[iex].filled(self.missing_value) else: good = good & ~self.zi2d[iex].mask if good.ndim==2: good = good.all(axis=0) # All data are bad if not good.any(): return None if not good.all(): # Some are good zzi = self.zi2d[iex][..., good] xi = self.xi[good] yi = self.yi[good] if mmi is not None: mmi = mmi[good] else: # There are all good zzi = self.zi2d[iex] xi = self.xi yi = self.yi if N.ma.isMA(zzi): zzi = zzi.filled(self.missing_value) del good return xi, yi, zzi, mmi def format(self, zo3d, mo3d, clipval=False, asmv=None, **kwargs): """Format output variable""" # Shape sho = self.zi.shape[:-1] + self.grid.shape if zo3d.shape != sho: zo = zo3d.reshape(sho) del zo3d if mo3d is not None: mo = mo3d.reshape(sho) else: zo = zo3d mo = mo3d # Masking if self.mask is not N.ma.nomask: mask = self.mask if mask.shape != zo.shape: mask = N.resize(mask, zo.shape) zo[mask] = self.missing_value if mo3d is not None and mo is not None: zo[mo>0.] = self.missing_value missing = closeto(zo, self.missing_value) # Value clipping if clipval: valmax = self.zi.max() valmin = self.zi.min() good = ~missing zo[good&(zo>valmax)] = valmax zo[good&(zo<valmin)] = valmin # Format zo = N.ma.masked_where(missing, zo, copy=0) if asmv is None: asmv = self.ongrid or self.grid_type in ('rect', 'curv') if not asmv: return zo zo = MV2.asarray(zo) set_grid(zo, self.grid) if cdms.isVariable(self.zi): cp_atts(self.zi, zo, id=True) for i, axis in enumerate(self.zi.getAxisList()[:-len(self.grid.shape)]): zo.setAxis(i, axis) return zo
[docs]def xy2grid(*args, **kwargs): """Alias for :func:`griddata` .. seealso:: :class:`GridData` :func:`cargen` :func:`xy2grid` """ return griddata(*args, **kwargs)
[docs]def xy2xy(xi, yi, zi, xo, yo, method='linear', proj=True, cache=None, **kwargs): """Interpolation between to unstructured grids using scipy :Params: - **xi/yi**: 1D input positions - **zi**: atleast-1D input values - **xo,yo**: 1D output positions - *proj*: convert positions to meters using mercator projection """ # Helper assert method in ('nearest', 'linear', 'cubic') assert proj is not False GDH = _GridDataHelper_(xi, yi, (xo, yo), compress=False, proj=proj, gtype='unstruct') # Data GDH.init_data(zi, initout=False) if GDH.masked and not GDH.compress and method == 'cubic': vcwarn("Can't interpolate masked with cubic method " "without compression. Switching compression on.") GDH.compress = True # Interpolator import scipy.interpolate as SI interpolator = {'nearest': SI.NearestNDInterpolator, 'linear': SI.LinearNDInterpolator, 'cubic': SI.CloughTocher2DInterpolator}[method] # Get stuff xi, yi, zi, mi = GDH.get() xyi = N.array([xi, yi]).T if method != 'nearest': xyi = get_tri(xyi, ttype='scipy', cache=cache) # triangulation xyo = N.array([GDH.xo, GDH.yo]).T # Interpolate kw = {} if method != 'nearest': kw['fill_value'] = GDH.missing_value zo3d = interpolator(xyi, zi.T, **kw)(xyo).T if GDH.masked and not GDH.compress and method != 'nearest': # if method != 'nearest': # kw['fill_value'] = 1. mo3d = interpolator(xyi, mi.T, **kw)(xyo).T else: mo3d = None # Format output kwargs.setdefault('asmv', False) return GDH.format(zo3d, mo3d, **kwargs)
# # Check input positions # xi = N.asarray(xi, 'd') # yi = N.asarray(yi, 'd') # xo = N.asarray(xo, 'd') # yo = N.asarray(yo, 'd') # proj = kwargs.pop('geo', proj) # if proj: # if not callable(proj): # proj = get_proj((xi,yi)) # xi, yi = proj(xi, yi) # xo, yo = proj(xo, yo) # # # Check input type # outtype = 0 # if cdms2.isVariable(zi): # outtype = 2 # axes = zi.getAxisList() # atts = get_atts(zi) # zi = zi.asma() # elif N.ma.isMA(zi): # if zi.mask is not N.ma.nomask and zi.mask.any(): # outtype = 1 # # # Check shapes # zi = zi.copy() # si = zi.shape # nsi = zi.shape[-1] # nex = zi.size/nsi # zi.shape = (nex, nsi) # nso = len(xo) # zo = N.zeros((nex, nso)) # zo[:] = N.nan # if outtype: # mo = N.zeros((nex, nso)) # goodi = ~zi.mask # else: # mo = None # # # Loop on extra dim # from masking import convex_hull, polygon_select # for iex in xrange(nex): # # if outtype: # gi = goodi[iex] # mi = zi.mask[iex].astype('f') # else: # gi = slice(None) # # # Build regridder only when needed # if iex==0 or (outtype and N.any(goodi[iex-1]!=goodi[iex])): # # # Check that output points are inside convex hull # hull = convex_hull((xi[gi], yi[gi]), poly=True) # go = polygon_select(xo, yo, [hull], mask=2) ; del hull # if go.all(): # del go # go = slice(None) # # # Regridder # from nat import Natgrid # r = Natgrid(xi[gi], yi[gi], xo[go], yo[go], listOutput='yes') # r.igr = int(nl) # if outtype: # rm = Natgrid(xi, yi, xo[go], yo[go], listOutput='yes') # rm.igr = int(nl) # # # Regridding # # - values # zin = zi[iex][gi] # if N.ma.isMA(zin): zin = zin.filled() # zo[iex][go] = r.rgrd(zin) # # - mask # if outtype: # mo[iex][go] = rm.rgrd(mi) # del zi # # # Missing points # mnan = N.isnan(zo) # if mnan.any(): # outtype = max(1, outtype) # zo = N.ma.masked_where(mnan, zo) # del mnan # # # Return pure numeric # zo.shape = si[:-1]+(nso, ) # if outtype==0: return zo # # # Masking # if mo is not None: # mo.shape = zo.shape # zo = N.ma.masked_where(mo>.5, zo) # del mo, mi # # # Masked arrays # if outtype == 1: return zo # # # cdms # zo = MV2.asarray(zo) # set_atts(zo, atts) # for i, ax in axes[:-1]: # zo.setAxis(i, ax) # return zo
[docs]def grid2xy(vari, xo, yo, zo=None, to=None, zi=None, method='linear', outaxis=None, distmode='haversine'): """Interpolate gridded data to ramdom positions :Params: - **vari**: Input cdms variable on a grid - **xo**: Output longitudes - **yo**: Output latitudes - **method**, optional: Interpolation method - ``nearest``: Nearest neighbor - ``linear``: Linear interpolation - **zo**, optional: Output depths (negative in the ocean). - **to**, optional: Output times. - **zi**, optional: Input depths when variable in space. - **outaxis**, optional: Output spatial axis - A cdms2 axis. - ``None`` or ``'auto'``: Longitudes or latitudes depending on the range if coordinates are monotonic, else ``'dist'``. - ``'lon'`` or ``'x'``: Longitudes. - ``'lat'`` or ``'y'``: Latitudes. - ``'dist'`` or ``'d'``: Distance in km. - **distmode**, optional: Distance computation mode. See :func:`~vacumm.misc.grid.misc.get_distances`. """ # Prefer 1D axes grid = get_grid(vari) grid = curv2rect(grid, mode=False) # Format # - input data and coordinates xi, yi = get_xy(grid, num=True) rect = xi.ndim==1 mv = vari.getMissing() if mv is None: mv = vari.getMissing() order = vari.getOrder() vi = vari.filled(mv).astype('d') if 'z' not in order and zo is not None: zo = None if 't' not in order and to is not None: to = None univ = to is not None or zo is not None or True # FIXME: univ na = 2 # interpolation dims extra_axes = [] if zo is not None: if zi is None: zi = vari.getLevel() zi = zi[:] if univ: if method=='nat': vacumm_warn('"nat" method not available with time or depth' ' interpolation. Switching to linear') method = 'linear' it = 2 + ('z' in order) if 't' not in order and to is None: # not requested and not present vi = vi.reshape(vi.shape[:-it]+(1, )+vi.shape[-it:]) # insert fake dim elif to is not None: # requested and present na += 1 taxis = vari.getTime() ti = taxis[:] else: # present but not requested vi = vi.reshape(vi.shape[:-it]+(1, )+vi.shape[-it:]) # insert fake dim extra_axes.insert(0, vari.getTime()) if 'z' not in order and zo is None: # not requested and not present vi = vi.reshape(vi.shape[:-2]+(1, )+vi.shape[-2:]) # insert fake dim elif zo is not None: # requested and present na += 1 if zi.ndim<3: # no xy assumed zi = zi.reshape(zi.shape+(1, 1)) if zi.ndim < vari.ndim: # with xy: left pad with ones zi = zi.reshape((1, )*(vari.ndim-zi.ndim)+zi.shape) else: # present but not requested vi = vi.reshape(vi.shape[:-2]+(1, )+vi.shape[-2:]) # insert fake dim if extra_axes: # t as extra: ET1Z1YX -> ETZ11YX vi = vi.reshape(vi.shape[:-5]+vi.shape[-4:-3]+(1, 1)+vi.shape[-2:]) else: # ETZ1YX -> EZT1YX vi = N.moveaxis(vi, -4, -5) extra_axes.append(vari.getLevel()) # if zo is not None: # zi = zi.reshape((-1, )+zi.shape[-4:]) if xi.ndim==1: xi = xi[None, :] if yi.ndim==1: yi = yi[:, None] extra_axes = ([ax for ax, o in zip(vari.getAxisList(), order) if o=='-'] + extra_axes) else: na = 2 ne = vi.ndim - 2 vi = vi.reshape((-1,)+vi.shape[-4:]) # if zi.shape[0] != vi.shape[0]: # zi = N.resize(zi, vi.shape[:1] + zi.shape[1:]) if vari.mask is not MV2.nomask: mi = vari.mask.astype('d').reshape(vi.shape) else: mi = None # - output coordinates isscalar = N.isscalar(xo) xo = N.atleast_1d(N.asarray(xo, dtype='d')) yo = N.atleast_1d(N.asarray(yo, dtype='d')) no = max(xo.size, yo.size) if zo is not None: no = max(zo.size, no) if to is not None: no = max(len(to), no) assert xo.ndim==1 and yo.shape==xo.shape, ('xo and yo must be scalars 1d or arrays' ' of equal lengths') if abs(xi.min()-xo.min())>180.: if xi.min()<xo.min(): xi = xi + 360. else: xi = xi - 360. if zo is not None: zo = N.atleast_1d(zo[:]) assert zo.shape==xo.shape, ('zo must have the same shape as xo and yo') else: zo = N.zeros(no, 'd') zi = N.zeros([1]*5, 'd') if to is not None: to = A.create_time(to) to.toRelativeTime(taxis.units) to = to[:] assert to.shape== xo.shape, ('"to" must have the same shape as xo and yo') else: to = N.zeros(no, 'd') ti = N.zeros(1, 'd') # Interpolate if method is None: method = 'linear' else: method = str(method) if univ: targets = [-4, -3] if rect: targets.extend([-2, -1]) subdims = {-3:-3, -2:-2, -1:-1} if method == 'nearest' or (method=='nat' and mi is not None): if univ: vi, [ti, zi, yi, xi] = _monotonise_(vi, [ti, zi, yi, xi], targets=targets, subdims=subdims) vo = _nearest4dto1dxx_(xi, yi, zi, ti, vi, xo, yo, zo, to, mv) else: if rect: func = _nearest2dto1d_ vi, [yi, xi] = _monotonise_(vi, [yi, xi]) else: func = _nearest2dto1dc_ vo = func(xi, yi, vi, xo, yo, mv) if method=='nat' and mi is not None: vonear = vo if 'linear' in method: if univ: vi, [ti, zi, yi, xi] = _monotonise_(vi, [ti, zi, yi, xi], targets=targets, subdims=subdims) vo = _linear4dto1dxx_(xi, yi, zi, ti, vi, xo, yo, zo, to, mv) else: if rect: func = _bilin2dto1d_ vi, [yi, xi] = _monotonise_(vi, [yi, xi]) else: func = _bilin2dto1dc_ vi, [yi, xi] = _monotonise_(vi, [yi, xi]) vo = func(xi, yi, vi, xo, yo, mv) # elif method.startswith('nat'): # # # Build regridder # from nat import Natgrid # nz = vi.shape[0] # vo = N.zeros((nz, len(xo)))+mv # if xi.ndim==1: # xxi, yyi = xi, yi # else: # xxi, yyi = N.meshgrid(xi, yi) # r = Natgrid(xxi.ravel(), yyi.ravel(), xo, yo, listOutput='yes') # if mi is not None: # mo = N.zeros(len(xo)) # # # Z loop interpolation # for iz in xrange(nz): # # # Base # vo[iz] = r.rgrd(vi[iz].ravel()) # # # Mask # if mi is not None: # mo[:] = r.rgrd(mi[iz].ravel()) # vo[iz] = N.where(mo!=0., vonear[iz], vo[iz]) # # if mi is not None: # del mi, mo, vonear elif method != 'nearest': raise NotImplementedError, 'Method yet not implemented: '+method # Output vo.shape = tuple([len(ax) for ax in extra_axes]) + (vo.shape[-1], ) varo = MV2.masked_values(vo, mv, copy=0) cp_atts(vari, varo, id=True) if outaxis=='auto': outaxis = None if outaxis is None or outaxis in ['m', 'pos', 'd', 'dist']: if len(xo)>1: #xom, yom = get_proj((xo, yo))(xo, yo) if outaxis is None: outaxis = 'lon' if N.ptp(xo)>N.ptp(yo) else 'lat' if ((outaxis=='lon' and (N.diff(N.sign(N.diff(xo)))!=0).any() or (N.diff(xo)==0).any()) or (outaxis=='lat' and (N.diff(N.sign(N.diff(yo)))!=0).any() or (N.diff(yo)==0).any())): outaxis = 'dist' elif isscalar: outaxis = None else: outaxis = 'lon' elif outaxis=='num' or outaxis is False: outaxis = varo.getAxis(-1) if outaxis in ['x', 'lon']: outaxis = A.create_lon(xo) elif outaxis in ['y', 'lat']: outaxis = A.create_lat(yo) elif to is not None and outaxis in ['t', 'time']: outaxis = A.create_time(to) elif zo is not None and outaxis in ['z', 'dep', 'depth']: outaxis = A.create_dep(zo) elif outaxis in ['m', 'd', 'pos', 'dist']: dist = N.concatenate(([0], get_distances(xo[1:], yo[1:], xo[:-1], yo[:-1], pairwise=True, mode=distmode))).cumsum()*0.001 #dist = get_distances(xo[0], yo[0], xo, yo, mode=distmode)*0.001 #dist = N.concatenate(([0],N.sqrt(N.diff(xom)**2+N.diff(yom)**2)*0.001)).cumsum() outaxis = cdms2.createAxis(dist, id='position') outaxis.long_name = 'Distance along transect' outaxis.units = 'km' if outaxis is not None and not A.isaxis(outaxis): outaxis = cdms2.createAxis(outaxis, id='position') outaxis.long_name = 'Position along transect' axes = extra_axes if outaxis is not None: axes.append(outaxis) else: varo = varo[...,0] if N.ndim(varo): varo.setAxisList(axes) return varo
[docs]def transect(var, lons, lats, depths=None, times=None, method='linear', subsamp=3, getcoords=False, outaxis=None, depth=None, **kwargs): """Make a transect in a -[T][Z]YX variable It calls :func:`~vacumm.misc.grid.transect_specs` to compute transect coordinates when not explictly specified, and :func:`grid2xy` to perform 4D interpolations. :Example: >>> tsst = transect(sst, (1,1.6), (42.,43.), subsamp=4) >>> tmld = transect(mld, [1.,1.,2.], [42.,43.,43.], outaxis='dist') >>> tprof = transect(temp, 1., 42.) :Params: - **var**: MV2 array of at least rank 2 (YX). - **lons/lats**: Specification of transect, either - Coordinates of first and last point in degrees as tuples in the form ``(lon0,lon1)`` and ``(lat0,lat1)``. The array of coordinates is generated using :func:`transect_specs`. - Or explicit array of coordinates (as scalars, lists or arrays). - **depths**, optional: Output depths. If not a tuple, it must have the same size as lons and lats. - **times**, optional: Tuple, or time sequence or axis of the same length as resulting coordinates. If provided, the interpolation is first done in space, them onto this lagrangian time, and the final space-time trajectory is returned. If ``outaxis`` is None, ``taxis`` becomes the output axis. - **subsamp**, optional: Subsampling with respect to grid cell (only when coordinates are not explicit). - **method**, optional: Interpolation method (see :func:`grid2xy`). - **getcoords**, optional: Also get computed coordiantes. - **outaxis**, optional: Output spatial axis (see :func:`grid2xy`). :Return: ``tvar`` or ``tvar,tons,tlats`` """ # Output coordinates times = kwargs.get('time', times) # backward compat if isinstance(lons, tuple): # Find coordinates lon0, lon1 = lons lat0, lat1 = lats lons, lats = transect_specs(var, lon0, lat0, lon1, lat1, subsamp=subsamp) single = False else: # explicit coordinates single = N.isscalar(lons) and N.isscalar(lats) lons = N.atleast_1d(lons) lats = N.atleast_1d(lats) if depths is not None: if isinstance(depths, tuple): depths = N.linspace(depths[0], depths[1], len(lons)) else: depths = N.atleast_1d(depths) if len(depths)!=len(lons): raise VACUMMError('Your depths axis must have a length of: %i (!=%i'%( len(depths), len(lons))) if times is None and cdms2.isVariable(lons) and lons.getTime() is not None: times = lons.getTime() if times is not None: if isinstance(times, tuple): times = lindates(times[0], times[1], len(lons)) else: if not A.istime(times): times = A.create_time(times) if len(times)!=len(lons): raise VACUMMError('Your times must have a length of: %i (!=%i'%( len(times), len(lons))) # Interpolation var = grid2xy(var, lons, lats, zo=depths, to=times, zi=depth, method=method, outaxis=outaxis) # Single point if single: var = var[...,0] if not getcoords: return var coords = lons, lats if depths is not None: coords += depths, if times is not None: coords += times, return (var,) + coords
[docs]def refine(vari, factor, geo=True, smoothcoast=False, noaxes=False): """Refine a variable on a grid by a factor :Params: - **vari**: 1D or 2D variable. - **factor**: Refinement factor > 1 """ # No refinement if factor < 2: return vari # Initialize if cdms.isVariable(vari): op = MA else: op = N sho = () for nxy in vari.shape: sho += ((nxy-1)*factor+1, ) varo = op.zeros(sho, vari[:].dtype) if isinstance(vari, TransientAxis): varo = cdms.createAxis(varo.astype('d')) cp_atts(vari, varo, id=True) geo = False elif cdms.isVariable(vari): varo = cdms.createVariable(varo) cp_atts(vari, varo, id=True) else: geo = False # Fill step = 1./factor if vari[:].ndim == 1: # 1D linear varo[0::factor] = vari[:] for i in xrange(1, factor): varo[i::factor] = vari[:-1]+(vari[1:]-vari[:-1])*i*step else: # 2D bilinear #FIXME: considerer les axes 2D en input et output # Bilinear on pure numeric values if not N.ma.isMA(vari): xi = N.arange(vari.shape[-1]) yi = N.arange(vari.shape[-2]) xo = N.linspace(0., xi[-1], varo.shape[-1]) yo = N.linspace(0., yi[-1], varo.shape[-2]) xxo, yyo = N.meshgrid(xo, yo) varo[:] = _mbilin2d_(vari, xi, yi, xxo.flat, yyo.flat, 1.e20, smoothcoast, nogeo=1-int(geo)).reshape(varo.shape) # Masked data else: varo.setMissing(1.e20) # Grids if cdms.isVariable(vari): xi = vari.getAxis(-1) yi = vari.getAxis(-2) xo = refine(xi, factor) yo = refine(yi, factor) else: xi = N.arange(vari.shape[-1]) yi = N.arange(vari.shape[-2]) xo = N.linspace(0., xi[-1], varo.shape[-1]) yo = N.linspace(0., yi[-1], varo.shape[-2]) xxo, yyo = N.meshgrid(xo[:], yo[:]) # Interpolation varo[:] = _mbilin2d_(vari.filled(1.e20),xi[:], yi[:], xxo.flat, yyo.flat, 1.e20, smoothcoast,len(xi),len(yi),N.size(xxo),geo).reshape(varo.shape) # Masking if vari.mask is not MV.nomask: fmaski = vari.mask.astype('f') fmasko = _mbilin2d_(fmaski,xi[:], yi[:], xxo.flat, yyo.flat, 1.e20, False, len(xi),len(yi),N.size(xxo),geo).reshape(varo.shape) varo[:] = MV.masked_where(N.greater(fmasko, .5), varo, copy=0) varo[:] = MV.masked_values(varo, 1.e20, copy=0) # Axes if not noaxes and cdms.isVariable(varo): for i in -2, -1: # varo.setAxis(i, refine(vari.getAxis(i), factor)) varo.setAxis(i, (yo, xo)[i]) varo.setGrid(create_grid(xo,yo,fmasko)) return varo
_cellave_methods = ['conservative', 'remap', 'cellave', 'conserv'] _cdat_methods = _cellave_methods+['bilinear'] _regrid2d_methods = ['nearest', 'mixt', 'interp', 'bining']+_griddata_methods+_cdat_methods
[docs]def regrid2d_method_name(method, raiseerr=True): """Check the method name and return its generic name""" if method is None or method.lower()=='auto': return 'auto' method = method.lower() if method.startswith('cons'): return 'conserv' if method.startswith('cell') or method.startswith('remap'): return 'cellave' if 'lin' in method or method=='interp': return 'bilinear' if method.startswith('bin'): return 'bining' if method.startswith('nat'): return 'nat' if method.startswith('near'): return 'nearest' if method.startswith('carg') or method.startswith('krig'): return 'carg' if method.startswith('dst'): return "dstwgt" if method.startswith('pat'): return "patch" if raiseerr: raise VACUMMError('Invalid regrid2d method. Please use for example one of these: '+ ', '.join(_regrid2d_methods)) else: return method
#: Available tools for each method regrid2_tools = OrderedDict([ ('cellave', dict(r2r=['regrid2', 'esmf'], c2c=['esmf'])), ('conserv', dict(r2r=['regrid2', 'esmf'], c2c=['esmf'])), ('nearest', dict(c2c=['vacumm'])), ('bilinear', dict(r2r=['vacumm','esmf','libcf'], r2c=['vacumm','esmf','libcf'], c2c=['esmf', 'libcf', 'vacumm'])), ('patch', dict(c2c=['esmf'])), ('dstwgt', dict(r2r=['vacumm'], c2c=['vacumm'])), ('carg', dict(r2r=['vacumm'])), ('nat', dict(c2r=['vacumm'])), ('bining', dict(c2r=['vacumm'])), ])
[docs]def regrid2d_tool_name(tool, raiseerr=True): """Check the tool name and return its generic name""" if tool is None or tool.lower()=='auto': return 'auto' tool = tool.lower() if tool.startswith('es'): return 'esmf' if tool.startswith('lib') or tool.startswith('g'): return 'libcf' if tool.startswith('cd') or tool.startswith('reg'): return 'regrid2' if tool.startswith('v'): return 'vacumm' if raiseerr: raise VACUMMError('Invalid regrid2d tool. Please use for example one of these: '+ 'regrid2, esmf, libcf, vacumm, auto') else: return method
[docs]def regrid2dold(vari, ggo, method='auto', mask_thres=.5, ext=False, bilinear_masking='dstwgt', ext_masking='poly', cdr=None, getcdr=False, usecdr=None, useoldcdr=True, check_mask=True, clipminmax=False, geo=None, **kwargs): """Regrid a variable from a regular grid to another If the input or output grid is curvilinear and ``method`` is set to ``"linear"``, ``"cellave"`` or ``"conserv"``, the :class:`CDATRegridder` is used. :Params: - **vari**: Variable cdms on regular grid - **ggo**: Tuple of (x,y) or a cdms grid or a cdms variable with a grid - **method**, optional: One of: - ``"auto"``: method guessed according to resolution of input and output grid (see :func:`regrid_method`) - ``"nearest"``: nearest neighbour - ``"linear"`` or ``"bilinear"``: bilinear interpolation (low res. to high res.) - ``"dstwgt"`` : distance weighting (low res. to high res.) - ``"cellave"`` : weighted regridding based on areas of cells (high res. to low res.) - ``"conserv"`` : same as ``cell`` but conservative (high res. to low res.) - ``"bining"`` : simple averaging using bining (very high res. to low res.) - ``"nat"`` : Natgrid interpolation (low res. to high res.) (see :class:`GridData` for more info) - ``"carg"`` : Interpolation with minicargen(low res. to high res.) (see :func:`cargen` for more info) - **cdr**, optional: :class:`CDATRegridder` instance. - **getcdr**, optional: Also return the computed :class:`CDATRegridder` instance. - **usecdr**, optional: Force the use or not of a :class:`CDATRegridder` instance, even for rectangular grids. - **useoldcdr**, optional: Force the use the old CDAT regridder (rectangular grids only). - **ext**, optional: Perform extrapolation when possible - **bilinear_masking**: the way to handle interpolation near masked values - ``"nearest"``: brut masking using nearest neighbor - ``"dstwgt"`` : distance weight data are used where interpolated mask is lower ``mask_thres`` - **mask_thres**, optional: Threshold for masking points for some methods (~ land fraction) for **rectangular grids only**: - ``method="bilinear"`` and ``bilinear_masking="dstwght"`` - ``method="cellave"`` or ``method="bining"`` - **ext_masking**, optional: Manual masking method when ``ext=False`` (when needed) with methods ["carg",] (see :func:`~vacumm.misc.grid.masking.grid_envelop_mask`) if input grid is not rectangular - ``"poly"``: use the polygon defined by the input grid envelopp and check if output points are inside - ``"nearest"``: use hack with nearest 2d interpolation - Other keywords are passed to special interpolation functions depending on method and choices : - :func:`cargen` when "nat" or "carg" method is used - **mask_thres**, optional: Time steps when interpolated mask is greater than this value are masked. :Examples: >>> regrid2d(var, (lon, lat), method='bilinear', bilinear_masking='nearest') >>> regrid2d(var, grid, method='cellave', mask_thres=.8) >>> regrid2d(var, grid, method='nat', hor=.2) """ # Method method = regrid2d_method_name(method) if method == 'auto': method = regrid2d_method_name(regrid_method(vari, ggo)) # Check mask, grids and method vari = MV.asarray(vari) if vari.getMissing() is None: vari.setMissing(1.e20) mv = vari.getMissing() maski = vari.mask if maski is MV.nomask: check_mask = False if check_mask: maski = maski.astype('f') ggi = curv2rect(get_grid(vari), mode='none') nyi, nxi = ggi.shape nzi = vari.size/(nxi*nyi) ggo = get_grid(ggo) ggor = curv2rect(ggo, mode='none') nyo, nxo = ggo.shape loni = get_axis(ggi, -1) lati = get_axis(ggi, -2) lono = get_axis(ggor, -1) lato = get_axis(ggor, -2) xo = lono[:] yo = lato[:] xi = loni[:] yi = lati[:] curvedi = xi.ndim==2 curvedo = xo.ndim==2 curved = curvedi or curvedo curvedio = curvedi and curvedo xxi, xxo, yyi, yyo = None, None, None, None if geo is None: geo = A.islon(loni) or A.islat(lati) or A.islon(lono) or A.islat(lato) # Bounds if method in _cellave_methods+['nearest']: funci = bounds2d if curvedi else bounds1d loni.setBounds(funci(loni)) lati.setBounds(funci(lati)) funco = bounds2d if curvedo else bounds1d lono.setBounds(funco(lono)) lato.setBounds(funco(lato)) # Some checks about methods if curved: if method not in _griddata_methods+['nearest', 'bining']+_cdat_methods: raise NotImplementedError, 'Method not allowed with curvilinear grids: '+method xxi, yyi = meshgrid(loni, lati) maskoext = False if method == 'krig': method = 'carg' if method == 'nearest' or \ (check_mask and method in ['bilinear', 'mixt', 'dstwgt'] and not bilinear_masking=='dstwgt') or \ (ext is False and method == 'carg') : # Needs nearest xxo, yyo = meshgrid(lono, lato) if not isgrid(ggi, curv=True) or method=='nearest': xxi, yyi = meshgrid(loni, lati) if not ext: from masking import grid_envelop_mask maskoext = N.resize(grid_envelop_mask(ggi, ggo, poly=ext_masking=="poly"), (nzi, nyo, nxo)) elif method == 'bining' and curvedo: raise NotImplementedError, 'Method not allowed with curvilinear output grid: '+method # 3D variable? if vari.ndim != 3: vari3d = MV.resize(vari, (nzi, nyi, nxi)) set_grid(vari3d, ggi) # vari3d.getAxis(0).designateLevel() # hack against cdat bug maski3d = N.resize(maski, vari3d.shape) else: # if vari.getOrder()[0] not in 'tz': # TODO: OPTIMIZE REGRID2 # vari3d = vari.clone() # zaxis = vari3d.getAxis(0).clone() # zaxis.designateLevel() # vari3d.setAxis(0, zaxis) vari3d = vari maski3d = maski # Interpolations if method == 'nearest': # Interpolation varo3d = _regrid2d_nearest2d_(vari3d, xxi, yyi, xxo, yyo, mv, geo, maskoext) elif (not curved and method in ['mixt', 'dstwgt']) or \ (method=='bilinear' and not curved and usecdr is not True): # Method wrapper = eval('_regrid2d_%s_'%method) # Interpolation #FIXME: wrapper pour dstwgt et mask... not sure varo3d = wrapper(vari3d, xi, yi, xo, yo, mv, geo, ext) # Masking if check_mask: if bilinear_masking == 'dstwgt': masko = wrapper(maski3d, xi, yi, xo, yo, 1., geo, ext) if method != 'dstwgt': varo3d_dstwgt = _regrid2d_dstwgt_(vari3d, xi, yi, xo, yo, mv, geo, ext) varo3d[:] = N.where((masko<mask_thres) & (masko>0.), varo3d_dstwgt, varo3d) del varo3d_dstwgt varo3d[masko>=mask_thres] = mv else: masko = varo3d==mv varo3d_nearest = _regrid2d_nearest2d_(vari3d,xxi,yyi,xxo,yyo,mv,geo,maskoext) varo3d = N.where(masko!=0., varo3d_nearest, varo3d) del varo3d_nearest del masko elif method in _cdat_methods: # Old regridder ? if useoldcdr or useoldcdr is None and not curved and method in ['cellave', 'conserv']: tool = 'regrid2' # ESMF regridder else: esmfcons = curved #and method in _cellave_methods if esmfcons: check_mask = True vari3d = MV2.where(maski3d, 0., vari3d) set_grid(vari3d, ggi) tool = None # Regridder if cdr is None: cdr = CDATRegridder(vari3d, ggor, method=method, tool=None) # Regrid data varo3d = cdr(vari3d, check_mask=check_mask) del maski3d # # Masking # if check_mask:# and not method.startswith('cons'): # good = vari3d.clone() # good[:] = 1-maski3d ; del maski3d # goodo = cdr(good).filled(0.) ; del good ## varo3d_nearest = _regrid2d_nearest2d_(vari3d.filled(mv),xxi,yyi,xxo,yyo,geo,maskoext,mv) ## varo3d[:] = MV2.where(masko!=0., varo3d_nearest, varo3d) # varo3d[:] = MV2.masked_where((goodo<0.9999), varo3d, copy=0) ; del goodo ## varo3d[:] = MV2.masked_where((masko>mask_thres).astype('b') & (masko>0.).astype('b'), varo3d, copy=0) ## del varo3d_nearest elif method.startswith('bin'): # Interpolation varo3d = _regrid2d_bining_(vari3d, xxi, yyi, xo, yo, mv) # Masking if check_mask: masko = _regrid2d_bining_(maski3d, xxi, yyi, xo, yo, 1.) varo3d[masko>=mask_thres] = mv del masko elif method in _griddata_methods: # Interpolation varo3d = _regrid2d_griddata_(vari3d, xi, yi, xo, yo, method, ext=ext, missing_value=mv, **kwargs) # Masking if check_mask: masko = _regrid2d_griddata_(maski3d, xi, yi, xo, yo, method, ext=ext, missing_value=1., **kwargs) # varo3d_nearest = _regrid2d_nearest2d_(vari3d.filled(mv),xxi,yyi,xxo,yyo,mv,geo,False) # varo3d = N.where(masko!=0., varo3d_nearest, varo3d) varo3d[masko>=mask_thres] = mv if method=='carg' and not ext: varo3d[maskoext] = mv # del varo3d_nearest else: raise RuntimeError, "Well, what's this funckin' method you bastard huh: %s?"%method # Back to rights dims if vari.ndim != 3: varo = MV.resize(varo3d, vari.shape[:-2]+(nyo, nxo)) del vari3d, varo3d else: varo = varo3d # MV2 variable varo[N.isnan(varo)] = mv varo = MV2.masked_values(varo, mv, copy=0) # if not isgrid(ggo, curv=True): set_grid(varo, ggo) if vari.ndim>2: for iaxis in range(vari.ndim-2): varo.setAxis(iaxis, vari.getAxis(iaxis)) cp_atts(vari, varo, id=True) if getcdr: return varo, cdr return varo
[docs]def regrid2d(vari, ggo, method='auto', tool=None, rgdr=None, getrgdr=False, erode=False, **kwargs): """Regrid a variable from a regular grid to another If the input or output grid is curvilinear and ``method`` is set to ``"linear"``, ``"cellave"`` or ``"conserv"``, the :class:`CDATRegridder` is used. :Params: - **vari**: Variable cdms on regular grid - **ggo**: Tuple of (x,y) or a cdms grid or a cdms variable with a grid - **method**, optional: One of: - ``"auto"``: method guessed between ``linear`` and ``cellave`` according to resolution of input and output grid (see :func:`regrid_method`) - ``"nearest"``: nearest neighbour - ``"linear"`` or ``"bilinear"``: bilinear interpolation (low res. to high res.) - ``"dstwgt"`` : distance weighting between the four nearest grid points (low res. to high res.) - ``"patch"`` : patch recovery interpolation (low res. to high res.) - ``"cellave"`` : weighted regridding based on areas of cells (high res. to low res.) - ``"conserv"`` : same as ``cell`` but conservative (high res. to low res.) - **tool**, optional: Regridder. One of: - ``"auto"``: tool guessed depending on the method, the first available tool and the grids (rectangular or curvilinear). - ``"vacumm"``: Internal routines. - ``"esmf"`` and ``"libcf"``: Regridders provided by UVCDAT. - ``"regrid2"``: Old regridder provided by CDAT (rectangular only). - **rgdr**, optional: An already set up regridder instance to speed up regridding: :class:`CDATRegridder` instance for ``regrid2``, ``esmf`` and ``libcf`` tools, else a :class:`CurvedInterpolator` instance for ``vacumm`` tool with interpolation on curvilinear grids. - **getrgdr**, optional: Also return the regridder instance if it applies, or None. - Other keywords are passed to special interpolation functions depending on method and choices : - :func:`cargen` when "nat" or "carg" method is used :Tools/methods: Overview table of method availability for each tool. ``RECT`` means that it only works with rectangular grids. +----------+--------+----------+------+-------+ | Met/Tool | Vacumm | regrid2d | ESMF | Libcf | +==========+========+==========+======+=======+ | nearest | OK | | | | +----------+--------+----------+------+-------+ | bilinear | OK | | OK | OK | +----------+--------+----------+------+-------+ | dstwgt | OK | | | | +----------+--------+----------+------+-------+ | patch | | | OK | | +----------+--------+----------+------+-------+ | cellave | | RECT | OK | | +----------+--------+----------+------+-------+ | conserv | | RECT | OK | | +----------+--------+----------+------+-------+ :Examples: >>> regrid2d(var, (lon, lat), method='linear') >>> regrid2d(var, grid, method='cellave') """ # Check grids vari = MV.asarray(vari) mv = vari.getMissing() if mv is None or N.isnan(mv): vari.setMissing(1.e20) mv = vari.getMissing() ggi = curv2rect(get_grid(vari), mode='none') nyi, nxi = ggi.shape nzi = vari.size/(nxi*nyi) ggo = get_grid(ggo) ggor = curv2rect(ggo, mode='none') nyo, nxo = ggo.shape loni = get_axis(ggi, -1) lati = get_axis(ggi, -2) lono = get_axis(ggor, -1) lato = get_axis(ggor, -2) xo = lono[:] yo = lato[:] xi = loni[:] yi = lati[:] curvedi = xi.ndim==2 curvedo = xo.ndim==2 curved = curvedi or curvedo curvedio = curvedi and curvedo xxi, xxo, yyi, yyo = None, None, None, None # if geo is None: # geo = A.islon(loni) or A.islat(lati) or A.islon(lono) or A.islat(lato) geo = True # Method method = regrid2d_method_name(method) if method == 'auto': method = regrid2d_method_name(regrid_method(vari, ggo)) valid_methods = ['nearest', 'bilinear', 'cellave', 'conserv', 'dstwgt', 'patch'] if method not in valid_methods: raise VACUMMError('Wrong regridding method: %s. Please choose one of :'%method+ ', '.join(valid_methods)) # Tool verification tool = regrid2d_tool_name(tool) tools = regrid2_tools[method] for ttype in 'r2r', 'r2c', 'c2r', 'c2c': if ttype not in tools: continue if (ttype=='r2r' and not curved) or \ (ttype=='c2r' and curvedi and not curvedo) or \ (ttype=='r2c' and curvedo and not curvedi) or \ ttype=='c2c': # Fall back | and curved): if tool=='auto': tool = tools[ttype][0] # First available elif tool not in tools[ttype]: raise VACUMMError('Tool %s not available for these grids and this method: '% (tool, method)) break else: raise VACUMMError('No suitable tool found for these grids and this method: '+method) # Bounds if method in _cellave_methods+['nearest']: funci = bounds2d if curvedi else bounds1d loni.setBounds(funci(loni)) lati.setBounds(funci(lati)) funco = bounds2d if curvedo else bounds1d lono.setBounds(funco(lono)) lato.setBounds(funco(lato)) # 2D arrays if curvedi or method=='nearest': xxi, yyi = meshgrid(loni, lati) if curvedi or curvedo or method=='nearest': xxo, yyo = meshgrid(lono, lato) # 3D variable? if vari.ndim != 3: vari3d = MV.resize(vari, (nzi, nyi, nxi)) set_grid(vari3d, ggi) else: vari3d = vari # Interpolations ! rgdr = None if tool in ['regrid2', 'esmf', 'libcf']: # # ESMF regridder # if tool=='esmf' and method in ["cellave", "conserv"]: # check_mask = True # vari3d = MV2.where(maski3d, 0., vari3d) # set_grid(vari3d, ggi) # else: # check_mask = False # Regridder if rgdr is None: rgdr = CDATRegridder(vari3d, ggor, method=method, tool=tool) # Regrid data varo3d = rgdr(vari3d)#, check_mask=check_mask) # del maski3d elif method == 'nearest': varo3d = _regrid2d_nearest2d_(vari3d, xxi, yyi, xxo, yyo, mv, geo, False) elif method in ['bilinear', 'dstwgt']: if curvedi: wrapper = eval('_regrid2d_%s_c2c_'%method) varo3d, rgdr = wrapper(vari3d, xxi, yyi, xxo, yyo, mv, rgdr) elif curvedo: wrapper = eval('_regrid2d_%s_r2c_'%method) varo3d = wrapper(vari3d, xi, yi, xxo, yyo, mv) else: wrapper = eval('_regrid2d_%s_r2r_'%method) varo3d = wrapper(vari3d, xi, yi, xo, yo, mv, geo) elif method == ['mixt']: # Method wrapper = eval('_regrid2d_%s_'%method) # Interpolation varo3d = wrapper(vari3d, xi, yi, xo, yo, mv, geo, ext) else: raise RuntimeError, "Well, what's this funckin' method you bastard huh: %s?"%method # Back to rights dims if vari.ndim != 3: varo = MV.resize(varo3d, vari.shape[:-2]+(nyo, nxo)) del vari3d, varo3d else: varo = varo3d # MV2 variable varo[N.isnan(varo)] = mv varo = MV2.masked_values(varo, mv, copy=0) # if not isgrid(ggo, curv=True): set_grid(varo, ggo) if vari.ndim>2: for iaxis in range(vari.ndim-2): varo.setAxis(iaxis, vari.getAxis(iaxis)) cp_atts(vari, varo, id=True) if getrgdr: return varo, rgdr return varo
regrid2dnew = regrid2d def _regrid2d_nearest2d_(vari3d, xxi, yyi, xxo, yyo, mv, geo, maskoext): """Wrapper to fortran _nearest2d_""" nb = min(xxi.shape)/50 if nb == 1: nb = 0 # nb = -1 if N.ma.isMA(vari3d): vari3d = vari3d.filled(mv) varo3d = N.asarray(_nearest2d_(vari3d, xxi, yyi, xxo, yyo, nb, not geo), order='C') if maskoext is not False: varo3d[maskoext] = mv return varo3d def _regrid2d_bilinear_r2r_(vari3d, xi, yi, xo, yo, mv, geo, ext=0): """Wrapper to fortran _bilin_""" if N.ma.isMA(vari3d): vari3d = vari3d.filled(mv) varo3d = N.asarray(_bilin_(vari3d, xi, yi, xo, yo, mv, not geo), order='C') return varo3d _regrid2d_bilinear_ = _regrid2d_bilinear_r2r_ def _regrid2d_bilinear_r2c_(vari3d, xi, yi, xxo, yyo, mv): """Wrapper to fortran _bilin2dto1d_""" if N.ma.isMA(vari3d): vari3d = vari3d.filled(mv) varo3d = N.ascontiguousarray(_bilin2dto1d_(xi, yi, vari3d, xxo.ravel(), yyo.ravel(), mv)) varo3d.shape = varo3d[:-1]+xxo.shape return varo3d def _regrid2d_bilinear_c2c_(vari3d, xxi, yyi, xxo, yyo, mv, rgdr=None): """Wrapper to fortran _bilin2dto1dc_reduc_ through :class:`CurvedInterpolator`""" if rgdr is None: rgdr = CurvedInterpolator((xxi, yyi), (xxo, yyo), g2g=True) varo3d = N.ascontiguousarray(rgdr(vari3d, method='bilinear')) return varo3d, rgdr def _regrid2d_dstwgt_r2r_(vari3d, xi, yi, xo, yo, mv, geo, ext=0): """Wrapper to fortran _bilin_""" if N.ma.isMA(vari3d): vari3d = vari3d.filled(mv) varo3d = N.asarray(_dstwgt_(vari3d, xi, yi, xo, yo, mv, not geo), order='C') return varo3d _regrid2d_dstwgt_ = _regrid2d_dstwgt_r2r_ def _regrid2d_dstwgt_r2c_(vari3d, xi, yi, xxo, yyo, mv): """Wrapper to fortran _bilin2dto1d_""" if N.ma.isMA(vari3d): vari3d = vari3d.filled(mv) varo3d = N.ascontiguousarray(_dstwgt2dto1d_(xi, yi, vari3d, xxo.ravel(), yyo.ravel(), mv)) varo3d.shape = varo3d[:-1]+xxo.shape return varo3d def _regrid2d_dstwgt_c2c_(vari3d, xxi, yyi, xxo, yyo, mv, rgdr=None): """Wrapper to fortran _bilin2dto1dc_reduc_ through :class:`CurvedInterpolator`""" if rgdr is None: rgdr = CurvedInterpolator((xxi, yyi), (xxo, yyo), g2g=True) varo3d = N.ascontiguousarray(rgdr(vari3d, method='dstwgt')) return varo3d, rgdr def _regrid2d_mixt_(vari3d, xi, yi, xo, yo, mv, geo, ext=0): """Wrapper to fortran _mixt2dx_""" if N.ma.isMA(vari3d): vari3d = vari3d.filled(mv) varo3d = N.asarray(_mixt2dx_(vari3d, xi, yi, xo, yo, mv, ext).T, order='C') return varo3d def _regrid2d_bining_(vari3d, xxi, yyi, xo, yo, mv): """Regridding using rebining""" xbins = meshcells(xo) ybins = meshcells(yo) varo3d = N.zeros(vari3d.shape[:-2]+(len(yo), len(xo))) for i, vari2d in enumerate(vari3d.filled(mv)): if hasattr(vari2d, 'mask') and vari2d.mask.any(): good = ~vari2d.mask xc = xxi[good] yc = yyi[good] vartmp = vari2d.compressed() else: xc = xxi.ravel() yc = yyi.ravel() vartmp = vari2d.ravel() pos = N.asarray([yc, xc]).T del xc, yc count, edges = N.histogramdd(pos, bins=(ybins, xbins)) ; del edges varo3d[i], edges = N.histogramdd(pos, bins=(ybins, xbins), weights=vartmp) del pos, edges good = count!=0 varo3d[i][good] /= count[good] varo3d[i][~good] = mv del good, count return varo3d def _regrid2d_griddata_(vari3d, xi, yi, xo, yo, method, **kwargs): """Wrapper to griddata""" # Good shapes xxi, yyi = meshgrid(xi, yi) nzi, nyi, nxi = vari3d.shape vari2d = vari3d.reshape(nzi, nxi*nyi) # Restrict zone dxo = xo.ptp()/10. dyo = yo.ptp()/10. #if hasattr(vari2d, 'mask') and vari2d.mask is not MV2.nomask and vari2d.mask.any(): #good = vari2d.mask #else: #good = N.zeros(vari2d.shape, '?') good = (xxi.ravel()>=(xo.min()-dxo)) & (xxi.ravel()<=(xo.max()+dxo)) & \ (yyi.ravel()>=(yo.min()-dyo)) & (yyi.ravel()<=(yo.max()+dyo)) varitmp = vari2d.compress(good.ravel(), axis=1) xxitmp = xxi.compress(good.ravel()) yyitmp = yyi.compress(good.ravel()) del vari2d varo2d = griddata(xxitmp, yyitmp, varitmp, (N.asarray(xo), N.asarray(yo)), method=method, **kwargs) del xxitmp, yyitmp, varitmp varo3d = varo2d.reshape(vari3d.shape[:-2]+varo2d.shape[-2:]) del varo2d return varo3d def _regrid2d_natgridlist_(vari3d, xi, yi, xxo, yyo, ext, **kwargs): #FIXME: _regrid2d_natgridlist_ : missing values """Wrapper to Natgrid.nat with list output""" # Good shapes xxi, yyi = meshgrid(xi, yi) nzi, ny, nx = vari3d.shape nyo, nxo = xxo.shape vari2d = vari3d.rehape(nzi, nxi*nyi) # Restrict zone dxo = xo.ptp()/10. dyo = xo.ptp()/10. #TODO: add dxo,dxy natgridlist in regrid2d border = zip(xxo[0], yyo[0])+zip(xxo[1:-1, -1], yyo[1:-1, -1])+\ zip(xxo[-1], yyo[-1])+zip(xxo[1:-1, 0], yyo[1:-1, 0]) poly = Polygon(N.array(border)) good = N.ones(xo.shape, '?') if hasattr(vari3d, 'mask') and vari3d.mask is not MV2.nomask and ~vari3d.mask.all(): good &= ~vari2d.mask for i in xrange(nxi): for j in xrange(nyi): good[i, j] = Point((xxi[j, i], yyi[j, i])).within(poly) varitmp = vari2d.compress(good, axis=-1) xxitmp = xxi.compress(good).astype('d') yyitmp = yyi.compress(good).astype('d') del vari2d r = Natgrid(xxitmp, yyitmp, xo.astype('d').ravel(), yo.astype('d').ravel(), listOutput = 'yes') r.ext = int(ext) r.nul = -10. r.dup = 0 varo2d = N.zeros((nzi, nyo, nxo), dtype=vari.dtype) for iz in xrange(nzi): varo2d[iz] = r.rgrd(varitmp[iz]) del xxitmp, yyitmp, varitmp varo3d = varo2d.reshape(vari3d.shape[:-2]+(nyo, nxo)) del varo2d return varo3d
[docs]def cellave2d(vari, ggo, **kwargs): """Shortcut to :func:`regrid2d` call with ``method="cellave"``""" return regrid2d(vari, ggo, method="cellave", **kwargs)
remap2d = cellave2d
[docs]def interp2d(vari, ggo, method="interp", **kwargs): """Shortcut to :func:`regrid2d` call with ``method="interp"`` by default""" return regrid2d(vari, ggo, method=method, **kwargs)
def _isaxis_(var): if A.isaxis(var): return True if isinstance(var, tuple): return False if isgrid(var): return False return True
[docs]def regrid_method(gridi, grido, ratio=.55, iaxi=-1, iaxo=-1): """Guess the best regridding method for passing from ``gridi`` to ``grido`` If ``resolution(gridi) <= ratio*resolution(grido)``, ``method="cellave"`` else ``method="interp"`` The :func:`~vacumm.misc.grid.misc.resol` function is used to estimate the resolution. For grids, a resolution common to X and Y axes is estimated using the following sequence:: xres, yres = resol(grid) res = (xres**2+yres**2)**.5 :Params: - **gridi**: Grid, tuple of axes or single axis or array. - **grido**: Grid, tuple of axes or single axis or array. - **ratio**, optional: Limit ratio of output grid resolution to input grid resolution. - **iaxi/iaxo**, optional: Dimension on which to compute the resolution with :func:`~vacumm.misc.grid.misc.resol` when ``gridi`` and ``grido`` are single axes with several dimensions. ..note:: The resolution of the grids is checked in their attributes "_xres" and "_yres" before before trying to compute them. :Returns: ``'cellave'`` OR ``'linear'`` """ if _isaxis_(gridi) and _isaxis_(grido): # Axes resi = resol(gridi, axis=iaxi) reso = resol(grido, axis=iaxo) else: # Grids xresi, yresi = resol(gridi) resi = (xresi**2+yresi**2)**.5 xreso, yreso = resol(grido) reso = (xreso**2+yreso**2)**.5 # Check the ratio return 'cellave' if resi <= ratio*reso else 'linear'
[docs]class GriddedMerger(object): """Merge several gridded variables onto a grid :Params: - **grid**: Output grid - **id**, optional: Output id - **long_name**, optional: Output long name - **units**, optional: Output units - Other keywords are set a output attributes :Example: >>> gm = GriddedMerger(mygrid) >>> gm += var1 >>> gm.append(var2) >>> gm += var3 >>> gm -= var3 >>> gm.insert(0, var3) >>> print len(gm) 3 >>> print gm .... >>> myvar = gm.merge(res_ratio=.4, pad=3) """ def __init__(self, grid, id=None, long_name=None, units=None, **kwargs): self._vars = [] self.long_name = long_name self.units = units self.id = id self._var = None self.set_grid(grid) for att, val in kwargs.items(): setattr(self, '_'+att, val)
[docs] def set_grid(self, grid): """Set the grid for merging""" self._grid = get_grid(grid) self._xres, self._yres = resol(self._grid) del self._var self._var = None
[docs] def get_grid(self): """Get the grid for merging""" return self._grid
[docs] def get_lon(self): """Get the longitudes of the grid""" return self._grid.getAxis(1)
[docs] def get_lat(self): """Get the loatitudes of the grid""" return self._grid.getAxis(0)
def __len__(self): return len(self._vars) def _load_(self, var, method): # Check grid grid = var.getGrid() assert grid is not None, 'Your variable must have a grid' # Check dims if len(self): firstdims = tuple([len(a) for a in var.getAxisList()[:-2]]) assert self._firstdims == firstdims, "Incompatible number of dimension: %s. It should be: %s."%\ (self._firstdims, firstdims) else: self._firstaxes = var.getAxisList()[:-2] self._firstdims = tuple([len(a) for a in self._firstaxes]) # Method and resolution var._gm_method = method grid._xres, grid._yres = resol(grid) # Bounds for axis in var.getAxisList()[-2:]: axis.setBounds(bounds1d(axis)) return var
[docs] def add(self, *args, **kwargs): """Alias for :meth:`append`""" self.append(*args, **kwargs)
[docs] def append(self, var, method='auto', **kwargs): """Append a bathymetry to the top of the merger""" self._vars.append(self._load_(var, method, **kwargs))
def __iadd__(self, var): self.add(var) return self
[docs] def remove(self, var): if isinstance(var, int): del self._vars[i] assert var in self._vars, 'Variable not in merger' self._vars.remove(var)
def __isub__(self, var): self.remove(var) return self def __getitem__(self, idx): return self._vars[idx] def __setitem__(self, idx, var): self._vars[idx] = self._load_(var) def __delitem__(self, idx): del self._vars[idx]
[docs] def insert(self, idx, var): self._vars.insert(idx, self._load_(var))
def __str__(self): if not len(self): return 'No variables in the merger' ret = '' for i, var in enumerate(self._vars): print '\n%i) %s_n'%(i, var.id) for att in 'long_name', 'units', '_gm_method': if hasattr(var, att): res += ' %s = %s'%(att, getattr(var, att)) for att in '_gm_xres', '_gm_yres': if hasattr(var.getGrid(), att): res += ' %s = %s'%(att, getattr(var.getGrid(), att)) ret += '\n--\nTotal: %i variable(s)'%len(self) return ret
[docs] def merge(self, res_ratio=.5, pad=5, **kwargs): """Merge all the variables on to a grid - **grid**: Out put grid or axes. - *res_ratio*: Resolution ratio for choosing between cell averaging and bilinear interpolation (see: :func:`regrid_method`). - *regrid_<kwparam>*: *<kwparam>* is passed to :func:`regrid2d` for interpolation. """ assert len(self), 'You must add at least one variable to the merger' # Some useful info if the output grid lono = self.get_lon() lato = self.get_lat() xbo = meshcells(lono) ybo = meshcells(lato) xbmino = xbo.min() xbmaxo = xbo.max() ybmino = ybo.min() ybmaxo = ybo.max() grid = self.get_grid() nyo, nxo = grid.shape # Inits outmask2d = N.ones(grid.shape, '?') varo = MV2.zeros(self._firstdims+grid.shape) set_grid(varo, grid) total_cover = N.zeros(grid.shape) cover = N.zeros(grid.shape) weight = N.zeros(grid.shape) wnd = N.zeros(varo.shape) vo = MV2.zeros(varo.shape) kwregrid = kwfilter(kwargs, 'regrid') for var in self._vars[::-1]: # Axes loni = var.getLongitude() ; xi = loni.getValue() lati = var.getLatitude() ; yi = lati.getValue() nyi, nxi = var.getGrid().shape # Guess the interpolation method if var._gm_method != 'auto': method = var._gm_method else: method = regrid_method(var.getGrid(), grid, ratio=res_ratio) # Guess the grid bounds according to the method if method == 'interp': xmini = xi.min() xmaxi = xi.max() ymini = yi.min() ymaxi = yi.max() else: xmini = loni.getBounds().min() xmaxi = loni.getBounds().max() ymini = lati.getBounds().min() ymaxi = lati.getBounds().max() # Cell covering cover[:] = 0. # - limits if xmini>=xbmaxo or xmaxi<=xbmino or \ ymini>=ybmaxo or ymaxi<=ybmino: continue ix0 = N.searchsorted(xbo, xmini, 'right')-1 ix1 = N.searchsorted(xbo, xmaxi, 'left')-1 iy0 = N.searchsorted(ybo, ymini, 'right')-1 iy1 = N.searchsorted(ybo, ymaxi, 'left')-1 # - partial+full cells cslice = [slice(max(iy0, 0), iy1+1), slice(max(ix0, 0), ix1+1)] cover[cslice[0], cslice[1]] = 1. # - partial cells if ix0>=0: cover[:, ix0] *= (xbo[ix0+1]-xmini)/(xbo[ix0+1]-xbo[ix0]) if ix1<nxo: cover[:, ix1] *= (xmaxi-xbo[ix1])/(xbo[ix1+1]-xbo[ix1]) if iy0>=0: cover[iy0, :] *= (ybo[iy0+1]-ymini)/(ybo[iy0+1]-ybo[iy0]) if iy1<nyo: cover[iy1, :] *= (ymaxi-ybo[iy1])/(ybo[iy1+1]-ybo[iy1]) # - borders xpad = N.ones(nyo) ypad = N.ones(nxo) ipadmax = min(ix1, nxo-1)-max(ix0, 0) for ipad in xrange(min(pad, ipadmax+1)): xcov = (ipad+.5)*xpad/(ipadmax+.5) cover[:, ipad] *= xcov cover[:, nxo-ipad-1] *= xcov del xcov jpadmax = min(iy1, nyo-1)-max(iy0, 0) for jpad in xrange(min(pad, jpadmax+1)): ycov = (jpad+.5)*ypad/(jpadmax+.5) cover[jpad] *= ycov cover[nyo-jpad-1] *= ycov del ycov del xpad, ypad # Regridding vo[:] = regrid2d(var, grid, method, **kwregrid) # Contribution to varo cover[total_cover==1] = 0. total_cover += cover wnd[:] = N.resize(cover, varo.shape) vo[:] *= wnd varo[..., cslice[0], cslice[1]] += vo[cslice[0], cslice[1]] varo[:] = MV2.masked_where(total_cover==0, varo/total_cover) del self._var, cover, total_cover, vo, wnd self._var = varo return varo
[docs] def plot(self, **kwargs): """Merge and plot""" from ...plot import map2 if self._var is None: self.merge() return map2(self._var)
def _shiftslicenames_(shift): if shift>0: target = firsts = 'firsts' out = last = 'last' oppos = 'first' neigh1 = 'last' neigh2 = 'lastm1' firsts = 'firsts' lasts = 'lasts' else: target = firsts = 'lasts' out = last = 'first' oppos = 'last' neigh1 = 'first' neigh2 = 'firstp1' firsts = 'lasts' lasts = 'firsts' return dict(target=target, out=out, oppos=oppos, last=last, neigh1=neigh1, neigh2=neigh2, firsts=firsts, lasts=lasts)
[docs]def shift1d(var, shift=0, bmode=None, axis=-1, copy=True, shiftaxis=True, **kwargs): """Interpolate data on an axis shifted by an half cell size :Params: - **var**: array. - **shift**, optional: Shift to operate. - ``0``: No shift. - ``<0``: Shilt toward bottom of the axis. - ``>0``: Shilt toward top of the axis. - **bmode**, optional: Boundary mode. - ``None``: ``"extrap"`` if axis else ``"same"`` - ``"linear"``: Linear extrapolation. - ``"same"``: Constant extrapolation. - ``"masked"``: Mask outside data. - ``"cyclic"``: Cyclic extrapolation. - **axis**, optional: Axis on which to operate. - **copy**, optional: Always input copy data. """ # Input data if copy: if hasattr(var, 'clone'): varo = var.clone() else: varo = var.copy() else: varo = var if shift==0: return varo if shift<0: shift = -0.5 else: shift = 0.5 bmode = kwargs.get('mode', bmode) if bmode is None: bmode = "linear" if A.isaxis(var) else "same" elif bmode=="nearest": bmode = "same" elif bmode=='extrap': bmode = 'linear' ax = len(var.shape)==1 and A.isaxis(var) if ax: varf = varo.getValue() else: varf = varo xo = None if cdms2.isVariable(varo): refvar = varo.asma().copy() if shiftaxis: xo = shift1d(var.getAxis(axis), shift, bmode='extrap') else: refvar = varf.copy() # Get slice specs ss = get_axis_slices(var, axis) sn = _shiftslicenames_(shift) # Inner data varf[ss[sn['target']]] = (refvar[ss['firsts']]+refvar[ss['lasts']])*0.5 # Boundary data if bmode=='masked': varf[ss[sn['out']]] = N.ma.masked elif bmode=='cyclic': varf[ss[sn['out']]] = var[ss[sn['oppos']]] elif bmode=="same": varf[ss[sn['out']]] = refvar[ss[sn['neigh1']]] else: varf[ss[sn['out']]] = refvar[ss[sn['neigh1']]] varf[ss[sn['out']]] += abs(shift) * (refvar[ss[sn['neigh1']]]-refvar[ss[sn['neigh2']]]) del refvar # Axes if ax: varo.assignValue(varf) elif xo is not None: varo.setAxis(axis, xo) return varo
[docs]def shift2d(vari, ishift=0, jshift=0, bmode=None, copy=True, **kwargs): """Interpolate data on an grid shifted by an half cell size in X and Y X and Y are supposed to be the -1 and -2 axes of var. :Params: - **var**: array. - **[i/j]shift**, optional: Shift to operate along X/Y. - ``0``: No shift. - ``<0``: Shilt toward bottom of the axis. - ``>0``: Shilt toward top of the axis. - **bmode**, optional: Boundary mode. - ``"linear"``: Linear extrapolation. - ``"same"``: Constant extrapolation. - ``"masked"``: Mask outside data. - ``"cyclic"``: Cyclic extrapolation. - **copy**, optional: Always input copy data. """ if copy: if hasattr(vari, 'clone'): var = vari.clone() else: var = vari.copy() else: var = vari if ishift==0 and jshift==0: return var ny, nx = var.shape[-2:] ne = N.multiply.reduce(var.shape)/(nx*ny) sg = not A.isaxis(var) and cdms2.isVariable(var) and var.getGrid() is not None if sg: grido = shiftgrid(var.getGrid(), ishift=ishift, jshift=jshift) bmode = kwargs.get('mode', bmode) if bmode=='masked' and not N.ma.isMA(var): var = N.ma.asarray(var) # Slices ssx = get_axis_slices(var, -1) snx = _shiftslicenames_(ishift) ssy = get_axis_slices(var, -2) sny = _shiftslicenames_(jshift) # Shift along X only kwxshift = dict(axis=-1, bmode=bmode, copy=copy or jshift!=0, shiftaxis=not sg) if jshift==0 or ishift!=0: varx = shift1d(vari, ishift, **kwxshift) if jshift==0: if sg: set_grid(varx, grido) return varx # Shift along Y only kwyshift = dict(axis=-2, bmode = bmode if bmode!='cyclic' else 'extrap', copy=copy or ishift!=0, shiftaxis=not sg) if ishift==0 or jshift!=0: vary = shift1d(vari, jshift, **kwyshift) if ishift==0: if sg: set_grid(vary, grido) return vary # Inner Y var[:] = 0., var[ssy[sny['firsts']]] += 0.5*(varx[ssy[sny['firsts']]]+varx[ssy[sny['lasts']]]) # Top Y sstopyfirsts = merge_axis_slice(ssy[sny['last']], ssx[snx['firsts']]) sstopylasts = merge_axis_slice(ssy[sny['last']], ssx[snx['lasts']]) var[sstopyfirsts] = 0.5*(vary[sstopyfirsts]+vary[sstopylasts]) # Shift along X and Y sslast = merge_axis_slice(ssy[sny['last']], ssx[snx['last']]) sslastxy = get_axis_slices(var.ndim-1, -1) sslastx = sslastxy[snx['last']] sslasty = sslastxy[sny['last']] varlastx = shift1d(varx[ssx[snx['last']]], ishift, **kwxshift) #; del varx var[sslast] += 0.5*varlastx[sslastx] #; del varlastx kwyshift['axis'] = -1 varlasty = shift1d(vary[ssy[sny['last']]], jshift, **kwyshift) #; del vary var[sslast] += 0.5*varlasty[sslasty] #; del varlastx # Grid for MV2 arrays if sg: set_grid(var, grido) return var
[docs]def shiftgrid(gg, ishift=0, jshift=0, bmode='linear', **kwargs): """Shift a grid of an half cell :Params: - **gg**: cdms2 grid. - **i/jshift**: Fraction cell to shift. """ xx, yy = get_xy(gg) bmode = kwargs.get('mode', bmode) if len(xx.shape)==2: xs = shift2d(xx, ishift=ishift, jshift=jshift, bmode=bmode) ys = shift2d(yy, ishift=ishift, jshift=jshift, bmode=bmode) else: xs = shift1d(xx, ishift, bmode=bmode) ys = shift1d(yy, jshift, bmode=bmode) if isgrid(gg) or cdms2.isVariable(gg): return create_grid(xs, ys) return xs, ys
def _extend_init_(var, new_shape, mode): """Some inits for extend1d and extend2d""" # In and out data if A.isaxis(var): datatype = 'axis' varm = var.getValue() if len(var.shape)==1: datatype = 'axis1d' varf = N.zeros(new_shape) else: datatype = 'axis2d' zeros = N.ma.zeros if N.ma.isMA(var) or mode=='masked' else N.zeros varf = zeros(new_shape) elif cdms2.isVariable(var): varm = var.asma() varf = N.zeros(new_shape) datatype = 'mv' else: datatype = 'ma' zeros = N.ma.zeros if N.ma.isMA(var) or mode=='masked' else N.zeros varm = var varf = N.ma.zeros(new_shape) # Extrapolation mode if mode is None: mode = "extrap" if A.isaxis(var) else "same" elif mode=="nearest": mode = "same" elif mode=='linear': mode = 'extrap' return varm, varf, datatype, mode
[docs]def extend1d(var, ext=0, mode=None, axis=-1, copy=False, num=False): """Extrapolate data on an axis :Params: - **var**: Array. - **ext**, optional: Size of extension. If a tuple, it gives left and right extensions, else the same for both. - **mode**, optional: Interpolation mode for boundary point outside initial positions. - ``None``: ``"extrap"`` if axis else ``"same"`` - ``"extrap"`` or ``"linear"``: Linear extrapolation. - ``"same"``: Constant extrapolation. - ``"cyclic"``: Cyclic extrapolation. - ``"masked"``: Masked. - **axis**, optional: Axis on which to operate. - **copy**, optional: Always input copy data. """ # Size of extension if not isinstance(ext, (list, tuple)): ext = (ext, ext) if ext[0]==0 and ext[1]==0: if copy: return var.clone() if hasattr(var, 'clone') else var.copy() return var # Data and mode varo_shape = list(var.shape) if axis<0: axis = len(varo_shape)+axis ni = varo_shape[axis] no = ni+ext[0]+ext[1] varo_shape[axis] = no varm, varf, datatype, mode = _extend_init_(var, varo_shape, mode) # Get slice specs ss = get_axis_slices(var, axis, extinner=slice(ext[0], -ext[1] or None), extleft=slice(0, ext[0]), extright_for_left=slice(-ext[0], None), extright=slice(-ext[1], None), extleft_for_right=slice(0, ext[1]), ) # Unchanged data varf[ss['extinner']] = varm # Left boundary if ext[0]: if mode=='masked': varf[ss['extleft']] = N.ma.masked elif mode=='cylic': varf[ss['extleft']] = varm[ss['extright_for_left']] else: if mode=='extrap': dv = varm[ss['first']]-varm[ss['firstp1']] for i in xrange(1, ext[0]+1): ss['extleft'][axis] = ext[0]-i varf[ss['extleft']] = varm[ss['first']] if mode=='extrap': varf[ss['extleft']] += i*dv # Right boundary if ext[1]: if mode=='masked': varf[ss['extright']] = N.ma.masked elif mode=='cylic': varf[ss['extright']] = varm[ss['extleft_for_right']] else: if mode=='extrap': dv = varm[ss['last']]-varm[ss['lastm1']] for i in xrange(1, ext[1]+1): ss['extright'][axis] = ext[0]+ni+i-1 varf[ss['extright']] = varm[ss['last']] if mode=='extrap': varf[ss['extright']] += i*dv del varm # Format if not num: if datatype=='axis1d': varo = A.create(varf) cp_atts(var, varo) elif datatype=='axis2d': varo = create_axes2d(varf) cp_atts(var, varo) for iaxis in 0, 1: cp_atts(var.getAxis(iaxis), varo.getAxis(iaxis)) elif datatype=='mv': varo = MV2.asarray(varf) cp_atts(var, varo) gridi = var.getGrid() for iaxis in xrange(varo.ndim): axi = var.getAxis(iaxis) if axis!=iaxis: varo.setAxis(iaxis, axi) elif gridi is None: axo = extend1d(axi, ext, mode='extrap') cp_atts(axi, axo) if gridi is not None: if axis>=var.ndim-2: iext = ext if axis==var.ndim-1 else 0 jext = ext if axis==var.ndim-2 else 0 grido = extendgrid(gridi, iext=iext, jext=jext) set_grid(varo, extendgrid(gridi, iext=iext, jext=jext)) else: set_grid(varo, gridi) else: varo = varf else: varo = varf return varo
[docs]def extend2d(vari, iext=0, jext=0, mode=None, copy=False): """Interpolate data on an grid shifted by an half cell size in X and Y X and Y are supposed to be the -1 and -2 axes of var. :Params: - **var**: Array. - **i/jext**, optional: Size of extension along i/j. If a tuple,it gives left and right extensions, else the same for both. - **mode**, optional: Interpolation mode for boundary point outside initial positions. - ``None``: ``"extrap"`` if axis else ``"same"`` - ``"extrap"`` or ``"linear"``: Linear extrapolation. - ``"same"``: Constant extrapolation. - ``"masked"``: Masked. - **copy**, optional: Always input copy data. """ # Size of extensions if not isinstance(iext, (list, tuple)): iext = (iext, iext) if not isinstance(jext, (list, tuple)): jext = (jext, jext) # No extension if iext[0]==0 and iext[1]==0 and jext[0]==0 and jext[1]==0: if copy: return vari.clone() if hasattr(vari, 'clone') else vari.copy() return vari # Single axis if iext[0]==0 and iext[1]==0: return extend1d(vari, ext=jext, axis=-2, mode=mode, copy=copy) elif jext[0]==0 and jext[1]==0: return extend1d(vari, ext=iext, axis=-1, mode=mode, copy=copy) # X then Y varo = extend1d(vari, ext=iext, axis=-1, mode=mode, copy=False) varo = extend1d(varo, ext=jext, axis=-2, mode=mode, copy=False) # Merge with Y then X tmp = extend1d(vari, ext=jext, axis=-2, mode=mode, copy=False, num=True) varo += extend1d(tmp, ext=iext, axis=-1, mode=mode, copy=False, num=True) del tmp varo[:] *= 0.5 return varo
[docs]def extendgrid(gg, iext=0, jext=0, mode='extrap'): """Extrapolate a grid :Params: - **gg**: cdms2 grid. - **i/jext**: Size of extrapolation along i/j. - **mode**, optional: Interpolation mode for boundary point outside initial positions. - ``"extrap"``: Linear extrapolation. - ``"same"``: Constant extrapolation. - ``"masked"``: Masked. """ xx, yy = get_xy(gg) if len(xx.shape)==2: xs = extend2d(xx, iext=iext, jext=jext, mode=mode) ys = extend2d(yy, iext=iext, jext=jext, mode=mode) else: xs = extend1d(xx, iext, mode=mode) ys = extend1d(yy, jext, mode=mode) if isgrid(gg) or cdms2.isVariable(gg): return create_grid(xs, ys) return xs, ys
[docs]class CDATRegridder(object): """Regridding using CDAT regridders .. note:: This code is adapted from the :meth:`regrid` method of MV2 arrays. :Params: - **fromgrid**: Input grid, or variable with a grid. - **togrid**: Output grid. - **tool**, optional: One of ``"esmf"``, ``"libcf"`` and ``"regrid2"``. - **method**, optional: One of ``"linear"``, ``"path"``, ``"conservative"`` and ``"cellave"``. """ def __init__(self, fromgrid, togrid, missing=None, order=None, mask=None, **keywords): if cdms2.isVariable(fromgrid) and not isgrid(fromgrid): fromvar = fromgrid fromgrid = fromvar.getGrid() else: fromvar = None # Verify bounds for g in fromgrid, togrid: for xy in g.getLongitude(), g.getLatitude(): if xy.getBounds() is None: func = bounds1d if len(xy.shape)==1 else bounds2d xy.setBounds(func(xy)) # Curved? curved = len(fromgrid.getLatitude().shape) > 1 or len(togrid.getLatitude().shape) > 1 # Default regridTool = None regridMethod = 'linear' # # Some keywords for longitudes # for lon in fromgrid.getAxis(-1), fromgrid.getLongitude(): # if lon.attributes.get('topology', None) == 'circular': # keywords['periodicity'] = 1 # for the ESMF regridders # keywords['mkCyclic'] = 1 # for LibCF regridder # break # User request # - method userSpecifiesMethod = False for rm in 'rm', 'method', 'regridmethod', 'regrid_method', 'regridMethod': if keywords.has_key(rm): if keywords[rm] is not None: regridMethod = keywords[rm] userSpecifiesMethod = True del keywords[rm] # - tool for rt in 'rt', 'tool', 'regridtool', 'regrid_tool', 'regridTool': if keywords.has_key(rt): if keywords[rt] is not None: regridTool = keywords[rt] del keywords[rt] # The method and grid determine the tool if not re.search('^(cons|cell)', regridMethod, re.I): regridTool = 'esmf' elif regridTool is None: if not curved: regridTool = 'regrid2' else: regridTool = 'esmf' # Make sure the tool can do it if re.search('^regrid', regridTool, re.I) and curved: regridTool = 'esmf' # Clean regrid tool names regridTool = regrid2d_tool_name(regridTool) self.tool = regridTool # Conservative versus cellave self._getdstfracs = False self._weidstfracs = 0 self._mskdstfracs = False if re.search('esmf',regridTool, re.I) and re.search('(cons|cell)', regridMethod, re.I): if re.search('cell', regridMethod, re.I): regridMethod = 'conservative' self._weidstfracs = -1 self._getdstfracs = True self._mskdstfracs = True elif re.search('^regrid',regridTool, re.I): if re.search('cell', regridMethod, re.I): regridMethod = 'conservative' else: self._weidstfracs = 1 self._getdstfracs = True self._divdstfracs = True # Clean method names regridMethod = regrid2d_method_name(regridMethod, raiseerr=False) if regridMethod=='bilinear': regridMethod = 'linear' elif re.search('patch',regridMethod, re.I): regridMethod = 'patch' elif regridMethod=='conserv': regridMethod = 'conservative' else: raise VACUMMError('Wrong CDATRegridder regrid method. Please one of: '+ 'linear, conserv, patch, cellave') self.method = regridMethod # Setup regridder if regridTool=='regrid2': # Origignal CDMS regridder keywords['returnTuple'] = self._getdstfracs from regrid2 import Horizontal self._regridder = Horizontal(fromgrid, togrid) self._kwargs = dict(missing=missing, order=order, mask=mask, **keywords) else: # ESMF or LIBCF regridders # Source mask srcGridMask = None dtype = N.float64 if fromvar is not None: # set the source mask if a mask is defined with the source data if N.any(fromvar.mask == True): srcGridMask = cdms2.avariable.getMinHorizontalMask(fromvar) dtype = fromvar.dtype if dtype.kind!='f': dtype = N.float64 # if regridMethod=='conservative': # # Hack for conservative # if srcGridMask is None: # srcGridMask = N.zeros(fromgrid.shape, '?') # srcGridMask[:, -1] = True # DstAreaFractions for cell if self._getdstfracs: diag = keywords.get('diag', {}) diag['dstAreaFractions'] = None keywords['diag'] = diag # compute the interpolation weights self._regridder = cdms2.mvCdmsRegrid.CdmsRegrid(fromgrid, togrid, dtype = dtype, regridMethod = regridMethod, regridTool = regridTool, srcGridMask = srcGridMask, srcGridAreas = None, dstGridMask = None, dstGridAreas = None, **keywords) self._kwargs = keywords
[docs] def regrid(self, vari, weidstfracs=None, check_mask=None, csvhack=False, **keywords): """Regrid the variable :Params: - **vari**: Input variable. - **weidstfracs**, optional: Specify how to apply weights computed using destination fractions. Divide if <0, else multiply. It is not used with linear and match methods. - **check_mask**, optional: Mask point using masked data (the algo interpolate the mask and check level 0.999). MUST BE IMPROVED! If None, mask is checked if tool!='regrid2' - **csvhack**, optional: Hack to prevent a bug with conservative-like method of the ESMF regridder. Use it if you have strange result in the output most right longitude. .. warning:: The algo mask the must right longitude of input data before processing. """ # Float type if vari.dtype.kind!='f': vari = vari.astype('d') # Hack for conservative method bug if csvhack and self.method=='conservative': mask = N.zeros(vari.shape, '?') mask[..., -1] = True vari = MV2.masked_where(mask, vari) del mask # Regrid kwargs = self._kwargs.copy() kwargs.update(keywords) res = self._regridder(vari, **kwargs) # Outputs if not isinstance(res, tuple): varo = res if self._getdstfracs: wo = N.resize(kwargs['diag']['dstAreaFractions'], varo.shape) else: # regrid2 old regridder varo, wo = res # Working with destination fractions if self._getdstfracs: mask = wo==0. # Masking if self._mskdstfracs: varo[:] = MV2.masked_where(mask, varo, copy=0) # Scaling if weidstfracs is None: weidstfracs = self._weidstfracs if weidstfracs: if weidstfracs==1: varo[:] *= wo else: wo[mask] = 1. varo[:] /= wo del wo, mask # Check mask if check_mask is None: check_mask = self.tool != 'regrid2' if check_mask: good = vari.clone() good[:] = 1-N.ma.getmaskarray(vari) goodo = self.regrid(good, weidstfracs=weidstfracs, check_mask=False, csvhack=csvhack, **keywords) del good if isinstance(goodo, tuple): goodo = goodo[0] goodo = goodo.filled(0.) varo[:] = MV2.masked_where((goodo<0.9999), varo, copy=0) ; del goodo # Finalize cp_atts(vari, varo) return varo
__call__ = regrid
[docs]class CurvedInterpolator(object): """Interpolator from a curved grid to random points or another grid :Params: - **fromgrid**: Input grid, or variable with a grid. - **topts**: Output coordinates or grid. It a tuple of coordinates, it is assumed that it refers to random points, and NOT a grid. - **g2g**, optional: Force the interpretation of ``topts`` as a grid or axes of a grid. :Examples: >>> interpolator = CurvedInterpolator(ssti.getGrid(), (lono, lato)) # lono/lato 1D >>> interpolator = CurvedInterpolator(ssti.getGrid(), grido) # grid >>> ssto = interpolator(ssti) """ valid_methods = ['bilinear', 'nearest', 'dstwgt'] def __init__(self, fromgrid, topts, g2g=False): # Input coordinates xxi, yyi = get_xy(fromgrid, mesh=True, num=True) self._shapei = xxi.shape # Output coordinates if cdms2.isVariable(topts): topts = topts.getGrid() if g2g: topts = get_grid(topts) if isgrid(topts): xxo, yyo = meshgrid(topts.getLongitude(), topts.getLatitude()) xo = xxo.ravel() yo = yyo.ravel() self._grido = topts self._shapeo = topts.shape else: xo, yo = topts if len(xo)!=len(yo): raise VACUMMError('Output axes must have the same length: %i!=%i'% (len(xo), len(yo))) xo = N.asarray(xo) yo = N.asarray(yo) self._grido = None self._shapeo = xo.shape # Find relative positions self._p, self._q = _curv2rel_(xxi, yyi, xo, yo)
[docs] def interp(self, vari, method='bilinear'): """Interpolate :Params: - **vari**: Variable to interpolate. - **method**, optional: Interpolation method. One of : %s. """%', '.join(self.valid_methods) # Method and function method = regrid2d_method_name(method) if method not in self.valid_methods: raise VACUMMError('Invalid interpolation method. Choose of: '+ ', '.join(self.valid_methods)) if method=='bilinear': method = 'bilin' func = eval('_%s2dto1dc_reduc_'%method) # Interpolate mv = vari.get_fill_value() zzi = vari.asma() if cdms2.isVariable(vari) else N.ma.asarray(vari) zzi = zzi.reshape((-1, )+self._shapei).filled(mv) zo = func(self._p, self._q, zzi, mv).reshape(vari.shape[:-2]+self._shapeo) zo = N.ascontiguousarray(zo) varo = N.ma.masked_values(zo, mv) # Finalize the cdms variable if cdms2.isVariable(vari): varo = MV2.asarray(varo) cp_atts(vari, varo) for i, ax in enumerate(vari.getAxisList()[:-2]): varo.setAxis(i, ax) if self._grido: set_grid(varo, self._grido) return varo
regrid = __call__ = interp
def _monotonise_(vari, axes, targets=None, back=False, subdims=None): if targets is None: targets = range(-len(axes), 0) if not isinstance(targets, list): targets = [targets] if not targets: if back: return vari return vari, axes n = vari.ndim targets = [(t if t < 0 else t-n) for t in targets] axes = [ax[:] for ax in axes] if not back: oaxes = list(axes) varo = vari for tg in targets: ax = axes[tg] # # 2D case # #FIXME: add case when ax is 2d but not curvilinear like variable depths # ash = ax.shape # if len(ash)==2: # if min(ash)>1: # continue # ax = ax.ravel() if ax.size>1: subdim = (subdims[tg] if isinstance(subdims, dict) and tg in subdims else -1) if (N.ma.diff(ax, axis=subdim)<0).any(): sel = varo.ndim*[slice(None)] sel[tg] = slice(None, None, -1) varo = varo[tuple(sel)] if not back: sel = ax.ndim*[slice(None)] sel[subdim] = slice(None, None, -1) oaxes[tg] = ax[tuple(sel)] #USE SUBDIM # ax.shape = ash if back: return varo return varo, oaxes ###################################################################### ######################################################################