The fatiando package has been deprecated. Please check out the new tools in the Fatiando a Terra website: www.fatiando.org

Source code for fatiando.utils

"""
Miscellaneous utility functions and classes.

**Mathematical functions**

* :func:`~fatiando.utils.normal`
* :func:`~fatiando.utils.gaussian`
* :func:`~fatiando.utils.gaussian2d`
* :func:`~fatiando.utils.safe_solve`
* :func:`~fatiando.utils.safe_dot`
* :func:`~fatiando.utils.safe_diagonal`
* :func:`~fatiando.utils.safe_inverse`

**Unit conversion**

* :func:`~fatiando.utils.si2mgal`
* :func:`~fatiando.utils.mgal2si`
* :func:`~fatiando.utils.si2eotvos`
* :func:`~fatiando.utils.eotvos2si`
* :func:`~fatiando.utils.si2nt`
* :func:`~fatiando.utils.nt2si`

**Coordinate system conversions**

* :func:`~fatiando.utils.sph2cart`

**Others**

* :func:`~fatiando.utils.fromimage`: Load a matrix from an image file
* :func:`~fatiando.utils.contaminate`: Contaminate a vector with pseudo-random
  Gaussian noise
* :func:`~fatiando.utils.dircos`: Get the 3 coordinates of a unit vector
* :func:`~fatiando.utils.ang2vec`: Convert intensity, inclination and
  declination to a 3-component vector
* :func:`~fatiando.utils.vecnorm`: Get the norm of a vector or list of vectors
* :func:`~fatiando.utils.vecmean`: Take the mean array out of a list of arrays
* :func:`~fatiando.utils.vecstd`: Take the standard deviation array out of a
  list of arrays
* :class:`~fatiando.utils.SparseList`: Store only non-zero elements on an
  immutable list
* :func:`~fatiando.utils.sec2hms`: Convert seconds to hours, minutes, and
  seconds
* :func:`~fatiando.utils.sec2year`: Convert seconds to Julian years
* :func:`~fatiando.utils.year2sec`: Convert Julian years to seconds

----

"""
import math

import numpy
import scipy.sparse
import scipy.sparse.linalg
import scipy.misc
import PIL.Image

from . import constants, gridder


