Source code for epygram.util

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import math
import copy
import numpy

from footprints import FootprintBase

from epygram import config, epygramError

[docs]class RecursiveObject(object): """ Generic abstract class implementing useful recursive properties: - display of object: the *__str__* method returns str(attr) for each of the object's attributes, with automatical indentation for readability. - test of (in)equality: the a == b test will be true if a and b have the same attributes and *a.attr == b.attr* for each attribute. """ def _strItem(self, item, reclevel=1): """ Recursive display of object attributes. """ offset = "".rjust(reclevel*4) itemstring = "" if isinstance(item, FootprintBase): itemstring += item.__class__.__name__+" containing:" for attr in item.__dict__.keys(): if attr == '_attributes': for i in item.__dict__[attr].keys(): itemstring += "\n"+offset+i+": "+self._strItem(item.__dict__[attr][i], reclevel+1) elif attr not in ('_puredict', '_observer'): # not supposed to be accessed from outside itemstring += "\n"+offset+attr+": "+self._strItem(item.__dict__[attr], reclevel+1) elif isinstance(item, list): if isinstance(item[0], RecursiveObject): itemstring = "[" + repr(item[0]) + ", ... (" + str(len(item)) + " objects)]" else: itemstring = str(numpy.array(item)) elif isinstance(item, RecursiveObject): itemstring += item.__class__.__name__+" containing:" for attr in item.__dict__.keys(): itemstring += "\n"+offset+attr+": "+self._strItem(item.__dict__[attr], reclevel+1) elif isinstance(item, dict): for key in sorted(item.keys()): itemstring += "\n"+offset+key+": "+self._strItem(item[key], reclevel+1) else: itemstring = str(item) return itemstring def __str__(self): """ Recursive display of object: displays each of its attributes with indentation. """ return self._strItem(self) def __eq__(self, other): """ Test of equality by recursion on the object's attributes. """ if self.__class__ == other.__class__ and self.__dict__.keys() == other.__dict__.keys(): ok = True for attr in self.__dict__.keys(): if type(self.__dict__[attr]) == float and type(other.__dict__[attr]) == float: # tolerance for floats if abs(self.__dict__[attr] - other.__dict__[attr]) > config.epsilon: ok = False break elif type(self.__dict__[attr]) == numpy.ndarray and type(other.__dict__[attr]) == numpy.ndarray: # and numpy arrays of floats if (abs(self.__dict__[attr] - other.__dict__[attr]) > config.epsilon).any(): ok = False break elif type(self.__dict__[attr]) == type(dict()): if attr not in ('_puredict', '_observer'): # because they not always contain what we could expect them to... for k in self.__dict__[attr].keys(): if self.__dict__[attr][k] != other.__dict__[attr][k]: #print k #print self.__dict__[attr][k], #print other.__dict__[attr][k] ok = False break else: if self.__dict__[attr] != other.__dict__[attr]: ok = False break else: ok = False return ok def __ne__(self, other): return not self == other
[docs] def copy(self): """ Returns a copy of the object. """ return copy.copy(self)
[docs] def deepcopy(self): """ Returns a deepcopy of the object. """ return copy.deepcopy(self)
[docs]class Angle(RecursiveObject): """ This class handles an angle. It enables conversions of units, while saving the original unit and value of the angle at its construction. Available units: 'degrees', 'radians', 'cos_sin' (cos, sin), 'DMS' (Degree, Minutes, Seconds). """ deg = 'degrees' rad = 'radians' trig = 'cos_sin' dms = 'DMS' units = set([deg, dms, rad, trig]) def __init__(self, value, unit): """ Constructor. 'unit' argument must be among: - 'degrees', - 'DMS' - in which case, value is a tuple (degrees, minutes, seconds), - 'radians', - 'cos_sin' - in which case, value is a tuple (cos, sin). """ if unit in Angle.units: self.__dict__['_'+unit] = value if unit in ('degrees', 'radians'): if unit == 'degrees': circle = 360. elif unit == 'radians': circle = 2. * math.pi while self.__dict__['_'+unit] > circle/2.: self.__dict__['_'+unit] -= circle while self.__dict__['_'+unit] < -circle/2.: self.__dict__['_'+unit] += circle else: raise ValueError("this angle unit is not implemented: "+str(unit)) self._origin_unit = unit self._origin_value = value def __eq__(self, other): """ Redefinition because of dynamism of buffering new computed values... """ if not isinstance(other, Angle): raise ValueError("cannot compare instances of different classes.") if abs(self.get('radians') - other.get('radians')) <= config.epsilon: ok = True else: ok = False return ok def __ne__(self, other): return not self == other
[docs] def get(self, unit=None): """ Returns the angle in the requested unit. If no unit is supplied, the origin unit is used. """ if unit == None: unit = self._origin_unit # or a default one ? elif unit in Angle.units: if not self.__dict__.has_key('_'+unit): self._compute(unit) else: raise ValueError("this angle unit is not implemented: "+str(unit)) return self.__dict__['_'+unit]
def _compute(self, unit): """ Compute the angle in the requested unit, from the original one. See constructor for more details about units. """ # conversion to degrees if unit == Angle.deg: if self._origin_unit == Angle.rad: self.__dict__['_'+unit] = math.degrees(self._origin_value) elif self._origin_unit == Angle.trig: self.__dict__['_'+unit] = math.degrees(math.copysign(math.acos(self._origin_value[0]), self._origin_value[1])) elif self._origin_unit == Angle.dms: self.__dict__['_'+unit] = self._origin_value[0] + self._origin_value[1]/60. + self._origin_value[2]/3600. else: raise NotImplementedError("conversion from this unit ("+self._origin_unit+") is not coded.") # conversion to radians elif unit == Angle.rad: if self._origin_unit == Angle.deg: self.__dict__['_'+unit] = math.radians(self._origin_value) elif self._origin_unit == Angle.trig: self.__dict__['_'+unit] = math.copysign(math.acos(self._origin_value[0]), self._origin_value[1]) elif self._origin_unit == Angle.dms: self.__dict__['_'+unit] = math.radians(self._origin_value[0] + self._origin_value[1]/60. + self._origin_value[2]/3600.) else: raise NotImplementedError("conversion from this unit ("+self._origin_unit+") is not coded.") # conversion to (cos, sin) elif unit == Angle.trig: if self._origin_unit == Angle.deg: self.__dict__['_'+unit] = (math.cos(math.radians(self._origin_value)), math.sin(math.radians(self._origin_value))) elif self._origin_unit == Angle.rad: self.__dict__['_'+unit] = (math.cos(self._origin_value), math.sin(self._origin_value)) elif self._origin_unit == Angle.dms: anglerad = math.radians(self._origin_value[0] + self._origin_value[1]/60. + self._origin_value[2]/3600.) self.__dict__['_'+unit] = (math.cos(anglerad), math.sin(anglerad)) else: raise NotImplementedError("conversion from this unit ("+self._origin_unit+") is not coded.") # conversion to (degrees, minutes, seconds) elif unit == Angle.dms: if self._origin_unit == Angle.deg: decdeg = self._origin_value elif self._origin_unit == Angle.rad: decdeg = math.degrees(self._origin_value) elif self._origin_unit == Angle.trig: decdeg = math.degrees(math.copysign(math.acos(self._origin_value[0]), self._origin_value[1])) else: raise NotImplementedError("conversion from this unit ("+self._origin_unit+") is not coded.") sign = int(math.copysign(1, decdeg)) decdeg = decdeg * sign degrees = int(decdeg) decmin = (decdeg - degrees)*60. minutes = int(decmin) seconds = (decmin - minutes)*60 self.__dict__['_'+unit] = (degrees * sign, minutes, seconds) else: raise NotImplementedError("conversion to this unit ("+unit+") is not coded.") ################# ### FUNCTIONS ### #################
[docs]def find_re_in_list(regexp, a_list): """ Finds all strings from a list that match a regular expression. """ import re pattern = re.subn('\.', r'\.', regexp)[0] # protect '.' pattern = pattern.replace('?', '.') # change unix '?' to python '.' (any char) pattern = pattern.replace('*', '.*') # change unix '*' to python '.*' (several any char) pattern += '(?!.)' found = [] for field in a_list: if re.match(pattern, field.strip()): found.append(field) return found
def nicedeco(decorator): def new_decorator(f): g = decorator(f) g.__name__ = f.__name__ g.__doc__ = f.__doc__ g.__dict__.update(f.__dict__) return g return new_decorator
[docs]def degrees_nearest_mod(d, ref): """ Returns the angle(s) **d** in the modulo nearest to **ref**. """ try: n = len(d) scalar = False except Exception: n = 1 d = [d] scalar = True d = numpy.array(d) d_inf = d - 360. d_sup = d + 360. result = numpy.zeros(n) for i in range(n): if abs(d[i]-ref) <= abs(d_sup[i]-ref) and abs(d[i]-ref) <= abs(d_inf[i]-ref): result[i] = d[i] elif abs(d_inf[i]-ref) <= abs(d_sup[i]-ref) and abs(d_inf[i]-ref) <= abs(d[i]-ref): result[i] = d_inf[i] else: result[i] = d_sup[i] if scalar: result = result[0] return result
[docs]def make_custom_cmap(filename): """ Creates a custom Colormap from a set of RGB colors in a file with the following formating: r1,g1,b1; r2,g2,b2; ... rn,gn,bn each value being comprised between 0 and 255 i.e. coming from """ import matplotlib with open(filename, 'r') as f: colors = f.readlines() for i in range(len(colors)): colors[i] = colors[i].replace(';','') colors[i] = colors[i].replace('[','') colors[i] = colors[i].replace(']','') colors[i] = colors[i].replace('\n','') colors[i] = colors[i].split(',') colors = numpy.array(colors, dtype=numpy.float64) cm = matplotlib.colors.ListedColormap(colors/255.) return cm
[docs]def add_cmap(cmap): """ Reads and registers the epygram-or-user-colormap called *cmap*, which must be either in config.epygram_colormaps or config.usercolormaps. """ import matplotlib.pyplot as plt if cmap not in plt.colormaps(): if cmap in config.colormaps.keys(): plt.register_cmap(name=cmap, cmap=make_custom_cmap(config.colormaps[cmap])) else: raise epygramError("unknown colormap '"+cmap+"': must be added to userconfig, in usercolormaps.")