Beispiel #1
0
    def __call__(self, value):

        vmin = self.vmin
        vmax = self.vmax

        if type(value) in [IntType, FloatType]:
            vtype = 'scalar'
            val = array([value])
        else:
            vtype = 'array'
            val = asarray(value)
        if vmin is None or vmax is None:
            rval = ravel(val)
            if vmin is None: vmin = nxmin(rval)
            if vmax is None: vmax = nxmax(rval)
        if vmin > vmax:
            raise ValueError("minvalue must be less than or equal to maxvalue")
        elif vmin==vmax:
            return 0.*value
        else:
            
            val = where(val<vmin, vmin, val)
            val = where(val>vmax, vmax, val)
            result = (1.0/(vmax-vmin))*(val-vmin)
        if vtype == 'scalar':
            result = result[0]
        return result
Beispiel #2
0
    def __call__(self, value):

        vmin = self.vmin
        vmax = self.vmax

        if type(value) in [IntType, FloatType]:
            vtype = "scalar"
            val = array([value])
        else:
            vtype = "array"
            val = asarray(value)
        if vmin is None or vmax is None:
            rval = ravel(val)
            if vmin is None:
                vmin = amin(rval)
            if vmax is None:
                vmax = amax(rval)
        if vmin > vmax:
            raise ValueError("minvalue must be less than or equal to maxvalue")
        elif vmin == vmax:
            return 0.0 * value
        else:

            val = where(val < vmin, vmin, val)
            val = where(val > vmax, vmax, val)
            result = (1.0 / (vmax - vmin)) * (val - vmin)
        if vtype == "scalar":
            result = result[0]
        return result
Beispiel #3
0
    def __call__(self, X, alpha=1.0):
        """
        X is either a scalar or an array (of any dimension).
        If scalar, a tuple of rgba values is returned, otherwise
        an array with the new shape = oldshape+(4,).  Any values
        that are outside the 0,1 interval are clipped to that
        interval before generating rgb values.  
        Alpha must be a scalar
        """
        alpha = min(alpha, 1.0) # alpha must be between 0 and 1
        alpha = max(alpha, 0.0)
        if type(X) in [IntType, FloatType]:
            vtype = 'scalar'
            xa = array([X])
        else:
            vtype = 'array'
            xa = array(X)

        xa = where(xa>1.,1.,xa)
        xa = where(xa<0.,0.,xa)
        xa = (xa *(self.N-1)).astype(Int)
        rgba = zeros(xa.shape+(4,), Float)
        rgba[...,0] = take(self._red_lut, xa)
        rgba[...,1] = take(self._green_lut, xa)
        rgba[...,2] = take(self._blue_lut, xa)
        rgba[...,3] = alpha
        if vtype == 'scalar':
            rgba = tuple(rgba[0,:])
        return rgba
Beispiel #4
0
    def __call__(self, X, alpha=1.0):
        """
        X is either a scalar or an array (of any dimension).
        If scalar, a tuple of rgba values is returned, otherwise
        an array with the new shape = oldshape+(4,).  Any values
        that are outside the 0,1 interval are clipped to that
        interval before generating rgb values.  
        Alpha must be a scalar
        """
        alpha = min(alpha, 1.0) # alpha must be between 0 and 1
        alpha = max(alpha, 0.0)
        if type(X) in [IntType, FloatType]:
            vtype = 'scalar'
            xa = array([X])
        else:
            vtype = 'array'
            xa = array(X)

        xa = where(xa>1.,1.,xa)
        xa = where(xa<0.,0.,xa)
        xa = (xa *(self.N-1)).astype(Int)
        rgba = zeros(xa.shape+(4,), Float)
        rgba[...,0] = take(self._red_lut, xa)
        rgba[...,1] = take(self._green_lut, xa)
        rgba[...,2] = take(self._blue_lut, xa)
        rgba[...,3] = alpha
        if vtype == 'scalar':
            rgba = tuple(rgba[0,:])
        return rgba
