Source code for bases.algorithm.algorithmbase

# -*- coding: utf-8 -*-
# bases/algorithm/algorithmbase.py

from __future__ import absolute_import # PEP328
from builtins import str
from builtins import object
from utils import isString, isList, testfor, assertName
from utils.mixedmethod import mixedmethod
from utils import classproperty, classname
from .parameter import ParameterBase, ParameterError

[docs]class AlgorithmError(Exception): pass
[docs]class AlgorithmNameError(AlgorithmError): pass
[docs]class AlgorithmParameterError(AlgorithmError): pass
# a) parameters should be set by providing a ParameterBase class # b) parameters should be access by their name in both instance AND class # Sphere.radius.setValue() # as well as # sph = Sphere() # sph.radius.setValue() # How to define an Algorithm? By python-style class definition? # (b) requires the algorithm to be constructed programmatically, doing sth # after class definition was loaded # How to let the user define his model easily? # -> needs to supply some functions, in the current case: ff() and vol() # -> in the filter case: input() and output(), different use # -> the user implements these function, by accessing Spere.radius # -> a staticfunction or similar would be counterintuitive because # parameters would not be available there by logic (from implementers POV) # -> therefore, we need some kind of subclass, does multiple inheritance help? # inheriting from AlgorithmBase and the user class? # Does an AlgorithmBase class contain Parameter types or instances? # Changing a Parameter type changes all future Parameters of that type. # -> allows to define e.g. RadiusParameter which can be reused for several # models.
[docs]class AlgorithmBase(object): """Base class for all data filtering algorithms.""" _name = None # name to display in GUI _parameters = None # list of parameters types or instances @classmethod
[docs] def setName(cls, name): assertName(name, AlgorithmNameError) cls._name = name
@classmethod
[docs] def name(cls): return cls._name
@classmethod
[docs] def setParams(cls, *parameters): """Expects a list of ParameterBase classes/types and sets them as class attributes to this class. They will become instances later, please see __init__()""" cls._parameters = [] for i, p in enumerate(parameters): testfor(isinstance(p, type) and issubclass(p, ParameterBase), AlgorithmParameterError, "{name}: Expected a " "ParameterBase type for parameter {index}, got {type}!" .format(name = cls.__name__, index = i, type = p)) cls.setParam(p)
@mixedmethod def setParam(selforcls, p): """Sets the given parameter as an attribute of this object. Use this method instead of setattr().""" attrname = p.name() if not isList(selforcls._parameters): selforcls._parameters = [] old = getattr(selforcls, attrname, None) oldIdx = -1 if old is not None: try: p.setOnValueUpdate(old.onValueUpdate()) except AttributeError: pass try: oldIdx = selforcls._parameters.index(old) except ValueError: pass setattr(selforcls, attrname, p) # maintain a list of all parameters, replace the old by the new to # keep the ordering or append the new if it didn't exist already if oldIdx >= 0: selforcls._parameters[oldIdx] = p else: selforcls._parameters.append(p) @mixedmethod def params(selforcls): """All parameters of this algorithm.""" if selforcls._parameters is None: return [] return selforcls._parameters @mixedmethod def param(selforcls, index): return selforcls._parameters[index] @mixedmethod def paramCount(selforcls): return len(selforcls._parameters) @property def showParams(self): """A list of parameter names which defines the parameters and their ordering shown in a UI. To be overridden in sub classes.""" return [p.name() for p in self.params()] @classmethod def factory(cls, name = None, *parameters): """This sets the algorithm up and adds the defined parameters as class attributes. They become instance attributes automatically at initialization.""" if name is None: if hasattr(cls, "shortName"): # for backwards compatibility name = cls.shortName else: name = cls.__name__ cls.setName(name) if not len(parameters) and hasattr(cls, "parameters"): # for backwards compatibility parameters = [] for baseCls in reversed(cls.mro()): # get parameters from parent classes as well params = [] try: params = baseCls.params() except AttributeError: pass try: params += [p for p in baseCls.parameters if p not in params] except AttributeError: pass # add parameters to final list without duplicates parameters.extend([p for p in params if p not in parameters]) # cls.parameters = None # enforce usage of params() cls.setParams(*parameters) return cls def __init__(self): """Creates instances from defined parameters and replaces the class attributes accordingly.""" testfor(self._name is not None, AlgorithmNameError, "No name provided! " "Set up {name} by calling factory() first." .format(name = type(self).__name__)) testfor(self._parameters is not None, AlgorithmParameterError, "Parameters not configured! " "Set up {name} by calling factory() first." .format(name = type(self).__name__)) paramTypes = self.params() self._parameters = None for ptype in paramTypes: self.setParam(ptype()) def __str__(self): text = [ self.name() ] for i, p in enumerate(self.params()): text.append(u" {0}: {1}".format(i, str(getattr(self, p.name())))) return "\n".join(text) @classmethod
[docs] def makeDefault(cls): return cls()
def __eq__(self, other): if (not isinstance(other, type(self)) or self.name() != other.name() or self.paramCount() != other.paramCount()): return False for p in self.params(): if getattr(self, p.name()) != getattr(other, p.name()): return False return True def __ne__(self, other): return not self.__eq__(other) def __getstate__(self): """Prepares the internal state for pickle by removing the attribute for each parameter which is usually set by __init__() and during unpickle in __setstate__().""" state = self.__dict__.copy() keys = set(state.keys()) for cls in type(self).mro(): if (issubclass(cls, AlgorithmBase) # prevents recursion loop or not hasattr(cls, "__getstate__")): # excludes other mixins continue parentState = cls.__getstate__(self) keys = keys & set(parentState.keys()) # sync common keys only state.update([(key, parentState[key]) for key in keys]) return state def __setstate__(self, state): self.__dict__ = state # self.params() contains parameter instances already at this point # AlgorithmBase.__init__ expects types to be initialized parameters = self.params() self._parameters = [type(p) for p in parameters] self.__init__() # call custom constructor code for p in parameters: newParam = getattr(self, p.name(), None) if not isinstance(newParam, ParameterBase): continue newParam.setAttributes(**p.attributes())
[docs] def hdfWrite(self, hdf): hdf.writeAttributes(name = self.name(), cls = classname(self)) hdf.writeMembers(self, *[p.name() for p in self.params()])
[docs] def update(self, other): """Copy parameter values from another algorithm of the same type.""" if not isinstance(other, type(self)): return for p in self.params(): otherP = getattr(other, p.name()) p.setValue(otherP.value()) # possibly runs value callback
if __name__ == "__main__": pass # vim: set ts=4 sts=4 sw=4 tw=0: