Coverage for /usr/lib/python3/dist-packages/matplotlib/tri/triangulation.py: 18%

97 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2025-06-14 15:25 +0200

1import numpy as np 

2 

3from matplotlib import _api 

4 

5 

6class Triangulation: 

7 """ 

8 An unstructured triangular grid consisting of npoints points and 

9 ntri triangles. The triangles can either be specified by the user 

10 or automatically generated using a Delaunay triangulation. 

11 

12 Parameters 

13 ---------- 

14 x, y : (npoints,) array-like 

15 Coordinates of grid points. 

16 triangles : (ntri, 3) array-like of int, optional 

17 For each triangle, the indices of the three points that make 

18 up the triangle, ordered in an anticlockwise manner. If not 

19 specified, the Delaunay triangulation is calculated. 

20 mask : (ntri,) array-like of bool, optional 

21 Which triangles are masked out. 

22 

23 Attributes 

24 ---------- 

25 triangles : (ntri, 3) array of int 

26 For each triangle, the indices of the three points that make 

27 up the triangle, ordered in an anticlockwise manner. If you want to 

28 take the *mask* into account, use `get_masked_triangles` instead. 

29 mask : (ntri, 3) array of bool 

30 Masked out triangles. 

31 is_delaunay : bool 

32 Whether the Triangulation is a calculated Delaunay 

33 triangulation (where *triangles* was not specified) or not. 

34 

35 Notes 

36 ----- 

37 For a Triangulation to be valid it must not have duplicate points, 

38 triangles formed from colinear points, or overlapping triangles. 

39 """ 

40 def __init__(self, x, y, triangles=None, mask=None): 

41 from matplotlib import _qhull 

42 

43 self.x = np.asarray(x, dtype=np.float64) 

44 self.y = np.asarray(y, dtype=np.float64) 

45 if self.x.shape != self.y.shape or self.x.ndim != 1: 

46 raise ValueError("x and y must be equal-length 1D arrays, but " 

47 f"found shapes {self.x.shape!r} and " 

48 f"{self.y.shape!r}") 

49 

50 self.mask = None 

51 self._edges = None 

52 self._neighbors = None 

53 self.is_delaunay = False 

54 

55 if triangles is None: 

56 # No triangulation specified, so use matplotlib._qhull to obtain 

57 # Delaunay triangulation. 

58 self.triangles, self._neighbors = _qhull.delaunay(x, y) 

59 self.is_delaunay = True 

60 else: 

61 # Triangulation specified. Copy, since we may correct triangle 

62 # orientation. 

63 try: 

64 self.triangles = np.array(triangles, dtype=np.int32, order='C') 

65 except ValueError as e: 

66 raise ValueError('triangles must be a (N, 3) int array, not ' 

67 f'{triangles!r}') from e 

68 if self.triangles.ndim != 2 or self.triangles.shape[1] != 3: 

69 raise ValueError( 

70 'triangles must be a (N, 3) int array, but found shape ' 

71 f'{self.triangles.shape!r}') 

72 if self.triangles.max() >= len(self.x): 

73 raise ValueError( 

74 'triangles are indices into the points and must be in the ' 

75 f'range 0 <= i < {len(self.x)} but found value ' 

76 f'{self.triangles.max()}') 

77 if self.triangles.min() < 0: 

78 raise ValueError( 

79 'triangles are indices into the points and must be in the ' 

80 f'range 0 <= i < {len(self.x)} but found value ' 

81 f'{self.triangles.min()}') 

82 

83 # Underlying C++ object is not created until first needed. 

84 self._cpp_triangulation = None 

85 

86 # Default TriFinder not created until needed. 

87 self._trifinder = None 

88 

89 self.set_mask(mask) 

90 

91 def calculate_plane_coefficients(self, z): 

92 """ 

93 Calculate plane equation coefficients for all unmasked triangles from 

94 the point (x, y) coordinates and specified z-array of shape (npoints). 

95 The returned array has shape (npoints, 3) and allows z-value at (x, y) 

96 position in triangle tri to be calculated using 

97 ``z = array[tri, 0] * x + array[tri, 1] * y + array[tri, 2]``. 

98 """ 

99 return self.get_cpp_triangulation().calculate_plane_coefficients(z) 

100 

101 @property 

102 def edges(self): 

103 """ 

104 Return integer array of shape (nedges, 2) containing all edges of 

105 non-masked triangles. 

106 

107 Each row defines an edge by its start point index and end point 

108 index. Each edge appears only once, i.e. for an edge between points 

109 *i* and *j*, there will only be either *(i, j)* or *(j, i)*. 

110 """ 

111 if self._edges is None: 

112 self._edges = self.get_cpp_triangulation().get_edges() 

113 return self._edges 

114 

115 def get_cpp_triangulation(self): 

116 """ 

117 Return the underlying C++ Triangulation object, creating it 

118 if necessary. 

119 """ 

120 from matplotlib import _tri 

121 if self._cpp_triangulation is None: 

122 self._cpp_triangulation = _tri.Triangulation( 

123 self.x, self.y, self.triangles, self.mask, self._edges, 

124 self._neighbors, not self.is_delaunay) 

125 return self._cpp_triangulation 

126 

127 def get_masked_triangles(self): 

128 """ 

129 Return an array of triangles taking the mask into account. 

130 """ 

131 if self.mask is not None: 

132 return self.triangles[~self.mask] 

133 else: 

134 return self.triangles 

135 

136 @staticmethod 

137 def get_from_args_and_kwargs(*args, **kwargs): 

138 """ 

139 Return a Triangulation object from the args and kwargs, and 

140 the remaining args and kwargs with the consumed values removed. 

141 

142 There are two alternatives: either the first argument is a 

143 Triangulation object, in which case it is returned, or the args 

144 and kwargs are sufficient to create a new Triangulation to 

145 return. In the latter case, see Triangulation.__init__ for 

146 the possible args and kwargs. 

147 """ 

148 if isinstance(args[0], Triangulation): 

149 triangulation, *args = args 

150 if 'triangles' in kwargs: 

151 _api.warn_external( 

152 "Passing the keyword 'triangles' has no effect when also " 

153 "passing a Triangulation") 

154 if 'mask' in kwargs: 

155 _api.warn_external( 

156 "Passing the keyword 'mask' has no effect when also " 

157 "passing a Triangulation") 

158 else: 

159 x, y, triangles, mask, args, kwargs = \ 

160 Triangulation._extract_triangulation_params(args, kwargs) 

161 triangulation = Triangulation(x, y, triangles, mask) 

162 return triangulation, args, kwargs 

163 

164 @staticmethod 

165 def _extract_triangulation_params(args, kwargs): 

166 x, y, *args = args 

167 # Check triangles in kwargs then args. 

168 triangles = kwargs.pop('triangles', None) 

169 from_args = False 

170 if triangles is None and args: 

171 triangles = args[0] 

172 from_args = True 

173 if triangles is not None: 

174 try: 

175 triangles = np.asarray(triangles, dtype=np.int32) 

176 except ValueError: 

177 triangles = None 

178 if triangles is not None and (triangles.ndim != 2 or 

179 triangles.shape[1] != 3): 

180 triangles = None 

181 if triangles is not None and from_args: 

182 args = args[1:] # Consumed first item in args. 

183 # Check for mask in kwargs. 

184 mask = kwargs.pop('mask', None) 

185 return x, y, triangles, mask, args, kwargs 

186 

187 def get_trifinder(self): 

188 """ 

189 Return the default `matplotlib.tri.TriFinder` of this 

190 triangulation, creating it if necessary. This allows the same 

191 TriFinder object to be easily shared. 

192 """ 

193 if self._trifinder is None: 

194 # Default TriFinder class. 

195 from matplotlib.tri.trifinder import TrapezoidMapTriFinder 

196 self._trifinder = TrapezoidMapTriFinder(self) 

197 return self._trifinder 

198 

199 @property 

200 def neighbors(self): 

201 """ 

202 Return integer array of shape (ntri, 3) containing neighbor triangles. 

203 

204 For each triangle, the indices of the three triangles that 

205 share the same edges, or -1 if there is no such neighboring 

206 triangle. ``neighbors[i, j]`` is the triangle that is the neighbor 

207 to the edge from point index ``triangles[i, j]`` to point index 

208 ``triangles[i, (j+1)%3]``. 

209 """ 

210 if self._neighbors is None: 

211 self._neighbors = self.get_cpp_triangulation().get_neighbors() 

212 return self._neighbors 

213 

214 def set_mask(self, mask): 

215 """ 

216 Set or clear the mask array. 

217 

218 Parameters 

219 ---------- 

220 mask : None or bool array of length ntri 

221 """ 

222 if mask is None: 

223 self.mask = None 

224 else: 

225 self.mask = np.asarray(mask, dtype=bool) 

226 if self.mask.shape != (self.triangles.shape[0],): 

227 raise ValueError('mask array must have same length as ' 

228 'triangles array') 

229 

230 # Set mask in C++ Triangulation. 

231 if self._cpp_triangulation is not None: 

232 self._cpp_triangulation.set_mask(self.mask) 

233 

234 # Clear derived fields so they are recalculated when needed. 

235 self._edges = None 

236 self._neighbors = None 

237 

238 # Recalculate TriFinder if it exists. 

239 if self._trifinder is not None: 

240 self._trifinder._initialize()