Beispiel #5
0
    def __call__(self, value):

        vmin = self.vmin
        vmax = self.vmax

        if type(value) in [IntType, FloatType]:
            vtype = 'scalar'
            val = array([value])
        else:
            vtype = 'array'
            val = array(value)
        if vmin is None or vmax is None:
            rval = ravel(val)
            if vmin is None:
                vmin = min(rval)
            if vmax is None:
                vmax = max(rval)
        if vmin > vmax:
            raise ValueError("minvalue must be less than or equal to maxvalue")
        elif vmin==vmax:
            return 0.*value
        else:
            val = where(val<vmin, vmin, val)
            val = where(val>vmax, vmax, val)
            result = divide(val-vmin, vmax-vmin)
        if vtype == 'scalar':
            result = result[0]
        return result
Beispiel #6
0
def soft(data, value, substitute=0):
    mvalue = -value

    cond_less = numerix.less(data, value)
    cond_greater = numerix.greater(data, mvalue)

    data = numerix.where(cond_less & cond_greater, substitute, data)
    data = numerix.where(cond_less, data + value, data)
    data = numerix.where(cond_greater, data - value, data)

    return data
Beispiel #7
0
def soft(data, value, substitute=0):
    mvalue = -value
    
    cond_less = numerix.less(data, value)
    cond_greater = numerix.greater(data, mvalue)

    data = numerix.where(cond_less & cond_greater, substitute, data)
    data = numerix.where(cond_less, data + value, data)
    data = numerix.where(cond_greater, data - value, data)
    
    return data
Beispiel #8
0
def makeMappingArray(N, data):
    """Create an N-element 1-d lookup table
    
    data represented by a list of x,y0,y1 mapping correspondences.
    Each element in this list represents how a value between 0 and 1
    (inclusive) represented by x is mapped to a corresponding value
    between 0 and 1 (inclusive). The two values of y are to allow 
    for discontinuous mapping functions (say as might be found in a
    sawtooth) where y0 represents the value of y for values of x
    <= to that given, and y1 is the value to be used for x > than
    that given). The list must start with x=0, end with x=1, and 
    all values of x must be in increasing order. Values between
    the given mapping points are determined by simple linear interpolation.
    
    The function returns an array "result" where result[x*(N-1)]
    gives the closest value for values of x between 0 and 1.
    """
    try:
        adata = array(data)
    except:
        raise TypeError("data must be convertable to an array")
    shape = adata.shape
    if len(shape) != 2 and shape[1] != 3:
        raise ValueError("data must be nx3 format")

    x  = adata[:,0]
    y0 = adata[:,1]
    y1 = adata[:,2]

    if x[0] != 0. or x[-1] != 1.0:
        raise ValueError(
           "data mapping points must start with x=0. and end with x=1")
    if sometrue(sort(x)-x):
        raise ValueError(
           "data mapping points must have x in increasing order")
    # begin generation of lookup table
    x = x * (N-1)
    lut = zeros((N,), Float)
    xind = arange(float(N))
    ind = searchsorted(x, xind)[1:-1]
    
    lut[1:-1] = ( divide(xind[1:-1] - take(x,ind-1),
                         take(x,ind)-take(x,ind-1) )
                *(take(y0,ind)-take(y1,ind-1)) + take(y1,ind-1))
    lut[0] = y1[0]
    lut[-1] = y0[-1]
    # ensure that the lut is confined to values between 0 and 1 by clipping it
    lut = where(lut > 1., 1., lut)
    lut = where(lut < 0., 0., lut)
    return lut
