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
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-15 19:01 +1300
1"""
2Intersection Utilities
3======================
5Define utilities for computing geometric intersections and line segment
6operations in two-dimensional space.
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"""
23from __future__ import annotations
25import typing
26from dataclasses import dataclass
28import numpy as np
30from colour.algebra import euclidean_distance, sdiv, sdiv_mode
32if typing.TYPE_CHECKING:
33 from colour.hints import ArrayLike, NDArrayFloat
35from colour.utilities import as_float_array, tsplit, tstack
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"
44__all__ = [
45 "extend_line_segment",
46 "LineSegmentsIntersections_Specification",
47 "intersect_line_segments",
48]
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.
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.
67 Returns
68 -------
69 :class:`numpy.ndarray`
70 New end point.
72 References
73 ----------
74 :cite:`Saeedna`
76 Notes
77 -----
78 - Input line segment points coordinates are 2d coordinates.
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 """
88 x_a, y_a = tsplit(a)
89 x_b, y_b = tsplit(b)
91 d = euclidean_distance(a, b)
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
97 return tstack([x_c, y_c])
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.
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 """
124 xy: NDArrayFloat
125 intersect: NDArrayFloat
126 parallel: NDArrayFloat
127 coincident: NDArrayFloat
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.
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.
150 Returns
151 -------
152 :class:`colour.algebra.LineSegmentsIntersections_Specification`
153 Line segments intersections specification.
155 References
156 ----------
157 :cite:`Bourkea`, :cite:`Erdema`
159 Notes
160 -----
161 - Input line segments points coordinates are 2d coordinates.
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 """
198 l_1 = as_float_array(l_1)
199 l_2 = as_float_array(l_2)
201 l_1 = np.reshape(l_1, (-1, 4))
202 l_2 = np.reshape(l_2, (-1, 4))
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]
207 x_1, y_1, x_2, y_2 = (np.tile(l_1[:, i, None], (1, r_2)) for i in range(c_1))
209 l_2 = np.transpose(l_2)
211 x_3, y_3, x_4, y_4 = (np.tile(l_2[i, :], (r_1, 1)) for i in range(c_2))
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
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
224 with sdiv_mode("Ignore"):
225 u_a = sdiv(numerator_a, denominator)
226 u_b = sdiv(numerator_b, denominator)
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))
234 return LineSegmentsIntersections_Specification(xy, intersect, parallel, coincident)