We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[LineZone] - flip in/out line crossing directions
## Description

Between `supervision-0.17.0` and `supervision-0.18.0`, releases in/out of the direction of the crossing were accidentally changed. Given that `LineZone` is one of the oldest features we have we do not want to make life difficult for users and want to restore the previous behavior. The change made in this [PR](https://github.com/roboflow/supervision/pull/735), most likely in this [line](https://github.com/roboflow/supervision/blob/0ccb0b85adee4202f5fe96834a374a057bbbd9da/supervision/detection/line_counter.py#L140), is responsible for the change in behavior.

https://github.com/roboflow/supervision/blob/0ccb0b85adee4202f5fe96834a374a057bbbd9da/supervision/detection/line_counter.py#L140

### Minimal Reproducible Example

You can easily confirm the crossing direction change between `supervision-0.17.0` and `supervision-0.18.0` releases using this [notebook](https://colab.research.google.com/github/roboflow-ai/notebooks/blob/main/notebooks/how-to-track-and-count-vehicles-with-yolov8-and-supervison.ipynb). Here are example results.

__supervision-0.17.0__

https://github.com/roboflow/supervision/assets/26109316/32e0f95c-9204-4703-ab25-c2255a597720

__supervision-0.18.0__

https://github.com/roboflow/supervision/assets/26109316/af6db77e-24f8-4338-9925-3c80afe178f8

### Additional

- Note: Please share a Google Colab with minimal code to test the new feature. We know it's additional work, but it will speed up the review process. The reviewer must test each change. Setting up a local environment to do this is time-consuming. Please ensure that Google Colab can be accessed without any issues (make it public). Thank you! 🙏🏻 

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `supervision/detection/line_counter.py`
Content:
```
1 from typing import Dict, Iterable, Optional, Tuple
2 
3 import cv2
4 import numpy as np
5 
6 from supervision.detection.core import Detections
7 from supervision.draw.color import Color
8 from supervision.draw.utils import draw_text
9 from supervision.geometry.core import Point, Position, Vector
10 
11 
12 class LineZone:
13     """
14     This class is responsible for counting the number of objects that cross a
15     predefined line.
16 
17     !!! warning
18 
19         LineZone uses the `tracker_id`. Read
20         [here](/latest/trackers/) to learn how to plug
21         tracking into your inference pipeline.
22 
23     Attributes:
24         in_count (int): The number of objects that have crossed the line from outside
25             to inside.
26         out_count (int): The number of objects that have crossed the line from inside
27             to outside.
28     """
29 
30     def __init__(
31         self,
32         start: Point,
33         end: Point,
34         triggering_anchors: Iterable[Position] = (
35             Position.TOP_LEFT,
36             Position.TOP_RIGHT,
37             Position.BOTTOM_LEFT,
38             Position.BOTTOM_RIGHT,
39         ),
40     ):
41         """
42         Args:
43             start (Point): The starting point of the line.
44             end (Point): The ending point of the line.
45             triggering_anchors (List[sv.Position]): A list of positions
46                 specifying which anchors of the detections bounding box
47                 to consider when deciding on whether the detection
48                 has passed the line counter or not. By default, this
49                 contains the four corners of the detection's bounding box
50         """
51         self.vector = Vector(start=start, end=end)
52         self.limits = self.calculate_region_of_interest_limits(vector=self.vector)
53         self.tracker_state: Dict[str, bool] = {}
54         self.in_count: int = 0
55         self.out_count: int = 0
56         self.triggering_anchors = triggering_anchors
57 
58     @staticmethod
59     def calculate_region_of_interest_limits(vector: Vector) -> Tuple[Vector, Vector]:
60         magnitude = vector.magnitude
61 
62         if magnitude == 0:
63             raise ValueError("The magnitude of the vector cannot be zero.")
64 
65         delta_x = vector.end.x - vector.start.x
66         delta_y = vector.end.y - vector.start.y
67 
68         unit_vector_x = delta_x / magnitude
69         unit_vector_y = delta_y / magnitude
70 
71         perpendicular_vector_x = -unit_vector_y
72         perpendicular_vector_y = unit_vector_x
73 
74         start_region_limit = Vector(
75             start=vector.start,
76             end=Point(
77                 x=vector.start.x + perpendicular_vector_x,
78                 y=vector.start.y + perpendicular_vector_y,
79             ),
80         )
81         end_region_limit = Vector(
82             start=vector.end,
83             end=Point(
84                 x=vector.end.x - perpendicular_vector_x,
85                 y=vector.end.y - perpendicular_vector_y,
86             ),
87         )
88         return start_region_limit, end_region_limit
89 
90     @staticmethod
91     def is_point_in_limits(point: Point, limits: Tuple[Vector, Vector]) -> bool:
92         cross_product_1 = limits[0].cross_product(point)
93         cross_product_2 = limits[1].cross_product(point)
94         return (cross_product_1 > 0) == (cross_product_2 > 0)
95 
96     def trigger(self, detections: Detections) -> Tuple[np.ndarray, np.ndarray]:
97         """
98         Update the `in_count` and `out_count` based on the objects that cross the line.
99 
100         Args:
101             detections (Detections): A list of detections for which to update the
102                 counts.
103 
104         Returns:
105             A tuple of two boolean NumPy arrays. The first array indicates which
106                 detections have crossed the line from outside to inside. The second
107                 array indicates which detections have crossed the line from inside to
108                 outside.
109         """
110         crossed_in = np.full(len(detections), False)
111         crossed_out = np.full(len(detections), False)
112 
113         if len(detections) == 0:
114             return crossed_in, crossed_out
115 
116         all_anchors = np.array(
117             [
118                 detections.get_anchors_coordinates(anchor)
119                 for anchor in self.triggering_anchors
120             ]
121         )
122 
123         for i, tracker_id in enumerate(detections.tracker_id):
124             if tracker_id is None:
125                 continue
126 
127             box_anchors = [Point(x=x, y=y) for x, y in all_anchors[:, i, :]]
128 
129             in_limits = all(
130                 [
131                     self.is_point_in_limits(point=anchor, limits=self.limits)
132                     for anchor in box_anchors
133                 ]
134             )
135 
136             if not in_limits:
137                 continue
138 
139             triggers = [
140                 self.vector.cross_product(point=anchor) > 0 for anchor in box_anchors
141             ]
142 
143             if len(set(triggers)) == 2:
144                 continue
145 
146             tracker_state = triggers[0]
147 
148             if tracker_id not in self.tracker_state:
149                 self.tracker_state[tracker_id] = tracker_state
150                 continue
151 
152             if self.tracker_state.get(tracker_id) == tracker_state:
153                 continue
154 
155             self.tracker_state[tracker_id] = tracker_state
156             if tracker_state:
157                 self.in_count += 1
158                 crossed_in[i] = True
159             else:
160                 self.out_count += 1
161                 crossed_out[i] = True
162 
163         return crossed_in, crossed_out
164 
165 
166 class LineZoneAnnotator:
167     def __init__(
168         self,
169         thickness: float = 2,
170         color: Color = Color.WHITE,
171         text_thickness: float = 2,
172         text_color: Color = Color.BLACK,
173         text_scale: float = 0.5,
174         text_offset: float = 1.5,
175         text_padding: int = 10,
176         custom_in_text: Optional[str] = None,
177         custom_out_text: Optional[str] = None,
178         display_in_count: bool = True,
179         display_out_count: bool = True,
180     ):
181         """
182         Initialize the LineCounterAnnotator object with default values.
183 
184         Attributes:
185             thickness (float): The thickness of the line that will be drawn.
186             color (Color): The color of the line that will be drawn.
187             text_thickness (float): The thickness of the text that will be drawn.
188             text_color (Color): The color of the text that will be drawn.
189             text_scale (float): The scale of the text that will be drawn.
190             text_offset (float): The offset of the text that will be drawn.
191             text_padding (int): The padding of the text that will be drawn.
192             display_in_count (bool): Whether to display the in count or not.
193             display_out_count (bool): Whether to display the out count or not.
194 
195         """
196         self.thickness: float = thickness
197         self.color: Color = color
198         self.text_thickness: float = text_thickness
199         self.text_color: Color = text_color
200         self.text_scale: float = text_scale
201         self.text_offset: float = text_offset
202         self.text_padding: int = text_padding
203         self.custom_in_text: str = custom_in_text
204         self.custom_out_text: str = custom_out_text
205         self.display_in_count: bool = display_in_count
206         self.display_out_count: bool = display_out_count
207 
208     def _annotate_count(
209         self,
210         frame: np.ndarray,
211         center_text_anchor: Point,
212         text: str,
213         is_in_count: bool,
214     ) -> None:
215         """This method is drawing the text on the frame.
216 
217         Args:
218             frame (np.ndarray): The image on which the text will be drawn.
219             center_text_anchor: The center point that the text will be drawn.
220             text (str): The text that will be drawn.
221             is_in_count (bool): Whether to display the in count or out count.
222         """
223         _, text_height = cv2.getTextSize(
224             text, cv2.FONT_HERSHEY_SIMPLEX, self.text_scale, self.text_thickness
225         )[0]
226 
227         if is_in_count:
228             center_text_anchor.y -= int(self.text_offset * text_height)
229         else:
230             center_text_anchor.y += int(self.text_offset * text_height)
231 
232         draw_text(
233             scene=frame,
234             text=text,
235             text_anchor=center_text_anchor,
236             text_color=self.text_color,
237             text_scale=self.text_scale,
238             text_thickness=self.text_thickness,
239             text_padding=self.text_padding,
240             background_color=self.color,
241         )
242 
243     def annotate(self, frame: np.ndarray, line_counter: LineZone) -> np.ndarray:
244         """
245         Draws the line on the frame using the line_counter provided.
246 
247         Attributes:
248             frame (np.ndarray): The image on which the line will be drawn.
249             line_counter (LineCounter): The line counter
250                 that will be used to draw the line.
251 
252         Returns:
253             np.ndarray: The image with the line drawn on it.
254 
255         """
256         cv2.line(
257             frame,
258             line_counter.vector.start.as_xy_int_tuple(),
259             line_counter.vector.end.as_xy_int_tuple(),
260             self.color.as_bgr(),
261             self.thickness,
262             lineType=cv2.LINE_AA,
263             shift=0,
264         )
265         cv2.circle(
266             frame,
267             line_counter.vector.start.as_xy_int_tuple(),
268             radius=5,
269             color=self.text_color.as_bgr(),
270             thickness=-1,
271             lineType=cv2.LINE_AA,
272         )
273         cv2.circle(
274             frame,
275             line_counter.vector.end.as_xy_int_tuple(),
276             radius=5,
277             color=self.text_color.as_bgr(),
278             thickness=-1,
279             lineType=cv2.LINE_AA,
280         )
281 
282         text_anchor = Vector(
283             start=line_counter.vector.start, end=line_counter.vector.end
284         )
285 
286         if self.display_in_count:
287             in_text = (
288                 f"{self.custom_in_text}: {line_counter.in_count}"
289                 if self.custom_in_text is not None
290                 else f"in: {line_counter.in_count}"
291             )
292             self._annotate_count(
293                 frame=frame,
294                 center_text_anchor=text_anchor.center,
295                 text=in_text,
296                 is_in_count=True,
297             )
298 
299         if self.display_out_count:
300             out_text = (
301                 f"{self.custom_out_text}: {line_counter.out_count}"
302                 if self.custom_out_text is not None
303                 else f"out: {line_counter.out_count}"
304             )
305             self._annotate_count(
306                 frame=frame,
307                 center_text_anchor=text_anchor.center,
308                 text=out_text,
309                 is_in_count=False,
310             )
311         return frame
312
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/supervision/detection/line_counter.py b/supervision/detection/line_counter.py
--- a/supervision/detection/line_counter.py
+++ b/supervision/detection/line_counter.py
@@ -137,7 +137,7 @@
                 continue
 
             triggers = [
-                self.vector.cross_product(point=anchor) > 0 for anchor in box_anchors
+                self.vector.cross_product(point=anchor) < 0 for anchor in box_anchors
             ]
 
             if len(set(triggers)) == 2:

{"golden_diff": "diff --git a/supervision/detection/line_counter.py b/supervision/detection/line_counter.py\n--- a/supervision/detection/line_counter.py\n+++ b/supervision/detection/line_counter.py\n@@ -137,7 +137,7 @@\n                 continue\n \n             triggers = [\n-                self.vector.cross_product(point=anchor) > 0 for anchor in box_anchors\n+                self.vector.cross_product(point=anchor) < 0 for anchor in box_anchors\n             ]\n \n             if len(set(triggers)) == 2:\n", "issue": "[LineZone] - flip in/out line crossing directions\n## Description\r\n\r\nBetween `supervision-0.17.0` and `supervision-0.18.0`, releases in/out of the direction of the crossing were accidentally changed. Given that `LineZone` is one of the oldest features we have we do not want to make life difficult for users and want to restore the previous behavior. The change made in this [PR](https://github.com/roboflow/supervision/pull/735), most likely in this [line](https://github.com/roboflow/supervision/blob/0ccb0b85adee4202f5fe96834a374a057bbbd9da/supervision/detection/line_counter.py#L140), is responsible for the change in behavior.\r\n\r\nhttps://github.com/roboflow/supervision/blob/0ccb0b85adee4202f5fe96834a374a057bbbd9da/supervision/detection/line_counter.py#L140\r\n\r\n### Minimal Reproducible Example\r\n\r\nYou can easily confirm the crossing direction change between `supervision-0.17.0` and `supervision-0.18.0` releases using this [notebook](https://colab.research.google.com/github/roboflow-ai/notebooks/blob/main/notebooks/how-to-track-and-count-vehicles-with-yolov8-and-supervison.ipynb). Here are example results.\r\n\r\n__supervision-0.17.0__\r\n\r\nhttps://github.com/roboflow/supervision/assets/26109316/32e0f95c-9204-4703-ab25-c2255a597720\r\n\r\n__supervision-0.18.0__\r\n\r\nhttps://github.com/roboflow/supervision/assets/26109316/af6db77e-24f8-4338-9925-3c80afe178f8\r\n\r\n### Additional\r\n\r\n- Note: Please share a Google Colab with minimal code to test the new feature. We know it's additional work, but it will speed up the review process. The reviewer must test each change. Setting up a local environment to do this is time-consuming. Please ensure that Google Colab can be accessed without any issues (make it public). Thank you! \ud83d\ude4f\ud83c\udffb \n", "before_files": [{"content": "from typing import Dict, Iterable, Optional, Tuple\n\nimport cv2\nimport numpy as np\n\nfrom supervision.detection.core import Detections\nfrom supervision.draw.color import Color\nfrom supervision.draw.utils import draw_text\nfrom supervision.geometry.core import Point, Position, Vector\n\n\nclass LineZone:\n    \"\"\"\n    This class is responsible for counting the number of objects that cross a\n    predefined line.\n\n    !!! warning\n\n        LineZone uses the `tracker_id`. Read\n        [here](/latest/trackers/) to learn how to plug\n        tracking into your inference pipeline.\n\n    Attributes:\n        in_count (int): The number of objects that have crossed the line from outside\n            to inside.\n        out_count (int): The number of objects that have crossed the line from inside\n            to outside.\n    \"\"\"\n\n    def __init__(\n        self,\n        start: Point,\n        end: Point,\n        triggering_anchors: Iterable[Position] = (\n            Position.TOP_LEFT,\n            Position.TOP_RIGHT,\n            Position.BOTTOM_LEFT,\n            Position.BOTTOM_RIGHT,\n        ),\n    ):\n        \"\"\"\n        Args:\n            start (Point): The starting point of the line.\n            end (Point): The ending point of the line.\n            triggering_anchors (List[sv.Position]): A list of positions\n                specifying which anchors of the detections bounding box\n                to consider when deciding on whether the detection\n                has passed the line counter or not. By default, this\n                contains the four corners of the detection's bounding box\n        \"\"\"\n        self.vector = Vector(start=start, end=end)\n        self.limits = self.calculate_region_of_interest_limits(vector=self.vector)\n        self.tracker_state: Dict[str, bool] = {}\n        self.in_count: int = 0\n        self.out_count: int = 0\n        self.triggering_anchors = triggering_anchors\n\n    @staticmethod\n    def calculate_region_of_interest_limits(vector: Vector) -> Tuple[Vector, Vector]:\n        magnitude = vector.magnitude\n\n        if magnitude == 0:\n            raise ValueError(\"The magnitude of the vector cannot be zero.\")\n\n        delta_x = vector.end.x - vector.start.x\n        delta_y = vector.end.y - vector.start.y\n\n        unit_vector_x = delta_x / magnitude\n        unit_vector_y = delta_y / magnitude\n\n        perpendicular_vector_x = -unit_vector_y\n        perpendicular_vector_y = unit_vector_x\n\n        start_region_limit = Vector(\n            start=vector.start,\n            end=Point(\n                x=vector.start.x + perpendicular_vector_x,\n                y=vector.start.y + perpendicular_vector_y,\n            ),\n        )\n        end_region_limit = Vector(\n            start=vector.end,\n            end=Point(\n                x=vector.end.x - perpendicular_vector_x,\n                y=vector.end.y - perpendicular_vector_y,\n            ),\n        )\n        return start_region_limit, end_region_limit\n\n    @staticmethod\n    def is_point_in_limits(point: Point, limits: Tuple[Vector, Vector]) -> bool:\n        cross_product_1 = limits[0].cross_product(point)\n        cross_product_2 = limits[1].cross_product(point)\n        return (cross_product_1 > 0) == (cross_product_2 > 0)\n\n    def trigger(self, detections: Detections) -> Tuple[np.ndarray, np.ndarray]:\n        \"\"\"\n        Update the `in_count` and `out_count` based on the objects that cross the line.\n\n        Args:\n            detections (Detections): A list of detections for which to update the\n                counts.\n\n        Returns:\n            A tuple of two boolean NumPy arrays. The first array indicates which\n                detections have crossed the line from outside to inside. The second\n                array indicates which detections have crossed the line from inside to\n                outside.\n        \"\"\"\n        crossed_in = np.full(len(detections), False)\n        crossed_out = np.full(len(detections), False)\n\n        if len(detections) == 0:\n            return crossed_in, crossed_out\n\n        all_anchors = np.array(\n            [\n                detections.get_anchors_coordinates(anchor)\n                for anchor in self.triggering_anchors\n            ]\n        )\n\n        for i, tracker_id in enumerate(detections.tracker_id):\n            if tracker_id is None:\n                continue\n\n            box_anchors = [Point(x=x, y=y) for x, y in all_anchors[:, i, :]]\n\n            in_limits = all(\n                [\n                    self.is_point_in_limits(point=anchor, limits=self.limits)\n                    for anchor in box_anchors\n                ]\n            )\n\n            if not in_limits:\n                continue\n\n            triggers = [\n                self.vector.cross_product(point=anchor) > 0 for anchor in box_anchors\n            ]\n\n            if len(set(triggers)) == 2:\n                continue\n\n            tracker_state = triggers[0]\n\n            if tracker_id not in self.tracker_state:\n                self.tracker_state[tracker_id] = tracker_state\n                continue\n\n            if self.tracker_state.get(tracker_id) == tracker_state:\n                continue\n\n            self.tracker_state[tracker_id] = tracker_state\n            if tracker_state:\n                self.in_count += 1\n                crossed_in[i] = True\n            else:\n                self.out_count += 1\n                crossed_out[i] = True\n\n        return crossed_in, crossed_out\n\n\nclass LineZoneAnnotator:\n    def __init__(\n        self,\n        thickness: float = 2,\n        color: Color = Color.WHITE,\n        text_thickness: float = 2,\n        text_color: Color = Color.BLACK,\n        text_scale: float = 0.5,\n        text_offset: float = 1.5,\n        text_padding: int = 10,\n        custom_in_text: Optional[str] = None,\n        custom_out_text: Optional[str] = None,\n        display_in_count: bool = True,\n        display_out_count: bool = True,\n    ):\n        \"\"\"\n        Initialize the LineCounterAnnotator object with default values.\n\n        Attributes:\n            thickness (float): The thickness of the line that will be drawn.\n            color (Color): The color of the line that will be drawn.\n            text_thickness (float): The thickness of the text that will be drawn.\n            text_color (Color): The color of the text that will be drawn.\n            text_scale (float): The scale of the text that will be drawn.\n            text_offset (float): The offset of the text that will be drawn.\n            text_padding (int): The padding of the text that will be drawn.\n            display_in_count (bool): Whether to display the in count or not.\n            display_out_count (bool): Whether to display the out count or not.\n\n        \"\"\"\n        self.thickness: float = thickness\n        self.color: Color = color\n        self.text_thickness: float = text_thickness\n        self.text_color: Color = text_color\n        self.text_scale: float = text_scale\n        self.text_offset: float = text_offset\n        self.text_padding: int = text_padding\n        self.custom_in_text: str = custom_in_text\n        self.custom_out_text: str = custom_out_text\n        self.display_in_count: bool = display_in_count\n        self.display_out_count: bool = display_out_count\n\n    def _annotate_count(\n        self,\n        frame: np.ndarray,\n        center_text_anchor: Point,\n        text: str,\n        is_in_count: bool,\n    ) -> None:\n        \"\"\"This method is drawing the text on the frame.\n\n        Args:\n            frame (np.ndarray): The image on which the text will be drawn.\n            center_text_anchor: The center point that the text will be drawn.\n            text (str): The text that will be drawn.\n            is_in_count (bool): Whether to display the in count or out count.\n        \"\"\"\n        _, text_height = cv2.getTextSize(\n            text, cv2.FONT_HERSHEY_SIMPLEX, self.text_scale, self.text_thickness\n        )[0]\n\n        if is_in_count:\n            center_text_anchor.y -= int(self.text_offset * text_height)\n        else:\n            center_text_anchor.y += int(self.text_offset * text_height)\n\n        draw_text(\n            scene=frame,\n            text=text,\n            text_anchor=center_text_anchor,\n            text_color=self.text_color,\n            text_scale=self.text_scale,\n            text_thickness=self.text_thickness,\n            text_padding=self.text_padding,\n            background_color=self.color,\n        )\n\n    def annotate(self, frame: np.ndarray, line_counter: LineZone) -> np.ndarray:\n        \"\"\"\n        Draws the line on the frame using the line_counter provided.\n\n        Attributes:\n            frame (np.ndarray): The image on which the line will be drawn.\n            line_counter (LineCounter): The line counter\n                that will be used to draw the line.\n\n        Returns:\n            np.ndarray: The image with the line drawn on it.\n\n        \"\"\"\n        cv2.line(\n            frame,\n            line_counter.vector.start.as_xy_int_tuple(),\n            line_counter.vector.end.as_xy_int_tuple(),\n            self.color.as_bgr(),\n            self.thickness,\n            lineType=cv2.LINE_AA,\n            shift=0,\n        )\n        cv2.circle(\n            frame,\n            line_counter.vector.start.as_xy_int_tuple(),\n            radius=5,\n            color=self.text_color.as_bgr(),\n            thickness=-1,\n            lineType=cv2.LINE_AA,\n        )\n        cv2.circle(\n            frame,\n            line_counter.vector.end.as_xy_int_tuple(),\n            radius=5,\n            color=self.text_color.as_bgr(),\n            thickness=-1,\n            lineType=cv2.LINE_AA,\n        )\n\n        text_anchor = Vector(\n            start=line_counter.vector.start, end=line_counter.vector.end\n        )\n\n        if self.display_in_count:\n            in_text = (\n                f\"{self.custom_in_text}: {line_counter.in_count}\"\n                if self.custom_in_text is not None\n                else f\"in: {line_counter.in_count}\"\n            )\n            self._annotate_count(\n                frame=frame,\n                center_text_anchor=text_anchor.center,\n                text=in_text,\n                is_in_count=True,\n            )\n\n        if self.display_out_count:\n            out_text = (\n                f\"{self.custom_out_text}: {line_counter.out_count}\"\n                if self.custom_out_text is not None\n                else f\"out: {line_counter.out_count}\"\n            )\n            self._annotate_count(\n                frame=frame,\n                center_text_anchor=text_anchor.center,\n                text=out_text,\n                is_in_count=False,\n            )\n        return frame\n", "path": "supervision/detection/line_counter.py"}], "after_files": [{"content": "from typing import Dict, Iterable, Optional, Tuple\n\nimport cv2\nimport numpy as np\n\nfrom supervision.detection.core import Detections\nfrom supervision.draw.color import Color\nfrom supervision.draw.utils import draw_text\nfrom supervision.geometry.core import Point, Position, Vector\n\n\nclass LineZone:\n    \"\"\"\n    This class is responsible for counting the number of objects that cross a\n    predefined line.\n\n    !!! warning\n\n        LineZone uses the `tracker_id`. Read\n        [here](/latest/trackers/) to learn how to plug\n        tracking into your inference pipeline.\n\n    Attributes:\n        in_count (int): The number of objects that have crossed the line from outside\n            to inside.\n        out_count (int): The number of objects that have crossed the line from inside\n            to outside.\n    \"\"\"\n\n    def __init__(\n        self,\n        start: Point,\n        end: Point,\n        triggering_anchors: Iterable[Position] = (\n            Position.TOP_LEFT,\n            Position.TOP_RIGHT,\n            Position.BOTTOM_LEFT,\n            Position.BOTTOM_RIGHT,\n        ),\n    ):\n        \"\"\"\n        Args:\n            start (Point): The starting point of the line.\n            end (Point): The ending point of the line.\n            triggering_anchors (List[sv.Position]): A list of positions\n                specifying which anchors of the detections bounding box\n                to consider when deciding on whether the detection\n                has passed the line counter or not. By default, this\n                contains the four corners of the detection's bounding box\n        \"\"\"\n        self.vector = Vector(start=start, end=end)\n        self.limits = self.calculate_region_of_interest_limits(vector=self.vector)\n        self.tracker_state: Dict[str, bool] = {}\n        self.in_count: int = 0\n        self.out_count: int = 0\n        self.triggering_anchors = triggering_anchors\n\n    @staticmethod\n    def calculate_region_of_interest_limits(vector: Vector) -> Tuple[Vector, Vector]:\n        magnitude = vector.magnitude\n\n        if magnitude == 0:\n            raise ValueError(\"The magnitude of the vector cannot be zero.\")\n\n        delta_x = vector.end.x - vector.start.x\n        delta_y = vector.end.y - vector.start.y\n\n        unit_vector_x = delta_x / magnitude\n        unit_vector_y = delta_y / magnitude\n\n        perpendicular_vector_x = -unit_vector_y\n        perpendicular_vector_y = unit_vector_x\n\n        start_region_limit = Vector(\n            start=vector.start,\n            end=Point(\n                x=vector.start.x + perpendicular_vector_x,\n                y=vector.start.y + perpendicular_vector_y,\n            ),\n        )\n        end_region_limit = Vector(\n            start=vector.end,\n            end=Point(\n                x=vector.end.x - perpendicular_vector_x,\n                y=vector.end.y - perpendicular_vector_y,\n            ),\n        )\n        return start_region_limit, end_region_limit\n\n    @staticmethod\n    def is_point_in_limits(point: Point, limits: Tuple[Vector, Vector]) -> bool:\n        cross_product_1 = limits[0].cross_product(point)\n        cross_product_2 = limits[1].cross_product(point)\n        return (cross_product_1 > 0) == (cross_product_2 > 0)\n\n    def trigger(self, detections: Detections) -> Tuple[np.ndarray, np.ndarray]:\n        \"\"\"\n        Update the `in_count` and `out_count` based on the objects that cross the line.\n\n        Args:\n            detections (Detections): A list of detections for which to update the\n                counts.\n\n        Returns:\n            A tuple of two boolean NumPy arrays. The first array indicates which\n                detections have crossed the line from outside to inside. The second\n                array indicates which detections have crossed the line from inside to\n                outside.\n        \"\"\"\n        crossed_in = np.full(len(detections), False)\n        crossed_out = np.full(len(detections), False)\n\n        if len(detections) == 0:\n            return crossed_in, crossed_out\n\n        all_anchors = np.array(\n            [\n                detections.get_anchors_coordinates(anchor)\n                for anchor in self.triggering_anchors\n            ]\n        )\n\n        for i, tracker_id in enumerate(detections.tracker_id):\n            if tracker_id is None:\n                continue\n\n            box_anchors = [Point(x=x, y=y) for x, y in all_anchors[:, i, :]]\n\n            in_limits = all(\n                [\n                    self.is_point_in_limits(point=anchor, limits=self.limits)\n                    for anchor in box_anchors\n                ]\n            )\n\n            if not in_limits:\n                continue\n\n            triggers = [\n                self.vector.cross_product(point=anchor) < 0 for anchor in box_anchors\n            ]\n\n            if len(set(triggers)) == 2:\n                continue\n\n            tracker_state = triggers[0]\n\n            if tracker_id not in self.tracker_state:\n                self.tracker_state[tracker_id] = tracker_state\n                continue\n\n            if self.tracker_state.get(tracker_id) == tracker_state:\n                continue\n\n            self.tracker_state[tracker_id] = tracker_state\n            if tracker_state:\n                self.in_count += 1\n                crossed_in[i] = True\n            else:\n                self.out_count += 1\n                crossed_out[i] = True\n\n        return crossed_in, crossed_out\n\n\nclass LineZoneAnnotator:\n    def __init__(\n        self,\n        thickness: float = 2,\n        color: Color = Color.WHITE,\n        text_thickness: float = 2,\n        text_color: Color = Color.BLACK,\n        text_scale: float = 0.5,\n        text_offset: float = 1.5,\n        text_padding: int = 10,\n        custom_in_text: Optional[str] = None,\n        custom_out_text: Optional[str] = None,\n        display_in_count: bool = True,\n        display_out_count: bool = True,\n    ):\n        \"\"\"\n        Initialize the LineCounterAnnotator object with default values.\n\n        Attributes:\n            thickness (float): The thickness of the line that will be drawn.\n            color (Color): The color of the line that will be drawn.\n            text_thickness (float): The thickness of the text that will be drawn.\n            text_color (Color): The color of the text that will be drawn.\n            text_scale (float): The scale of the text that will be drawn.\n            text_offset (float): The offset of the text that will be drawn.\n            text_padding (int): The padding of the text that will be drawn.\n            display_in_count (bool): Whether to display the in count or not.\n            display_out_count (bool): Whether to display the out count or not.\n\n        \"\"\"\n        self.thickness: float = thickness\n        self.color: Color = color\n        self.text_thickness: float = text_thickness\n        self.text_color: Color = text_color\n        self.text_scale: float = text_scale\n        self.text_offset: float = text_offset\n        self.text_padding: int = text_padding\n        self.custom_in_text: str = custom_in_text\n        self.custom_out_text: str = custom_out_text\n        self.display_in_count: bool = display_in_count\n        self.display_out_count: bool = display_out_count\n\n    def _annotate_count(\n        self,\n        frame: np.ndarray,\n        center_text_anchor: Point,\n        text: str,\n        is_in_count: bool,\n    ) -> None:\n        \"\"\"This method is drawing the text on the frame.\n\n        Args:\n            frame (np.ndarray): The image on which the text will be drawn.\n            center_text_anchor: The center point that the text will be drawn.\n            text (str): The text that will be drawn.\n            is_in_count (bool): Whether to display the in count or out count.\n        \"\"\"\n        _, text_height = cv2.getTextSize(\n            text, cv2.FONT_HERSHEY_SIMPLEX, self.text_scale, self.text_thickness\n        )[0]\n\n        if is_in_count:\n            center_text_anchor.y -= int(self.text_offset * text_height)\n        else:\n            center_text_anchor.y += int(self.text_offset * text_height)\n\n        draw_text(\n            scene=frame,\n            text=text,\n            text_anchor=center_text_anchor,\n            text_color=self.text_color,\n            text_scale=self.text_scale,\n            text_thickness=self.text_thickness,\n            text_padding=self.text_padding,\n            background_color=self.color,\n        )\n\n    def annotate(self, frame: np.ndarray, line_counter: LineZone) -> np.ndarray:\n        \"\"\"\n        Draws the line on the frame using the line_counter provided.\n\n        Attributes:\n            frame (np.ndarray): The image on which the line will be drawn.\n            line_counter (LineCounter): The line counter\n                that will be used to draw the line.\n\n        Returns:\n            np.ndarray: The image with the line drawn on it.\n\n        \"\"\"\n        cv2.line(\n            frame,\n            line_counter.vector.start.as_xy_int_tuple(),\n            line_counter.vector.end.as_xy_int_tuple(),\n            self.color.as_bgr(),\n            self.thickness,\n            lineType=cv2.LINE_AA,\n            shift=0,\n        )\n        cv2.circle(\n            frame,\n            line_counter.vector.start.as_xy_int_tuple(),\n            radius=5,\n            color=self.text_color.as_bgr(),\n            thickness=-1,\n            lineType=cv2.LINE_AA,\n        )\n        cv2.circle(\n            frame,\n            line_counter.vector.end.as_xy_int_tuple(),\n            radius=5,\n            color=self.text_color.as_bgr(),\n            thickness=-1,\n            lineType=cv2.LINE_AA,\n        )\n\n        text_anchor = Vector(\n            start=line_counter.vector.start, end=line_counter.vector.end\n        )\n\n        if self.display_in_count:\n            in_text = (\n                f\"{self.custom_in_text}: {line_counter.in_count}\"\n                if self.custom_in_text is not None\n                else f\"in: {line_counter.in_count}\"\n            )\n            self._annotate_count(\n                frame=frame,\n                center_text_anchor=text_anchor.center,\n                text=in_text,\n                is_in_count=True,\n            )\n\n        if self.display_out_count:\n            out_text = (\n                f\"{self.custom_out_text}: {line_counter.out_count}\"\n                if self.custom_out_text is not None\n                else f\"out: {line_counter.out_count}\"\n            )\n            self._annotate_count(\n                frame=frame,\n                center_text_anchor=text_anchor.center,\n                text=out_text,\n                is_in_count=False,\n            )\n        return frame\n", "path": "supervision/detection/line_counter.py"}]}