Beispiel #9
0
def makeMappingArray(N, data):
    """Create an N-element 1-d lookup table
    
    data represented by a list of x,y0,y1 mapping correspondences.
    Each element in this list represents how a value between 0 and 1
    (inclusive) represented by x is mapped to a corresponding value
    between 0 and 1 (inclusive). The two values of y are to allow 
    for discontinuous mapping functions (say as might be found in a
    sawtooth) where y0 represents the value of y for values of x
    <= to that given, and y1 is the value to be used for x > than
    that given). The list must start with x=0, end with x=1, and 
    all values of x must be in increasing order. Values between
    the given mapping points are determined by simple linear interpolation.
    
    The function returns an array "result" where result[x*(N-1)]
    gives the closest value for values of x between 0 and 1.
    """
    try:
        adata = array(data)
    except:
        raise TypeError("data must be convertable to an array")
    shape = adata.shape
    if len(shape) != 2 and shape[1] != 3:
        raise ValueError("data must be nx3 format")

    x  = adata[:,0]
    y0 = adata[:,1]
    y1 = adata[:,2]

    if x[0] != 0. or x[-1] != 1.0:
        raise ValueError(
           "data mapping points must start with x=0. and end with x=1")
    if sometrue(sort(x)-x):
        raise ValueError(
           "data mapping points must have x in increasing order")
    # begin generation of lookup table
    x = x * (N-1)
    lut = zeros((N,), Float)
    xind = arange(float(N))
    ind = searchsorted(x, xind)[1:-1]
    
    lut[1:-1] = ( divide(xind[1:-1] - take(x,ind-1),
                         take(x,ind)-take(x,ind-1) )
                  *(take(y0,ind)-take(y1,ind-1)) + take(y1,ind-1))
    lut[0] = y1[0]
    lut[-1] = y0[-1]
    # ensure that the lut is confined to values between 0 and 1 by clipping it
    lut = where(lut > 1., 1., lut)
    lut = where(lut < 0., 0., lut)
    return lut
Beispiel #10
0
def hard(data, value, substitute=0):
    mvalue = -value
    
    cond = numerix.less(data, value)
    cond &= numerix.greater(data, mvalue)
    
    return numerix.where(cond, substitute, data)
Beispiel #11
0
def hard(data, value, substitute=0):
    mvalue = -value

    cond = numerix.less(data, value)
    cond &= numerix.greater(data, mvalue)

    return numerix.where(cond, substitute, data)
Beispiel #12
0
def contour_rectgrid(x_grid, y_grid, z, levels):
    '''calculate the contours of z on the rectangular grid x_grid, y_grid at
    levels.

    xarr and yarr are 1d arrays which hold the x/y coordinates of the grid.
    They do not need to be equispaced nor of same length.

    z[y, x] holds the value of some scalar function on that
    grid.
    
    levels is a list of float values for which the contours shall be
    calculated.

    Returns a list of equal length to levels. Each entry is a set of contour
    lines for the corresponding level value.

    A set of contour lines is a list which contains 2-element lists
    [xarr, yarr]. xarr and yarr hold the points of one contour line in cartesic
    coordinates.
    
    xarr = result[levidx][lineidx][0]
    yarr = result[levidx][lineidx][1]'''
    result = []
    dx_grid = x_grid[1:] - x_grid[:-1]  # x_n+1 - x_n
    dy_grid = y_grid[1:] - y_grid[:-1]  # y_n+1 - y_n
    for level in levels:
        lines = contour_graph(z, level)
        translated_lines = []
        for line in lines:
            interp_y, xind, yind, weights = map(array, line)
            xcoord = where(interp_y,
                        x_grid[xind],
                        x_grid[xind] + weights*dx_grid[xind-1])
            ycoord = where(interp_y,
                        y_grid[yind] + weights*dy_grid[yind-1],
                        y_grid[yind])
            translated_lines.append([xcoord, ycoord])
        result.append(translated_lines)
    return result
