File: isocurve.py

package info (click to toggle)
python-vispy 0.6.6-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 21,344 kB
  • sloc: python: 57,412; javascript: 6,810; makefile: 63; sh: 5
file content (175 lines) | stat: -rw-r--r-- 5,897 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright (c) Vispy Development Team. All Rights Reserved.
# Distributed under the (new) BSD License. See LICENSE.txt for more info.
# -----------------------------------------------------------------------------

from __future__ import division

import numpy as np


def isocurve(data, level, connected=False, extend_to_edge=False):
    """
    Generate isocurve from 2D data using marching squares algorithm.

    Parameters
    ----------
    data : ndarray
        2D numpy array of scalar values
    level : float
        The level at which to generate an isosurface
    connected : bool
        If False, return a single long list of point pairs
        If True, return multiple long lists of connected point 
        locations. (This is slower but better for drawing 
        continuous lines)
    extend_to_edge : bool
        If True, extend the curves to reach the exact edges of 
        the data. 
    """    
    # This function is SLOW; plenty of room for optimization here.
    
    if extend_to_edge:
        d2 = np.empty((data.shape[0]+2, data.shape[1]+2), dtype=data.dtype)
        d2[1:-1, 1:-1] = data
        d2[0, 1:-1] = data[0]
        d2[-1, 1:-1] = data[-1]
        d2[1:-1, 0] = data[:, 0]
        d2[1:-1, -1] = data[:, -1]
        d2[0, 0] = d2[0, 1]
        d2[0, -1] = d2[1, -1]
        d2[-1, 0] = d2[-1, 1]
        d2[-1, -1] = d2[-1, -2]
        data = d2
    
    side_table = [
        [],
        [0, 1],
        [1, 2],
        [0, 2],
        [0, 3],
        [1, 3],
        [0, 1, 2, 3],
        [2, 3],
        [2, 3],
        [0, 1, 2, 3],
        [1, 3],
        [0, 3],
        [0, 2],
        [1, 2],
        [0, 1],
        []
    ]
    
    edge_key = [
        [(0, 1), (0, 0)],
        [(0, 0), (1, 0)],
        [(1, 0), (1, 1)],
        [(1, 1), (0, 1)]
    ]
    
    level = float(level)
    lines = []
    
    # mark everything below the isosurface level
    mask = data < level
    
    ## make four sub-fields and compute indexes for grid cells
    index = np.zeros([x-1 for x in data.shape], dtype=np.ubyte)
    fields = np.empty((2, 2), dtype=object)
    slices = [slice(0, -1), slice(1, None)]
    for i in [0, 1]:
        for j in [0, 1]:
            fields[i, j] = mask[slices[i], slices[j]]
            vertIndex = i+2*j
            index += (fields[i, j] * 2**vertIndex).astype(np.ubyte)
    
    # add lines
    for i in range(index.shape[0]):                 # data x-axis
        for j in range(index.shape[1]):             # data y-axis     
            sides = side_table[index[i, j]]
            for side_idx in range(0, len(sides), 2):  # faces for this grid cell
                edges = sides[side_idx:side_idx+2]
                pts = []
                for m in [0, 1]:      # points in this face
                    # p1, p2 are points at either side of an edge
                    p1 = edge_key[edges[m]][0] 
                    p2 = edge_key[edges[m]][1]
                    # v1 and v2 are the values at p1 and p2
                    v1 = data[i+p1[0], j+p1[1]] 
                    v2 = data[i+p2[0], j+p2[1]]
                    f = (level-v1) / (v2-v1)
                    fi = 1.0 - f
                    # interpolate between corners
                    p = (p1[0]*fi + p2[0]*f + i + 0.5, 
                         p1[1]*fi + p2[1]*f + j + 0.5)
                    if extend_to_edge:
                        # check bounds
                        p = (min(data.shape[0]-2, max(0, p[0]-1)),
                             min(data.shape[1]-2, max(0, p[1]-1)))
                    if connected:
                        gridKey = (i + (1 if edges[m] == 2 else 0), 
                                   j + (1 if edges[m] == 3 else 0), 
                                   edges[m] % 2)
                        # give the actual position and a key identifying the 
                        # grid location (for connecting segments)
                        pts.append((p, gridKey))
                    else:
                        pts.append(p)
                
                lines.append(pts)

    if not connected:
        return lines
                
    # turn disjoint list of segments into continuous lines

    points = {}  # maps each point to its connections
    for a, b in lines:
        if a[1] not in points:
            points[a[1]] = []
        points[a[1]].append([a, b])
        if b[1] not in points:
            points[b[1]] = []
        points[b[1]].append([b, a])

    # rearrange into chains
    for k in list(points.keys()):
        try:
            chains = points[k]
        except KeyError:  # already used this point elsewhere
            continue
        for chain in chains:
            x = None
            while True:
                if x == chain[-1][1]:
                    break  # nothing left to do on this chain
                    
                x = chain[-1][1]
                if x == k:
                    # chain has looped; we're done and can ignore the opposite
                    # chain
                    break
                y = chain[-2][1]
                connects = points[x]
                for conn in connects[:]:
                    if conn[1][1] != y:
                        chain.extend(conn[1:])
                del points[x]
            if chain[0][1] == chain[-1][1]:
                # looped chain; no need to continue the other direction
                chains.pop()
                break
                
    # extract point locations 
    lines = []
    for chain in points.values():
        if len(chain) == 2:
            # join together ends of chain
            chain = chain[1][1:][::-1] + chain[0]
        else:
            chain = chain[0]
        lines.append([pt[0] for pt in chain])
    
    return lines  # a list of pairs of points