[docs]def fromimage(fname, ranges=None, shape=None): """ Load an array of normalized gray-scale values from an image file. The values will be in the range [0, 1]. The shape of the array is the shape of the image (ny, nx), i.e., number of pixels in vertical (height) and horizontal (width) dimensions. Parameters: * fname : str Name of the image file * ranges : [vmax, vmin] = floats If not ``None``, will set the gray-scale values to this range. * shape : (ny, nx) If not ``None``, will interpolate the array to match this new shape Returns: * values : 2d-array The array of gray-scale values """ image = scipy.misc.fromimage(PIL.Image.open(fname), flatten=True) # Invert the color scale and normalize values = (image.max() - image) / numpy.abs(image).max() if ranges is not None: vmin, vmax = ranges values *= vmax - vmin values += vmin if shape is not None and tuple(shape) != values.shape: ny, nx = values.shape X, Y = numpy.meshgrid(range(nx), range(ny)) values = gridder.interp(X.ravel(), Y.ravel(), values.ravel(), shape)[2].reshape(shape) return values
[docs]def safe_inverse(matrix): """ Calculate the inverse of a matrix using an apropriate algorithm. Uses the standard :func:`numpy.linalg.inv` if *matrix* is dense. If it is sparse (from :mod:`scipy.sparse`) then will use :func:`scipy.sparse.linalg.inv`. Parameters: * matrix : 2d-array The matrix Returns: * inverse : 2d-array The inverse of *matrix* """ if scipy.sparse.issparse(matrix): return scipy.sparse.linalg.inv(matrix) else: return numpy.linalg.inv(matrix)
[docs]def safe_solve(matrix, vector): """ Solve a linear system using an apropriate algorithm. Uses the standard :func:`numpy.linalg.solve` if both *matrix* and *vector* are dense. If any of the two is sparse (from :mod:`scipy.sparse`) then will use the Conjugate Gradient Method (:func:`scipy.sparse.cgs`). Parameters: * matrix : 2d-array The matrix defining the linear system * vector : 1d or 2d-array The right-side vector of the system Returns: * solution : 1d or 2d-array The solution of the linear system """ if scipy.sparse.issparse(matrix) or scipy.sparse.issparse(vector): estimate, status = scipy.sparse.linalg.cgs(matrix, vector) if status >= 0: return estimate else: raise ValueError('CGS exited with input error') else: return numpy.linalg.solve(matrix, vector)
[docs]def safe_dot(a, b): """ Make the dot product using the appropriate method. If *a* and *b* are dense, will use :func:`numpy.dot`. If either is sparse (from :mod:`scipy.sparse`) will use the multiplication operator (i.e., \*). Parameters: * a, b : array or matrix The vectors/matrices to take the dot product of. Returns: * prod : array or matrix The dot product of *a* and *b* """ if scipy.sparse.issparse(a) or scipy.sparse.issparse(b): return a * b else: return numpy.dot(a, b)
[docs]def safe_diagonal(matrix): """ Get the diagonal of a matrix using the appropriate method. Parameters: * matrix : 2d-array, matrix, sparse matrix The matrix... Returns: * diag : 1d-array A numpy array with the diagonal of the matrix """ if scipy.sparse.issparse(matrix): return numpy.array(matrix.diagonal()) else: return numpy.diagonal(matrix).copy()
[docs]def vecnorm(vectors): """ Get the l2 norm of each vector in a list. Use this to get, for example, the magnetization intensity from a list of magnetization vectors. Parameters: * vectors : list of arrays The vector Returns: * norms : list The norms of the vectors Examples:: >>> v = [[1, 1, 1], [2, 2, 2], [3, 3, 3]] >>> print vecnorm(v) [ 1.73205081 3.46410162 5.19615242] """ norm = numpy.sqrt(sum(i ** 2 for i in numpy.transpose(vectors))) return norm
[docs]def sph2cart(lon, lat, height): """ Convert spherical coordinates to Cartesian geocentric coordinates. Parameters: * lon, lat, height : floats Spherical coordinates. lon and lat in degrees, height in meters. height is the height above mean Earth radius. Returns: * x, y, z : floats Converted Cartesian coordinates """ d2r = numpy.pi / 180.0 radius = constants.MEAN_EARTH_RADIUS + height x = numpy.cos(d2r * lat) * numpy.cos(d2r * lon) * radius y = numpy.cos(d2r * lat) * numpy.sin(d2r * lon) * radius z = numpy.sin(d2r * lat) * radius return x, y, z
[docs]def si2nt(value): """ Convert a value from SI units to nanoTesla. Parameters: * value : number or array The value in SI Returns: * value : number or array The value in nanoTesla """ return value * constants.T2NT
[docs]def nt2si(value): """ Convert a value from nanoTesla to SI units. Parameters: * value : number or array The value in nanoTesla Returns: * value : number or array The value in SI """ return value / constants.T2NT
[docs]def si2eotvos(value): """ Convert a value from SI units to Eotvos. Parameters: * value : number or array The value in SI Returns: * value : number or array The value in Eotvos """ return value * constants.SI2EOTVOS
[docs]def eotvos2si(value): """ Convert a value from Eotvos to SI units. Parameters: * value : number or array The value in Eotvos Returns: * value : number or array The value in SI """ return value / constants.SI2EOTVOS
[docs]def si2mgal(value): """ Convert a value from SI units to mGal. Parameters: * value : number or array The value in SI Returns: * value : number or array The value in mGal """ return value * constants.SI2MGAL
[docs]def mgal2si(value): """ Convert a value from mGal to SI units. Parameters: * value : number or array The value in mGal Returns: * value : number or array The value in SI """ return value / constants.SI2MGAL
[docs]def vec2ang(vector): """ Convert a 3-component vector to intensity, inclination and declination. .. note:: Coordinate system is assumed to be x->North, y->East, z->Down. Inclination is positive down and declination is measured with respect to x (North). Parameter: * vector : array = [x, y, z] The vector Returns: * [intensity, inclination, declination] : floats The intensity, inclination and declination (in degrees) Examples:: >>> s = vec2ang([1.5, 1.5, 2.121320343559643]) >>> print "%.3f %.3f %.3f" % tuple(s) 3.000 45.000 45.000 """ intensity = numpy.linalg.norm(vector) r2d = 180. / numpy.pi x, y, z = vector declination = r2d * numpy.arctan2(y, x) inclination = r2d * numpy.arcsin(z / intensity) return [intensity, inclination, declination]
[docs]def ang2vec(intensity, inc, dec): """ Convert intensity, inclination and declination to a 3-component vector .. note:: Coordinate system is assumed to be x->North, y->East, z->Down. Inclination is positive down and declination is measured with respect to x (North). Parameter: * intensity : float or array The intensity (norm) of the vector * inc : float The inclination of the vector (in degrees) * dec : float The declination of the vector (in degrees) Returns: * vec : array = [x, y, z] The vector Examples:: >>> import numpy >>> print ang2vec(3, 45, 45) [ 1.5 1.5 2.12132034] >>> print ang2vec(numpy.arange(4), 45, 45) [[ 0. 0. 0. ] [ 0.5 0.5 0.70710678] [ 1. 1. 1.41421356] [ 1.5 1.5 2.12132034]] """ return numpy.transpose([intensity * i for i in dircos(inc, dec)])
[docs]def dircos(inc, dec): """ Returns the 3 coordinates of a unit vector given its inclination and declination. .. note:: Coordinate system is assumed to be x->North, y->East, z->Down. Inclination is positive down and declination is measured with respect to x (North). Parameter: * inc : float The inclination of the vector (in degrees) * dec : float The declination of the vector (in degrees) Returns: * vect : list = [x, y, z] The unit vector """ d2r = numpy.pi / 180. vect = [numpy.cos(d2r * inc) * numpy.cos(d2r * dec), numpy.cos(d2r * inc) * numpy.sin(d2r * dec), numpy.sin(d2r * inc)] return vect
[docs]def vecmean(arrays): """ Take the mean array out of a list of arrays. Parameter: * arrays : list List of arrays Returns: * mean : array The mean of each element in the arrays Example:: >>> print vecmean([[1, 1, 2], [2, 3, 5]]) [ 1.5 2. 3.5] """ return numpy.mean(arrays, axis=0)
[docs]def vecstd(arrays): """ Take the standard deviation array out of a list of arrays. Parameter: * arrays : list List of arrays Returns: * std : array Standard deviation of each element in the arrays Example:: >>> print vecstd([[1, 1, 2], [2, 3, 5]]) [ 0.5 1. 1.5] """ return numpy.std(arrays, axis=0)
[docs]class SparseList(object): """ Store only non-zero elements on an immutable list. Can iterate over and access elements just like if it were a list. Parameters: * size : int Size of the list. * elements : dict Dictionary used to initialize the list. Keys are the index of the elements and values are their respective values. Example:: >>> l = SparseList(5) >>> l[3] = 42.0 >>> print len(l) 5 >>> print l[1], l[3] 0.0 42.0 >>> l[1] += 3.0 >>> for i in l: ... print i, 0.0 3.0 0.0 42.0 0.0 >>> l2 = SparseList(4, elements={1:3.2, 3:2.8}) >>> for i in l2: ... print i, 0.0 3.2 0.0 2.8 """ def __init__(self, size, elements=None): self.size = size self.i = 0 if elements is None: self.elements = {} else: self.elements = elements def __str__(self): return str(self.elements) def __len__(self): return self.size def __iter__(self): self.i = 0 return self def __getitem__(self, index): if index < 0: index = self.size + index if index >= self.size or index < 0: raise IndexError('index out of range') return self.elements.get(index, 0.) def __setitem__(self, key, value): if key >= self.size: raise IndexError('index out of range') self.elements[key] = value def next(self): if self.i == self.size: raise StopIteration() res = self.__getitem__(self.i) self.i += 1 return res
[docs]def sec2hms(seconds): """ Convert seconds into a string with hours, minutes and seconds. Parameters: * seconds : float Time in seconds Returns: * time : str String in the format ``'%dh %dm %2.5fs'`` Example:: >>> print sec2hms(62.2) 0h 1m 2.20000s >>> print sec2hms(3862.12345678) 1h 4m 22.12346s """ h = int(seconds / 3600) m = int((seconds - h * 3600) / 60) s = seconds - h * 3600 - m * 60 return '%dh %dm %2.5fs' % (h, m, s)
[docs]def sec2year(seconds): """ Convert seconds into decimal Julian years. Julian years have 365.25 days. Parameters: * seconds : float Time in seconds Returns: * years : float Time in years Example:: >>> print sec2year(31557600) 1.0 """ return float(seconds) / 31557600.0
[docs]def year2sec(years): """ Convert decimal Julian years into seconds. Julian years have 365.25 days. Parameters: * years : float Time in years Returns: * seconds : float Time in seconds Example:: >>> print year2sec(1) 31557600.0 """ return 31557600.0 * float(years)
[docs]def contaminate(data, stddev, percent=False, return_stddev=False, seed=None): r""" Add pseudorandom gaussian noise to an array. Noise added is normally distributed with zero mean. Parameters: * data : array or list of arrays Data to contaminate * stddev : float or list of floats Standard deviation of the Gaussian noise that will be added to *data* * percent : True or False If ``True``, will consider *stddev* as a decimal percentage and the standard deviation of the Gaussian noise will be this percentage of the maximum absolute value of *data* * return_stddev : True or False If ``True``, will return also the standard deviation used to contaminate *data* * seed : None or int Seed used to generate the pseudo-random numbers. If `None`, will use a different seed every time. Use the same seed to generate the same random sequence to contaminate the data. Returns: if *return_stddev* is ``False``: * contam : array or list of arrays The contaminated data array else: * results : list = [contam, stddev] The contaminated data array and the standard deviation used to contaminate it. Examples: >>> import numpy as np >>> data = np.ones(5) >>> noisy = contaminate(data, 0.1, seed=0) >>> print noisy [ 1.03137726 0.89498775 0.95284582 1.07906135 1.04172782] >>> noisy, std = contaminate(data, 0.05, seed=0, percent=True, ... return_stddev=True) >>> print std 0.05 >>> print noisy [ 1.01568863 0.94749387 0.97642291 1.03953067 1.02086391] >>> data = [np.zeros(5), np.ones(3)] >>> noisy = contaminate(data, [0.1, 0.2], seed=0) >>> print noisy[0] [ 0.03137726 -0.10501225 -0.04715418 0.07906135 0.04172782] >>> print noisy[1] [ 0.81644754 1.20192079 0.98163167] """ numpy.random.seed(seed) # Check if dealing with an array or list of arrays if not isinstance(stddev, list): stddev = [stddev] data = [data] contam = [] for i in xrange(len(stddev)): if stddev[i] == 0.: contam.append(data[i]) continue if percent: stddev[i] = stddev[i] * max(abs(data[i])) noise = numpy.random.normal(scale=stddev[i], size=len(data[i])) # Subtract the mean so that the noise doesn't introduce a systematic # shift in the data noise -= noise.mean() contam.append(numpy.array(data[i]) + noise) numpy.random.seed() if len(contam) == 1: contam = contam[0] stddev = stddev[0] if return_stddev: return [contam, stddev] else: return contam
[docs]def normal(x, mean, std): """ Normal distribution. .. math:: N(x,\\bar{x},\sigma) = \\frac{1}{\sigma\sqrt{2 \pi}} \exp\\left(-\\frac{(x-\\bar{x})^2}{\sigma^2}\\right) Parameters: * x : float or array Value at which to calculate the normal distribution * mean : float The mean of the distribution :math:`\\bar{x}` * std : float The standard deviation of the distribution :math:`\sigma` Returns: * normal : array Normal distribution evaluated at *x* """ factor = (std * numpy.sqrt(2 * numpy.pi)) return numpy.exp(-1 * ((mean - x) / std) ** 2) / factor
[docs]def gaussian(x, mean, std): """ Non-normalized Gaussian function .. math:: G(x,\\bar{x},\sigma) = \exp\\left(-\\frac{(x-\\bar{x})^2}{\sigma^2} \\right) Parameters: * x : float or array Values at which to calculate the Gaussian function * mean : float The mean of the distribution :math:`\\bar{x}` * std : float The standard deviation of the distribution :math:`\sigma` Returns: * gauss : array Gaussian function evaluated at *x* """ return numpy.exp(-1 * ((mean - x) / std) ** 2)
[docs]def gaussian2d(x, y, sigma_x, sigma_y, x0=0, y0=0, angle=0.0): """ Non-normalized 2D Gaussian function Parameters: * x, y : float or arrays Coordinates at which to calculate the Gaussian function * sigma_x, sigma_y : float Standard deviation in the x and y directions * x0, y0 : float Coordinates of the center of the distribution * angle : float Rotation angle of the gaussian measure from the x axis (north) growing positive to the east (positive y axis) Returns: * gauss : array Gaussian function evaluated at *x*, *y* """ theta = -1 * angle * numpy.pi / 180. tmpx = 1. / sigma_x ** 2 tmpy = 1. / sigma_y ** 2 sintheta = numpy.sin(theta) costheta = numpy.cos(theta) a = tmpx * costheta + tmpy * sintheta ** 2 b = (tmpy - tmpx) * costheta * sintheta c = tmpx * sintheta ** 2 + tmpy * costheta ** 2 xhat = x - x0 yhat = y - y0 return numpy.exp(-(a * xhat ** 2 + 2. * b * xhat * yhat + c * yhat ** 2))