Beispiel #13
0
 def __call__(self, X, alpha=1.0):
     """
     X is either a scalar or an array (of any dimension).
     If scalar, a tuple of rgba values is returned, otherwise
     an array with the new shape = oldshape+(4,). If the X-values
     are integers, then they are used as indices into the array.
     If they are floating point, then they must be in the
     interval (0.0, 1.0).
     Alpha must be a scalar.
     """
     if not self._isinit: self._init()
     alpha = min(alpha, 1.0) # alpha must be between 0 and 1
     alpha = max(alpha, 0.0)
     self._lut[:-3, -1] = alpha
     mask_bad = None
     if isinstance(X, (int, float)):
         vtype = 'scalar'
         xa = array([X])
     else:
         vtype = 'array'
         xma = ma.asarray(X)
         xa = xma.filled(0)
         mask_bad = ma.getmaskorNone(xma)
     if typecode(xa) in typecodes['Float']:
         xa = where(xa == 1.0, 0.9999999, xa) # Tweak so 1.0 is in range.
         xa = (xa * self.N).astype(Int)
     mask_under = xa < 0
     mask_over = xa > self.N-1
     xa = where(mask_under, self._i_under, xa)
     xa = where(mask_over, self._i_over, xa)
     if mask_bad is not None: # and sometrue(mask_bad):
         xa = where(mask_bad, self._i_bad, xa)
     #print 'types', typecode(self._lut), typecode(xa), xa.shape
     rgba = take(self._lut, xa)
     if vtype == 'scalar':
         rgba = tuple(rgba[0,:])
     #print rgba[0,1:10,:]       # Now the same for numpy, numeric...
     return rgba
Beispiel #14
0
 def __call__(self, X, alpha=1.0):
     """
     X is either a scalar or an array (of any dimension).
     If scalar, a tuple of rgba values is returned, otherwise
     an array with the new shape = oldshape+(4,). If the X-values
     are integers, then they are used as indices into the array.
     If they are floating point, then they must be in the
     interval (0.0, 1.0).
     Alpha must be a scalar.
     """
     if not self._isinit: self._init()
     alpha = min(alpha, 1.0)  # alpha must be between 0 and 1
     alpha = max(alpha, 0.0)
     self._lut[:-3, -1] = alpha
     mask_bad = None
     if isinstance(X, (int, float)):
         vtype = 'scalar'
         xa = array([X])
     else:
         vtype = 'array'
         xma = ma.asarray(X)
         xa = xma.filled(0)
         mask_bad = ma.getmaskorNone(xma)
     if typecode(xa) in typecodes['Float']:
         xa = where(xa == 1.0, 0.9999999, xa)  # Tweak so 1.0 is in range.
         xa = (xa * self.N).astype(Int)
     mask_under = xa < 0
     mask_over = xa > self.N - 1
     xa = where(mask_under, self._i_under, xa)
     xa = where(mask_over, self._i_over, xa)
     if mask_bad is not None:  # and sometrue(mask_bad):
         xa = where(mask_bad, self._i_bad, xa)
     #print 'types', typecode(self._lut), typecode(xa), xa.shape
     rgba = take(self._lut, xa)
     if vtype == 'scalar':
         rgba = tuple(rgba[0, :])
     #print rgba[0,1:10,:]       # Now the same for numpy, numeric...
     return rgba
Beispiel #15
0
def prctile(x, p = (0.0, 25.0, 50.0, 75.0, 100.0)):
    """
    Return the percentiles of x.  p can either be a sequence of
    percentil values or a scalar.  If p is a sequence the i-th element
    of the return sequence is the p(i)-th percentile of x
    """
    x = sort(ravel(x))
    Nx = len(x)

    if not iterable(p):
        return x[int(p*Nx/100.0)]

    p = multiply(array(p), Nx/100.0)
    ind = p.astype(Int)
    ind = where(ind>=Nx, Nx-1, ind)        
    return take(x, ind)
Beispiel #16
0
def prctile(x, p=(0.0, 25.0, 50.0, 75.0, 100.0)):
    """
    Return the percentiles of x.  p can either be a sequence of
    percentil values or a scalar.  If p is a sequence the i-th element
    of the return sequence is the p(i)-th percentile of x
    """
    x = sort(ravel(x))
    Nx = len(x)

    if not iterable(p):
        return x[int(p * Nx / 100.0)]

    p = multiply(array(p), Nx / 100.0)
    ind = p.astype(Int)
    ind = where(ind >= Nx, Nx - 1, ind)
    return take(x, ind)
Beispiel #17
0
def contour_gencoords(x_grid, y_grid, z, levels):
    '''calculate the contours of z on the coordinate grid x_grid, y_grid at
    levels.

    Let the 2d plane be parametrized by p and q.
    x_grid[p, q] and y_grid[p,q] have to contain the cartesic coordinates of
    the point (p, q). z[p, q] holds the value of some scalar function on that
    grid. levels is a list of float values for which the contours shall be
    calculated.

    Returns a list of equal length to levels. Each entry is a set of contour
    lines for the corresponding level value.

    A set of contour lines is a list which contains 2-element lists
    [xarr, yarr]. xarr and yarr hold the points of one contour line in cartesic
    coordinates.
    
    xarr = result[levidx][lineidx][0]
    yarr = result[levidx][lineidx][1]'''
    result = []
    maxy, maxx = z.shape
    for level in levels:
        lines = contour_graph(z, level)
        coordinates = []
        for line in lines:
            interp_y, x, y, weights = map(array, line)
            line_translated = []
            for grid in x_grid, y_grid:
                line_translated.append(where(interp_y,
                    # The % operation does not affect the result!
                    # It will avoid index errors but the corresponding
                    # entries will be eaten by where.
                    grid[y,x]*(1-weights) + grid[(y+1)%maxy,x]*weights,
                    grid[y,x]*(1-weights) + grid[y,(x+1)%maxx]*weights ))
            coordinates.append(line_translated)
        result.append(coordinates)
    return result  # result[lev][line][0 or 1] -> x / y (interpol. x_grid / y_grid)
Beispiel #18
0
def less(data, value, substitute=0):
    return numerix.where(numerix.greater(data, value), substitute, data)
