Coverage for colour/geometry/intersection.py: 100%

55 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-15 19:01 +1300

1""" 

2Intersection Utilities 

3====================== 

4 

5Define utilities for computing geometric intersections and line segment 

6operations in two-dimensional space. 

7 

8References 

9---------- 

10- :cite:`Bourkea` : Bourke, P. (n.d.). Intersection point of two line 

11 segments in 2 dimensions. Retrieved January 15, 2016, from 

12 http://paulbourke.net/geometry/pointlineplane/ 

13- :cite:`Erdema` : Erdem, U. M. (n.d.). Fast Line Segment Intersection. 

14 Retrieved January 15, 2016, from 

15 http://www.mathworks.com/matlabcentral/fileexchange/\ 

1627205-fast-line-segment-intersection 

17- :cite:`Saeedna` : Saeedn. (n.d.). Extend a line segment a specific 

18 distance. Retrieved January 16, 2016, from 

19 http://stackoverflow.com/questions/7740507/\ 

20extend-a-line-segment-a-specific-distance 

21""" 

22 

23from __future__ import annotations 

24 

25import typing 

26from dataclasses import dataclass 

27 

28import numpy as np 

29 

30from colour.algebra import euclidean_distance, sdiv, sdiv_mode 

31 

32if typing.TYPE_CHECKING: 

33 from colour.hints import ArrayLike, NDArrayFloat 

34 

35from colour.utilities import as_float_array, tsplit, tstack 

36 

37__author__ = "Colour Developers" 

38__copyright__ = "Copyright 2013 Colour Developers" 

39__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause" 

40__maintainer__ = "Colour Developers" 

41__email__ = "colour-developers@colour-science.org" 

42__status__ = "Production" 

43 

44__all__ = [ 

45 "extend_line_segment", 

46 "LineSegmentsIntersections_Specification", 

47 "intersect_line_segments", 

48] 

49 

50 

51def extend_line_segment( 

52 a: ArrayLike, b: ArrayLike, distance: float = 1 

53) -> NDArrayFloat: 

54 """ 

55 Extend the line segment defined by point arrays :math:`a` and :math:`b` by 

56 the specified distance and generate the new end point. 

57 

58 Parameters 

59 ---------- 

60 a 

61 Point array :math:`a`. 

62 b 

63 Point array :math:`b`. 

64 distance 

65 Distance to extend the line segment. 

66 

67 Returns 

68 ------- 

69 :class:`numpy.ndarray` 

70 New end point. 

71 

72 References 

73 ---------- 

74 :cite:`Saeedna` 

75 

76 Notes 

77 ----- 

78 - Input line segment points coordinates are 2d coordinates. 

79 

80 Examples 

81 -------- 

82 >>> a = np.array([0.95694934, 0.13720932]) 

83 >>> b = np.array([0.28382835, 0.60608318]) 

84 >>> extend_line_segment(a, b) # doctest: +ELLIPSIS 

85 array([-0.5367248..., 1.1776534...]) 

86 """ 

87 

88 x_a, y_a = tsplit(a) 

89 x_b, y_b = tsplit(b) 

90 

91 d = euclidean_distance(a, b) 

92 

93 with sdiv_mode(): 

94 x_c = x_b + sdiv(x_b - x_a, d) * distance 

95 y_c = y_b + sdiv(y_b - y_a, d) * distance 

96 

97 return tstack([x_c, y_c]) 

98 

99 

100@dataclass 

101class LineSegmentsIntersections_Specification: 

102 """ 

103 Define the specification for intersection of line segments :math:`l_1` and 

104 :math:`l_2` returned by the 

105 :func:`colour.algebra.intersect_line_segments` definition. 

106 

107 Parameters 

108 ---------- 

109 xy 

110 Array of :math:`l_1` and :math:`l_2` line segments intersections 

111 coordinates. Non-existing segments intersections coordinates are set 

112 with `np.nan`. 

113 intersect 

114 Array of *bool* indicating if line segments :math:`l_1` and 

115 :math:`l_2` intersect. 

116 parallel 

117 Array of :class:`bool` indicating if line segments :math:`l_1` and 

118 :math:`l_2` are parallel. 

119 coincident 

120 Array of :class:`bool` indicating if line segments :math:`l_1` and 

121 :math:`l_2` are coincident. 

122 """ 

123 

124 xy: NDArrayFloat 

125 intersect: NDArrayFloat 

126 parallel: NDArrayFloat 

127 coincident: NDArrayFloat 

128 

129 

130def intersect_line_segments( 

131 l_1: ArrayLike, l_2: ArrayLike 

132) -> LineSegmentsIntersections_Specification: 

