|
26 | 26 | import numpy as np |
27 | 27 | from guidata.configtools import get_icon |
28 | 28 | from guidata.utils.misc import assert_interfaces_valid |
| 29 | +from skimage import measure |
29 | 30 |
|
30 | 31 | from plotpy.config import _ |
31 | | -from plotpy.contour2d import contour_2d_grid, contour_2d_ortho |
32 | 32 | from plotpy.items.shape.polygon import PolygonShape |
33 | 33 | from plotpy.styles import ShapeParam |
34 | 34 |
|
@@ -73,64 +73,33 @@ def compute_contours( |
73 | 73 | raise TypeError("Input z must be a 2D array.") |
74 | 74 | elif z.shape[0] < 2 or z.shape[1] < 2: |
75 | 75 | raise TypeError("Input z must be at least a 2x2 array.") |
76 | | - else: |
77 | | - Ny, Nx = z.shape |
78 | | - |
79 | | - if X is None: |
80 | | - X = np.arange(Nx) |
81 | | - if Y is None: |
82 | | - Y = np.arange(Ny) |
83 | | - |
84 | | - x = np.asarray(X, dtype=np.float64) |
85 | | - y = np.asarray(Y, dtype=np.float64) |
86 | | - |
87 | | - if x.ndim != y.ndim: |
88 | | - raise TypeError("Number of dimensions of x and y should match.") |
89 | | - if x.ndim == 1: |
90 | | - (nx,) = x.shape |
91 | | - (ny,) = y.shape |
92 | | - if nx != Nx: |
93 | | - raise TypeError("Length of x must be number of columns in z.") |
94 | | - if ny != Ny: |
95 | | - raise TypeError("Length of y must be number of rows in z.") |
96 | | - elif x.ndim == 2: |
97 | | - if x.shape != z.shape: |
98 | | - raise TypeError( |
99 | | - "Shape of x does not match that of z: found " |
100 | | - "{0} instead of {1}.".format(x.shape, z.shape) |
101 | | - ) |
102 | | - if y.shape != z.shape: |
103 | | - raise TypeError( |
104 | | - "Shape of y does not match that of z: found " |
105 | | - "{0} instead of {1}.".format(y.shape, z.shape) |
106 | | - ) |
107 | | - else: |
108 | | - raise TypeError("Inputs x and y must be 1D or 2D.") |
109 | 76 |
|
110 | 77 | if isinstance(levels, np.ndarray): |
111 | 78 | levels = np.asarray(levels, dtype=np.float64) |
112 | 79 | else: |
113 | 80 | levels = np.asarray([levels], dtype=np.float64) |
114 | 81 |
|
115 | | - if x.ndim == 2: |
116 | | - func = contour_2d_grid |
| 82 | + if X is None: |
| 83 | + delta_x, x_origin = 1.0, 0.0 |
| 84 | + else: |
| 85 | + delta_x, x_origin = X[0, 1] - X[0, 0], X[0, 0] |
| 86 | + if Y is None: |
| 87 | + delta_y, y_origin = 1.0, 0.0 |
117 | 88 | else: |
118 | | - func = contour_2d_ortho |
119 | | - |
120 | | - lines = [] |
121 | | - points, offsets = func(z, x, y, levels) |
122 | | - start = 0 |
123 | | - v = 0 |
124 | | - for v, index in offsets: |
125 | | - if index - start >= 2: |
126 | | - cline = ContourLine.create(vertices=points[start:index], level=levels[v]) |
127 | | - lines.append(cline) |
128 | | - start = index |
129 | | - last_points = points[start:] |
130 | | - if len(last_points) >= 2: |
131 | | - cline = ContourLine.create(vertices=last_points, level=levels[v]) |
132 | | - lines.append(cline) |
133 | | - return lines |
| 89 | + delta_y, y_origin = Y[1, 0] - Y[0, 0], Y[0, 0] |
| 90 | + |
| 91 | + # Find contours in the binary image for each level |
| 92 | + clines = [] |
| 93 | + for level in levels: |
| 94 | + for contour in measure.find_contours(Z, level): |
| 95 | + contour = contour.squeeze() |
| 96 | + if len(contour) > 1: # Avoid single points |
| 97 | + line = np.zeros_like(contour, dtype=np.float32) |
| 98 | + line[:, 0] = contour[:, 1] * delta_x + x_origin |
| 99 | + line[:, 1] = contour[:, 0] * delta_y + y_origin |
| 100 | + cline = ContourLine.create(vertices=line, level=level) |
| 101 | + clines.append(cline) |
| 102 | + return clines |
134 | 103 |
|
135 | 104 |
|
136 | 105 | class ContourItem(PolygonShape): |
@@ -179,11 +148,12 @@ def create_contour_items( |
179 | 148 | A list of :py:class:`.ContourItem` instances. |
180 | 149 | """ |
181 | 150 | items = [] |
182 | | - lines = compute_contours(Z, levels, X, Y) |
183 | | - for line in lines: |
| 151 | + |
| 152 | + contours = compute_contours(Z, levels, X, Y) |
| 153 | + for cline in contours: |
184 | 154 | param = ShapeParam("Contour", icon="contour.png") |
185 | | - item = ContourItem(points=line.vertices, shapeparam=param) |
| 155 | + item = ContourItem(points=cline.vertices, shapeparam=param) |
186 | 156 | item.set_style("plot", "shape/contour") |
187 | | - item.setTitle(_("Contour") + f"[Z={line.level}]") |
| 157 | + item.setTitle(_("Contour") + f"[Z={cline.level}]") |
188 | 158 | items.append(item) |
189 | 159 | return items |
0 commit comments