Beispiel #19
0
def pyxplotcontour(canvas, data, x=None, y=None, levels=8,
                   colors='map', colmap=None, color=None,
                   labels=False, labelsize=0.4,
                   **kwargs):
    """do contour plot.
    
    data[y, x]: 2d real array containing z values
    x, y: None - equidistant point spacing
          1d array: rectangular non-equidistant grid
          2d array: arbitrary coordinates
    levels: int (level count) or list of z values
            default: 8 equispaced levels
    colors: "map": use colmap (ColorMapper)
            "map_invert": use colmap but invert color
                          (max visibility on top of bitmap plot)
            "color_array": color and levels must be arrays of the same
                           length, level i get's the color i
            "color": use color (as in pyxgraph.pyxplot)
    labels: draw text labels designating the level value of each contour
    labelsize: label text size (relative to normal text size)
    kwargs: are passed to pyxplot
    """
    
    zmin, zmax = data.min(), data.max()
    # define a function zcolor(z) that returns the color to use
    if 'map' in colors:
        if colmap==None:
            colmap=ColMapper.ColorMapper("gauss")
        if 'inv' in colors:
            def zcolor(z):
                r, g, b = colmap.colfct((z-zmin)/(zmax-zmin))
                return 1-r, 1-g, 1-b
        else:
            def zcolor(z):
                return colmap.colfct((z-zmin)/(zmax-zmin))

    elif colors == "color_array":
        levels = array(levels)
        assert hasattr(color, "__iter__")    # for this color and levels
        assert hasattr(levels, "__iter__")   # have to be lists of the
        assert len(color)==len(levels)       # same length
        def zcolor(z):
            return color[argmin(abs(levels - z))]
                                             # return most suitable color
    
    else:  #use color
        def zcolor(z):
            return color
            
    # calculate level list
    if isinstance(levels, int):
        levels = (arange(levels)+0.5)*(zmax-zmin)/levels + zmin

    # calculate the levels
    if x is None or y is None:
        clines = contour_nogrid(data, levels)
    elif len(x.shape) == 1 and len(y.shape) == 1:
        clines = contour_rectgrid(x, y, data, levels)
    elif x.shape == y.shape == data.shape:
        clines = contour_gencoord(x, y, data, levels)
    else:
        raise ValueError, 'pyxplotcontour: x and/or y misshaped'
    # clines is clines[levidx][lineidx][0 or 1 for x/y]
    # set default args
    if 'linetype' not in kwargs and 'lt' not in kwargs:
        kwargs['linetype']=pyx.style.linestyle.solid
    if 'style' not in kwargs:
        kwargs['style'] = 'lines'
    # plot
    for levidx, level in enumerate(levels):
        llabel = '%0.4g'%level
        levelcolor = zcolor(level)
        for line in clines[levidx]:
            canvas.pyxplot((line[0], line[1]),
                           color=levelcolor, title=False, **kwargs)
            if labels:
                # find label position
                x, y = line
                xy  = map(canvas.pos, x, y)
                canvas_x = array([i[0] for i in xy])
                canvas_y = array([i[1] for i in xy])
                dcx = canvas_x[1:] - canvas_x[:-1]
                dcy = canvas_y[1:] - canvas_y[:-1]
                dcx = where(dcx==0, min(dcx)/10, dcx)  # avoid singularity problems
                ind = argmin(abs(dcy/dcx))
                # try to avoid placing labels on plot borders
                # FIXME: find better method for that?
                relx = canvas_x[ind] - canvas.xpos
                if not (labelsize < relx < canvas.width-labelsize): # label at left or right border
                    #if ind < 3 or ind > len(x)-4:     # probably a line ending at border
                    ind = len(x) / 2
                # label position (graphcoords)
                lx, ly = x[ind], y[ind]
                angle = arctan(dcy[ind]/dcx[ind])*180/pi
                if abs(angle) < 5:
                    angle = 0
                # FIXME: do something against labels exceeding plot area

                canvas.pyxlabel((lx, ly), llabel, graphcoords=True,
                                style=[pyx.trafo.translate(0, 0.05),
                                       pyx.trafo.rotate(angle),
                                       pyx.trafo.scale(labelsize),
                                       pyx.text.halign.center,
                                       pyx.text.valign.bottom])
Beispiel #20
0
def less(data, value, substitute=0):
    return numerix.where(numerix.greater(data, value), substitute, data)