133 """ 

134 Compute :math:`l_1` line segments intersections with :math:`l_2` line 

135 segments. 

136 

137 Parameters 

138 ---------- 

139 l_1 

140 :math:`l_1` line segments array, each row is a line segment such as 

141 (:math:`x_1`, :math:`y_1`, :math:`x_2`, :math:`y_2`) where 

142 (:math:`x_1`, :math:`y_1`) and (:math:`x_2`, :math:`y_2`) are 

143 respectively the start and end points of :math:`l_1` line segments. 

144 l_2 

145 :math:`l_2` line segments array, each row is a line segment such as 

146 (:math:`x_3`, :math:`y_3`, :math:`x_4`, :math:`y_4`) where 

147 (:math:`x_3`, :math:`y_3`) and (:math:`x_4`, :math:`y_4`) are 

148 respectively the start and end points of :math:`l_2` line segments. 

149 

150 Returns 

151 ------- 

152 :class:`colour.algebra.LineSegmentsIntersections_Specification` 

153 Line segments intersections specification. 

154 

155 References 

156 ---------- 

157 :cite:`Bourkea`, :cite:`Erdema` 

158 

159 Notes 

160 ----- 

161 - Input line segments points coordinates are 2d coordinates. 

162 

163 Examples 

164 -------- 

165 >>> l_1 = np.array( 

166 ... [ 

167 ... [[0.15416284, 0.7400497], [0.26331502, 0.53373939]], 

168 ... [[0.01457496, 0.91874701], [0.90071485, 0.03342143]], 

169 ... ] 

170 ... ) 

171 >>> l_2 = np.array( 

172 ... [ 

173 ... [[0.95694934, 0.13720932], [0.28382835, 0.60608318]], 

174 ... [[0.94422514, 0.85273554], [0.00225923, 0.52122603]], 

175 ... [[0.55203763, 0.48537741], [0.76813415, 0.16071675]], 

176 ... ] 

177 ... ) 

178 >>> s = intersect_line_segments(l_1, l_2) 

179 >>> s.xy # doctest: +ELLIPSIS 

180 array([[[ nan, nan], 

181 [ 0.2279184..., 0.6006430...], 

182 [ nan, nan]], 

183 <BLANKLINE> 

184 [[ 0.4281451..., 0.5055568...], 

185 [ 0.3056055..., 0.6279838...], 

186 [ 0.7578749..., 0.1761301...]]]) 

187 >>> s.intersect 

188 array([[False, True, False], 

189 [ True, True, True]], dtype=bool) 

190 >>> s.parallel 

191 array([[False, False, False], 

192 [False, False, False]], dtype=bool) 

193 >>> s.coincident 

194 array([[False, False, False], 

195 [False, False, False]], dtype=bool) 

196 """ 

197 

198 l_1 = as_float_array(l_1) 

199 l_2 = as_float_array(l_2) 

200 

201 l_1 = np.reshape(l_1, (-1, 4)) 

202 l_2 = np.reshape(l_2, (-1, 4)) 

203 

204 r_1, c_1 = l_1.shape[0], l_1.shape[1] 

205 r_2, c_2 = l_2.shape[0], l_2.shape[1] 

206 

207 x_1, y_1, x_2, y_2 = (np.tile(l_1[:, i, None], (1, r_2)) for i in range(c_1)) 

208 

209 l_2 = np.transpose(l_2) 

210 

211 x_3, y_3, x_4, y_4 = (np.tile(l_2[i, :], (r_1, 1)) for i in range(c_2)) 

212 

213 x_4_x_3 = x_4 - x_3 

214 y_1_y_3 = y_1 - y_3 

215 y_4_y_3 = y_4 - y_3 

216 x_1_x_3 = x_1 - x_3 

217 x_2_x_1 = x_2 - x_1 

218 y_2_y_1 = y_2 - y_1 

219 

220 numerator_a = x_4_x_3 * y_1_y_3 - y_4_y_3 * x_1_x_3 

221 numerator_b = x_2_x_1 * y_1_y_3 - y_2_y_1 * x_1_x_3 

222 denominator = y_4_y_3 * x_2_x_1 - x_4_x_3 * y_2_y_1 

223 

224 with sdiv_mode("Ignore"): 

225 u_a = sdiv(numerator_a, denominator) 

226 u_b = sdiv(numerator_b, denominator) 

227 

228 intersect = np.logical_and.reduce((u_a >= 0, u_a <= 1, u_b >= 0, u_b <= 1)) 

229 xy = tstack([x_1 + x_2_x_1 * u_a, y_1 + y_2_y_1 * u_a]) 

230 xy[~intersect] = np.nan 

231 parallel = denominator == 0 

232 coincident = np.logical_and.reduce((numerator_a == 0, numerator_b == 0, parallel)) 

233 

234 return LineSegmentsIntersections_Specification(xy, intersect, parallel, coincident)