Beispiel #21
0
def contour_graph(z, level):
    '''calculate contour of z at level.

    z[y,x] has to be a 2d array.

    Returns a list of 4-element lists. Each entry represents one contour.
    Each entry is [interp_y, xind, yind, weight] where all four are 1d lists
    of equal size.
    
    Imagine a rectangular grid where the crossings are the points of z. All
    points of the result are located on this grid. For point n:
    if interp_y[n] is True, the point is on a vertical line, else on a
    horizontal line.
    
    If it is on a vertical line, xind[n] gives the corresponding index into z.
    The y position is between yind[n] and yind[n]+1. weight[n] is a number
    between 0.0 and 1.0 giving the position: 0 means the point is on yind[n]
    exactly, 1.0 means the point is on yind[n]+1 exactly.
    
    If the point is on a horizontal line, weight[n] denotes the position
    between xind[n] and xind[n]+1.

    result[lineidx][0..3][pointidx]
    '''
    bw = (z>level)                  # "black and white" image of z
    horiz = (bw[:,:-1] != bw[:,1:]) # True where a contour crosses rows
    vert = (bw[:-1] != bw[1:])      # True where a contour crosses columns
    # for later readability
    up, down, left, right = 1, 4, 2, 8
    # adjacency & up means crosspoint at y (top), & left: at x (left),
    # & down: at y+1, & right: at x+1
    # adjacency[y, x] is the cell between x...x+1 and y...y+1
    adjacency = (horiz[:-1,:]*up | vert[:,:-1]*left |
                 horiz [1:,:]*down | vert[:,1:]*right)

    # calculate interpolation weights and store in horiz, vert
    # weight = (level - z[n])/(z[n+1]-z[n])
    # -1 => dummy value (no crossing)
    horiz = where(horiz, (level-z[:,:-1]) / (z[:,1:]-z[:,:-1]), -1)
    vert = where(vert, (level-z[:-1]) / (z[1:]-z[:-1]), -1)
    
    result = []
    def _appendpoint(line, x, y, direction):
        '''append the point adjacency[y,x], direction translated into grid
        coordinates and remove from adjacency.
        returns tuple (bool, x, y, weight)
        bool: False means on horizontal grid, True on vertical grid
        weight is the position between x and x+1 resp y and y+1'''
        adjacency[y, x] ^= direction   # XOR out direction
        if direction & (up|down):
            if direction & down:
                y += 1
            assert horiz[y,x] > -1      # for debugging
            line[0].append(False)
            line[1].append(x)
            line[2].append(y)
            line[3].append(horiz[y, x])
        else:
            # direction is left or right
            if direction & right:
                x += 1
            assert vert[y, x] > -1      # for debugging
            line[0].append(True)
            line[1].append(x)
            line[2].append(y)
            line[3].append(vert[y,x])
    # end def
    opposite = {up:down, down:up, left:right, right:left}
    while any(adjacency != 0):
        # first look if there are any contours ending at the borders of z
        border = True
        # top border
        if any(adjacency[0] & up):
            x0 = nonzero(adjacency[0] & up)[0][0]  # just take first one
            y0 = 0
            orig = up                   # where we come from
        # bottom
        elif any(adjacency[-1] & down):
            x0 = nonzero(adjacency[-1] & down)[0][0]
            y0 = adjacency.shape[0] - 1
            orig = down
        # left
        elif any(adjacency[:,0] & left):
            x0 = 0
            y0 = nonzero(adjacency[:,0] & left)[0][0]
            orig = left
        # right border
        elif any(adjacency[:,-1] & right):
            x0 = adjacency.shape[1] - 1
            y0 = nonzero(adjacency[:,-1] & right)[0][0]
            orig = right
        else:  # pick an arbitrary point (all contours are closed loops now)
            m = adjacency.argmax()
            # FIXME: This is not very pretty! forward compatible?!
            x0, y0 = m%adjacency.shape[1], m/adjacency.shape[1]
            border = False              # did not start from border
            adj = adjacency[y0, x0]
            if adj & up:
                orig = up
            elif adj & down:
                orig = down
            elif adj & left:
                orig = left
            else:
                # There can be only 2 or 4 ends, so adj&right only is an error
                raise 'Dead end at r%d,c%d -- should be impossible!'%(y0,x0)
        # okay now we have starting cell x0,y0 and direction orig.
        endofcontour = False   # set when border reached or closed
        line = [[], [], [], []]
        while not endofcontour:
            # append last point
            _appendpoint(line, x0, y0, orig)
            # find next point
            adj = adjacency[y0, x0]
            if (adj | orig) == 15:      # all directions possible, hm...
                # lets connect cross-wise
                # this might create weird results with multiple contours
                cont = opposite[orig]
            else:   # only one continuation - no problem :-D
                cont = adj
            newx0 = x0 + {up:0, down:0, left:-1, right:1}[cont]
            newy0 = y0 + {up:-1, down:1, left:0, right:0}[cont]
            orig = opposite[cont]
            # out of bounds
            if border and ((not 0<=newx0<adjacency.shape[1]) or
                           (not 0<=newy0<adjacency.shape[0])):
                _appendpoint(line, x0, y0, cont)
                endofcontour = True
            else:
                adjacency[y0, x0] ^= cont
                x0, y0 = newx0, newy0
                # loop closed
                if adjacency[y0, x0] & orig == 0:
                    _appendpoint(line, x0, y0, orig)
                    adjacency[y0, x0] ^= orig
                    endofcontour = True
        result.append(line)
    return result