diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..6de587d1 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +version: 2 +updates: + # GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + commit-message: + prefix: ⬆️ + # Python + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "daily" + commit-message: + prefix: ⬆️ diff --git a/.github/workflows/clear-cache.yml b/.github/workflows/clear-cache.yml index 76aed5cf..5b96de42 100644 --- a/.github/workflows/clear-cache.yml +++ b/.github/workflows/clear-cache.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Clear cache - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: script: | console.log("About to clear") diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 2e0405cc..85c8c51b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -6,11 +6,17 @@ on: - master - main - develop + +permissions: + contents: write + pages: write + pull-requests: write + jobs: deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: 3.x diff --git a/.github/workflows/publish-test.yml b/.github/workflows/publish-test.yml index b473a360..e0c77bee 100644 --- a/.github/workflows/publish-test.yml +++ b/.github/workflows/publish-test.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: 🐍 Set up Python 3.8 environment for build uses: actions/setup-python@v4 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ee897360..3b7b3a6d 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -17,7 +17,7 @@ jobs: python-version: [3.8] steps: - name: πŸ›ŽοΈ Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.head_ref }} - name: 🐍 Set up Python ${{ matrix.python-version }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cd151b02..f9bbad99 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: python-version: ["3.8", "3.9", "3.10","3.11"] steps: - name: πŸ›ŽοΈ Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: 🐍 Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 #Β id based on python version diff --git a/.github/workflows/welcome.yml b/.github/workflows/welcome.yml index d5cf47ba..11e6df17 100644 --- a/.github/workflows/welcome.yml +++ b/.github/workflows/welcome.yml @@ -11,7 +11,7 @@ jobs: name: πŸ‘‹ Welcome runs-on: ubuntu-latest steps: - - uses: actions/first-interaction@v1.1.1 + - uses: actions/first-interaction@v1.2.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} issue-message: "Hello there, thank you for opening an Issue ! πŸ™πŸ» The team was notified and they will get back to you asap." diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2e0b0ede..20bb1fb5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,6 +11,7 @@ repos: hooks: - id: end-of-file-fixer - id: trailing-whitespace + exclude: test/.*\.py - id: check-yaml - id: check-docstring-first - id: check-executables-have-shebangs @@ -65,12 +66,12 @@ repos: - repo: https://github.com/psf/black - rev: 23.9.1 + rev: 23.11.0 hooks: - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.292 + rev: v0.1.6 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] diff --git a/README.md b/README.md index ffb88e98..3a95ff13 100644 --- a/README.md +++ b/README.md @@ -24,10 +24,6 @@ - - - - ## πŸ‘‹ hello **We write your reusable computer vision tools.** Whether you need to load your dataset from your hard drive, draw detections on an image or video, or count how many detections are in a zone. You can count on us! 🀝 @@ -364,5 +360,4 @@ We love your input! Please see our [contributing guide](https://github.com/robof - diff --git a/demo.ipynb b/demo.ipynb index 11abb584..9c06b62c 100644 --- a/demo.ipynb +++ b/demo.ipynb @@ -226,7 +226,7 @@ "output_type": "stream", "text": [ "\u001b[?25l \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m0.0/45.4 kB\u001b[0m \u001b[31m?\u001b[0m eta \u001b[36m-:--:--\u001b[0m\r\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m45.4/45.4 kB\u001b[0m \u001b[31m3.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25h0.15.0\n" + "\u001b[?25h0.16.0\n" ] } ], @@ -540,7 +540,7 @@ } ], "source": [ - "mask_annotator = sv.MaskAnnotator(color_map=\"index\")\n", + "mask_annotator = sv.MaskAnnotator(color_lookup=sv.ColorLookup.INDEX)\n", "\n", "annotated_image = mask_annotator.annotate(image.copy(), detections=detections)\n", "\n", diff --git a/docs/annotators.md b/docs/annotators.md index 9709d7c8..7dd8124d 100644 --- a/docs/annotators.md +++ b/docs/annotators.md @@ -82,6 +82,27 @@ +=== "Dot" + + ```python + >>> import supervision as sv + + >>> image = ... + >>> detections = sv.Detections(...) + + >>> dot_annotator = sv.DotAnnotator() + >>> annotated_frame = dot_annotator.annotate( + ... scene=image.copy(), + ... detections=detections + ... ) + ``` + +
+ + ![circle-annotator-example](https://media.roboflow.com/supervision-annotator-examples/dot-annotator-example-purple.png){ align=center width="800" } + +
+ === "Ellipse" ```python @@ -145,6 +166,27 @@ +=== "Polygon" + + ```python + >>> import supervision as sv + + >>> image = ... + >>> detections = sv.Detections(...) + + >>> polygon_annotator = sv.PolygonAnnotator() + >>> annotated_frame = polygon_annotator.annotate( + ... scene=image.copy(), + ... detections=detections + ... ) + ``` + +
+ + ![polygon-annotator-example](https://media.roboflow.com/supervision-annotator-examples/polygon-annotator-example-purple.png){ align=center width="800" } + +
+ === "Label" ```python @@ -191,15 +233,25 @@ ```python >>> import supervision as sv + >>> from ultralytics import YOLO - >>> image = ... - >>> detections = sv.Detections(...) + >>> model = YOLO('yolov8x.pt') >>> trace_annotator = sv.TraceAnnotator() - >>> annotated_frame = trace_annotator.annotate( - ... scene=image.copy(), - ... detections=detections - ... ) + + >>> video_info = sv.VideoInfo.from_video_path(video_path='...') + >>> frames_generator = get_video_frames_generator(source_path='...') + >>> tracker = sv.ByteTrack() + + >>> with sv.VideoSink(target_path='...', video_info=video_info) as sink: + ... for frame in frames_generator: + ... result = model(frame)[0] + ... detections = sv.Detections.from_ultralytics(result) + ... detections = tracker.update_with_detections(detections) + ... annotated_frame = trace_annotator.annotate( + ... scene=frame.copy(), + ... detections=detections) + ... sink.write_frame(frame=annotated_frame) ```
@@ -208,6 +260,35 @@
+=== "HeatMap" + + ```python + >>> import supervision as sv + >>> from ultralytics import YOLO + + >>> model = YOLO('yolov8x.pt') + + >>> heat_map_annotator = sv.HeatMapAnnotator() + + >>> video_info = sv.VideoInfo.from_video_path(video_path='...') + >>> frames_generator = get_video_frames_generator(source_path='...') + + >>> with sv.VideoSink(target_path='...', video_info=video_info) as sink: + ... for frame in frames_generator: + ... result = model(frame)[0] + ... detections = sv.Detections.from_ultralytics(result) + ... annotated_frame = heat_map_annotator.annotate( + ... scene=frame.copy(), + ... detections=detections) + ... sink.write_frame(frame=annotated_frame) + ``` + +
+ + ![trace-annotator-example](https://media.roboflow.com/supervision-annotator-examples/heat-map-annotator-example-purple.png){ align=center width="800" } + +
+ ## BoundingBoxAnnotator :::supervision.annotators.core.BoundingBoxAnnotator @@ -224,6 +305,10 @@ :::supervision.annotators.core.CircleAnnotator +## DotAnnotator + +:::supervision.annotators.core.DotAnnotator + ## EllipseAnnotator :::supervision.annotators.core.EllipseAnnotator @@ -232,10 +317,18 @@ :::supervision.annotators.core.HaloAnnotator +## HeatMapAnnotator + +:::supervision.annotators.core.HeatMapAnnotator + ## MaskAnnotator :::supervision.annotators.core.MaskAnnotator +## PolygonAnnotator + +:::supervision.annotators.core.PolygonAnnotator + ## LabelAnnotator :::supervision.annotators.core.LabelAnnotator @@ -247,3 +340,7 @@ ## TraceAnnotator :::supervision.annotators.core.TraceAnnotator + +## ColorLookup + +:::supervision.annotators.utils.ColorLookup diff --git a/docs/assets.md b/docs/assets.md new file mode 100644 index 00000000..13e3cbfb --- /dev/null +++ b/docs/assets.md @@ -0,0 +1,21 @@ +Supervision offers an assets download utility that allows you to download video files +that you can use in your demos. + +## install extra + +To install the Supervision assets utility, you can use `pip`. This utility is available +as an extra within the Supervision package. + +!!! example "pip install" + + ```bash + pip install supervision[assets] + ``` + +## download_assets + +:::supervision.assets.downloader.download_assets + +## VideoAssets + +:::supervision.assets.list.VideoAssets diff --git a/docs/changelog.md b/docs/changelog.md index 5c303b86..1039bb07 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,3 +1,47 @@ +### 0.16.0 October 19, 2023 + +- Added [#422](https://github.com/roboflow/supervision/pull/422): [`sv.BoxMaskAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.BoxMaskAnnotator) allowing to annotate images and videos with mox masks. + +- Added [#433](https://github.com/roboflow/supervision/pull/433): [`sv.HaloAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.HaloAnnotator) allowing to annotate images and videos with halo effect. + +```python +>>> import supervision as sv + +>>> image = ... +>>> detections = sv.Detections(...) + +>>> halo_annotator = sv.HaloAnnotator() +>>> annotated_frame = halo_annotator.annotate( +... scene=image.copy(), +... detections=detections +... ) +``` + +- Added [#466](https://github.com/roboflow/supervision/pull/466): [`sv.HeatMapAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.HeatMapAnnotator) allowing to annotate videos with heat maps. + +- Added [#492](https://github.com/roboflow/supervision/pull/492): [`sv.DotAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.DotAnnotator) allowing to annotate images and videos with dots. + +- Added [#449](https://github.com/roboflow/supervision/pull/449): [`sv.draw_image`](https://supervision.roboflow.com/draw/utils/#supervision.draw.utils.draw_image) allowing to draw an image onto a given scene with specified opacity and dimensions. + +- Added [#280](https://github.com/roboflow/supervision/pull/280): [`sv.FPSMonitor`](https://supervision.roboflow.com/utils/video/#supervision.utils.video.FPSMonitor) for monitoring frames per second (FPS) to benchmark latency. + +- Added [#454](https://github.com/roboflow/supervision/pull/454): πŸ€— Hugging Face Annotators [space](https://huggingface.co/spaces/Roboflow/Annotators). + +- Changed [#482](https://github.com/roboflow/supervision/pull/482): [`sv.LineZone.tigger`](https://supervision.roboflow.com/detection/tools/line_zone/#supervision.detection.line_counter.LineZone.trigger) now return `Tuple[np.ndarray, np.ndarray]`. The first array indicates which detections have crossed the line from outside to inside. The second array indicates which detections have crossed the line from inside to outside. + +- Changed [#465](https://github.com/roboflow/supervision/pull/465): Annotator argument name from `color_map: str` to `color_lookup: ColorLookup` enum to increase type safety. + +- Changed [#426](https://github.com/roboflow/supervision/pull/426): [`sv.MaskAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.MaskAnnotator) allowing 2x faster annotation. + +- Fixed [#477](https://github.com/roboflow/supervision/pull/477): Poetry env definition allowing proper local installation. + +- Fixed [#430](https://github.com/roboflow/supervision/pull/430): [`sv.ByteTrack`](https://supervision.roboflow.com/trackers/#supervision.tracker.byte_tracker.core.ByteTrack) to return `np.array([], dtype=int)` when `svDetections` is empty. + +!!! warning + + `sv.Detections.from_yolov8` and `sv.Classifications.from_yolov8` as those are now replaced by [`sv.Detections.from_ultralytics`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_ultralytics) and [`sv.Classifications.from_ultralytics`](https://supervision.roboflow.com/classification/core/#supervision.classification.core.Classifications.from_ultralytics). + + ### 0.15.0 October 5, 2023 - Added [#170](https://github.com/roboflow/supervision/pull/170): [`sv.BoundingBoxAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.BoundingBoxAnnotator) allowing to annotate images and videos with bounding boxes. diff --git a/docs/detection/tools/line_zone.md b/docs/detection/tools/line_zone.md new file mode 100644 index 00000000..cbecdaa9 --- /dev/null +++ b/docs/detection/tools/line_zone.md @@ -0,0 +1,3 @@ +## LineZone + +:::supervision.detection.line_counter.LineZone diff --git a/docs/draw/color.md b/docs/draw/color.md new file mode 100644 index 00000000..5114f4ba --- /dev/null +++ b/docs/draw/color.md @@ -0,0 +1,7 @@ +## Color + +:::supervision.draw.color.Color + +## ColorPalette + +:::supervision.draw.color.ColorPalette diff --git a/docs/draw/utils.md b/docs/draw/utils.md index e7bb806b..5832089e 100644 --- a/docs/draw/utils.md +++ b/docs/draw/utils.md @@ -17,3 +17,7 @@ ## draw_text :::supervision.draw.utils.draw_text + +## draw_image + +:::supervision.draw.utils.draw_image diff --git a/docs/how_to/detect_and_annotate.md b/docs/how_to/detect_and_annotate.md index c9497945..04250bc3 100644 --- a/docs/how_to/detect_and_annotate.md +++ b/docs/how_to/detect_and_annotate.md @@ -1 +1,78 @@ -🚧 Page under construction. +With Supervision, you can easily [annotate](https://supervision.roboflow.com/annotators/) predictions obtained from a variety of object detection and segmentation models. This document outlines how to run inference using the [Ultralytics](https://github.com/ultralytics/ultralytics) YOLOv8 model, load these predictions into Supervision, and annotate the image. + +## Run Inference + +First, you'll need to obtain predictions from your object detection or segmentation model. +```python +import cv2 +from ultralytics import YOLO + +model = YOLO("yolov8n.pt") +image = cv2.imread("image.jpg") +results = model(image)[0] +``` + +## Load Predictions into Supervision + +Now that we have predictions from a model, we can load them into Supervision. We can do so using the [`sv.Detections.from_ultralytics`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_ultralytics) method, which accepts model results from both detection and segmentation models. + +```python +import cv2 +from ultralytics import YOLO +import supervision as sv + +model = YOLO("yolov8n.pt") +image = cv2.imread("image.jpg") +results = model(image)[0] +detections = sv.Detections.from_ultralytics(results) +``` + +You can conveniently load predictions from other computer vision frameworks and libraries using: + +- [`from_deepsparse`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_deepsparse) ([Deepsparse](https://github.com/neuralmagic/deepsparse)) +- [`from_detectron2`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_detectron2) ([Detectron2](https://github.com/facebookresearch/detectron2)) +- [`from_mmdetection`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_mmdetection) ([MMDetection](https://github.com/open-mmlab/mmdetection)) +- [`from_roboflow`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_roboflow) ([Roboflow Inference](https://github.com/roboflow/inference)) +- [`from_sam`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_sam) ([Segment Anything Model](https://github.com/facebookresearch/segment-anything)) +- [`from_transformers`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_transformers) ([HuggingFace Transformers](https://github.com/huggingface/transformers)) +- [`from_yolo_nas`](https://supervision.roboflow.com/detection/core/#supervision.detection.core.Detections.from_yolo_nas) ([YOLO-NAS](https://github.com/Deci-AI/super-gradients/blob/master/YOLONAS.md)) + + +## Annotate Image + +Finally, we can annotate the image with the predictions. Since we are working with an object detection model, we will use the [`sv.BoundingBoxAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.BoundingBoxAnnotator) and [`sv.LabelAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.LabelAnnotator) classes. If you are running the segmentation model [`sv.MaskAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.MaskAnnotator) is a drop-in replacement for [`sv.BoundingBoxAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.BoundingBoxAnnotator) that will allow you to draw masks instead of boxes. + +```python +import cv2 +from ultralytics import YOLO +import supervision as sv + +model = YOLO("yolov8n.pt") +image = cv2.imread("image.jpg") +results = model(image)[0] +detections = sv.Detections.from_ultralytics(results) + +bounding_box_annotator = sv.BoundingBoxAnnotator() +label_annotator = sv.LabelAnnotator() + +labels = [ + results.names[class_id] + for class_id + in detections.class_id +] + +annotated_image = bounding_box_annotator.annotate( + scene=image, detections=detections) +annotated_image = label_annotator.annotate( + scene=annotated_image, detections=detections, labels=labels) +``` + +![Predictions plotted on an image](https://media.roboflow.com/supervision_annotate_example.png) + +## Display Annotated Image + +To display the annotated image in Jupyter Notebook or Google Colab, use the [`sv.plot_image`](https://supervision.roboflow.com/utils/notebook/#supervision.utils.notebook.plot_image) function. + +```python +sv.plot_image(annotated_image) +``` diff --git a/docs/how_to/track_objects.md b/docs/how_to/track_objects.md index c9497945..ea48b070 100644 --- a/docs/how_to/track_objects.md +++ b/docs/how_to/track_objects.md @@ -1 +1,183 @@ -🚧 Page under construction. +Utilize Supervision to elevate your video analysis capabilities by effortlessly +[tracking](https://supervision.roboflow.com/trackers/) objects identified by various +object detection and segmentation models. This guide will walk you through the process +of running inference using the [Ultralytics](https://github.com/ultralytics/ultralytics) +YOLOv8 model, subsequently tracking these objects, and annotating the video. + +To make it easier for you to follow our tutorial download the video we will use as an +example. You can do this using +[`supervision[assets]`](https://supervision.roboflow.com/assets/) extension. + +```python +from supervision.assets import download_assets, VideoAssets + +download_assets(VideoAssets.PEOPLE_WALKING) +``` + + + +## Run Inference + +First, you'll need to obtain predictions from your object detection or segmentation +model. In this tutorial, we are using the YOLOv8 model as an example. However, +Supervision is versatile and compatible with various models. Check this +[link](https://supervision.roboflow.com/how_to/detect_and_annotate/#load-predictions-into-supervision) +for guidance on how to plug in other models. + +We will define a `callback` function, which will process each frame of the video +by obtaining model predictions and then annotating the frame based on these predictions. +This `callback` function will be essential in the subsequent steps of the tutorial, as +it will be modified to include tracking, labeling, and trace annotations. + +```{ .py } +import numpy as np +import supervision as sv +from ultralytics import YOLO + +model = YOLO("yolov8n.pt") +box_annotator = sv.BoundingBoxAnnotator() + +def callback(frame: np.ndarray, _: int) -> np.ndarray: + results = model(frame)[0] + detections = sv.Detections.from_ultralytics(results) + return box_annotator.annotate(frame.copy(), detections=detections) + +sv.process_video( + source_path="people-walking.mp4", + target_path="result.mp4", + callback=callback +) +``` + + + +## Tracking + +After running inference and obtaining predictions, the next step is to track the +detected objects throughout the video. Utilizing Supervision’s +[`sv.ByteTrack`](https://supervision.roboflow.com/trackers/#supervision.tracker.byte_tracker.core.ByteTrack) +functionality, each detected object is assigned a unique tracker ID, +enabling the continuous following of the object's motion path across different frames. + +```{ .py hl_lines="6 12" } +import numpy as np +import supervision as sv +from ultralytics import YOLO + +model = YOLO("yolov8n.pt") +tracker = sv.ByteTrack() +box_annotator = sv.BoundingBoxAnnotator() + +def callback(frame: np.ndarray, _: int) -> np.ndarray: + results = model(frame)[0] + detections = sv.Detections.from_ultralytics(results) + detections = tracker.update_with_detections(detections) + return box_annotator.annotate(frame.copy(), detections=detections) + +sv.process_video( + source_path="people-walking.mp4", + target_path="result.mp4", + callback=callback +) +``` + +## Annotate Video with Tracking IDs + +Annotating the video with tracking IDs helps in distinguishing and following each object +distinctly. With the +[`sv.LabelAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.LabelAnnotator) +in Supervision, we can overlay the tracker IDs and class labels on the detected objects, +offering a clear visual representation of each object's class and unique identifier. + +```{ .py hl_lines="8 15-19 23-24" } +import numpy as np +import supervision as sv +from ultralytics import YOLO + +model = YOLO("yolov8n.pt") +tracker = sv.ByteTrack() +box_annotator = sv.BoundingBoxAnnotator() +label_annotator = sv.LabelAnnotator() + +def callback(frame: np.ndarray, _: int) -> np.ndarray: + results = model(frame)[0] + detections = sv.Detections.from_ultralytics(results) + detections = tracker.update_with_detections(detections) + + labels = [ + f"#{tracker_id} {results.names[class_id]}" + for class_id, tracker_id + in zip(detections.class_id, detections.tracker_id) + ] + + annotated_frame = box_annotator.annotate( + frame.copy(), detections=detections) + return label_annotator.annotate( + annotated_frame, detections=detections, labels=labels) + +sv.process_video( + source_path="people-walking.mp4", + target_path="result.mp4", + callback=callback +) +``` + + + +## Annotate Video with Traces + +Adding traces to the video involves overlaying the historical paths of the detected +objects. This feature, powered by the +[`sv.TraceAnnotator`](https://supervision.roboflow.com/annotators/#supervision.annotators.core.TraceAnnotator), +allows for visualizing the trajectories of objects, helping in understanding the +movement patterns and interactions between objects in the video. + +```{ .py hl_lines="9 26-27" } +import numpy as np +import supervision as sv +from ultralytics import YOLO + +model = YOLO("yolov8n.pt") +tracker = sv.ByteTrack() +box_annotator = sv.BoundingBoxAnnotator() +label_annotator = sv.LabelAnnotator() +trace_annotator = sv.TraceAnnotator() + +def callback(frame: np.ndarray, _: int) -> np.ndarray: + results = model(frame)[0] + detections = sv.Detections.from_ultralytics(results) + detections = tracker.update_with_detections(detections) + + labels = [ + f"#{tracker_id} {results.names[class_id]}" + for class_id, tracker_id + in zip(detections.class_id, detections.tracker_id) + ] + + annotated_frame = box_annotator.annotate( + frame.copy(), detections=detections) + annotated_frame = label_annotator.annotate( + annotated_frame, detections=detections, labels=labels) + return trace_annotator.annotate( + annotated_frame, detections=detections) + +sv.process_video( + source_path="people-walking.mp4", + target_path="result.mp4", + callback=callback +) +``` + + + +This structured walkthrough should give a detailed pathway to annotate videos +effectively using Supervision’s various functionalities, including object tracking and +trace annotations. diff --git a/docs/index.md b/docs/index.md index 3019901f..ae3a425a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -63,7 +63,7 @@ You can install `supervision` with pip in a cd supervision # setup python environment and activate it - poetry env use python 3.10 + poetry env use python3.10 poetry shell # headless install diff --git a/docs/javascript/init_kapa_widget.js b/docs/javascript/init_kapa_widget.js new file mode 100644 index 00000000..ffa121ac --- /dev/null +++ b/docs/javascript/init_kapa_widget.js @@ -0,0 +1,10 @@ +document.addEventListener("DOMContentLoaded", function () { + var script = document.createElement("script"); + script.src = "https://widget.kapa.ai/kapa-widget.bundle.js"; + script.setAttribute("data-website-id", "e83c5c60-2968-410b-a2da-08fb104f23df"); + script.setAttribute("data-project-name", "Roboflow"); + script.setAttribute("data-project-color", "#6405C9"); + script.setAttribute("data-project-logo", "https://media.roboflow.com/chat.png"); + script.async = true; + document.head.appendChild(script); +}); diff --git a/examples/traffic_analysis/script.sh b/examples/traffic_analysis/setup.sh similarity index 100% rename from examples/traffic_analysis/script.sh rename to examples/traffic_analysis/setup.sh diff --git a/mkdocs.yml b/mkdocs.yml index 6528b238..8fac7770 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,5 +1,5 @@ site_name: Supervision -site_url: https://roboflow.github.io/supervision +site_url: https://supervision.roboflow.com/ site_author: Roboflow site_description: A set of easy-to-use utils that will come in handy in any Computer Vision project repo_name: roboflow/supervision @@ -39,6 +39,7 @@ nav: - Core: detection/core.md - Utils: detection/utils.md - Tools: + - Line Zone: detection/tools/line_zone.md - Polygon Zone: detection/tools/polygon_zone.md - Inference Slicer: detection/tools/inference_slicer.md - Annotators: annotators.md @@ -47,12 +48,14 @@ nav: - Metrics: - Object Detection: metrics/detection.md - Draw: + - Color: draw/color.md - Utils: draw/utils.md - Utils: - Video: utils/video.md - Image: utils/image.md - Notebook: utils/notebook.md - File: utils/file.md + - Assets: assets.md - Changelog: changelog.md theme: @@ -91,3 +94,7 @@ markdown_extensions: alternate_style: true - toc: permalink: true + +extra_javascript: + - "https://widget.kapa.ai/kapa-widget.bundle.js" + - "javascript/init_kapa_widget.js" diff --git a/poetry.lock b/poetry.lock index e8f92e94..d0a873bd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -119,6 +119,20 @@ six = "*" [package.extras] test = ["astroid", "pytest"] +[[package]] +name = "async-lru" +version = "2.0.4" +description = "Simple LRU cache for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "async-lru-2.0.4.tar.gz", hash = "sha256:b8a59a5df60805ff63220b2a0c5b5393da5521b113cd5465a44eb037d81a5627"}, + {file = "async_lru-2.0.4-py3-none-any.whl", hash = "sha256:ff02944ce3c288c5be660c42dbcca0742b32c3b279d6dceda655190240b99224"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} + [[package]] name = "attrs" version = "23.1.0" @@ -137,6 +151,23 @@ docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib- tests = ["attrs[tests-no-zope]", "zope-interface"] tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +[[package]] +name = "babel" +version = "2.13.0" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Babel-2.13.0-py3-none-any.whl", hash = "sha256:fbfcae1575ff78e26c7449136f1abbefc3c13ce542eeb13d43d50d8b047216ec"}, + {file = "Babel-2.13.0.tar.gz", hash = "sha256:04c3e2d28d2b7681644508f836be388ae49e0cfe91465095340395b60d00f210"}, +] + +[package.dependencies] +pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} + +[package.extras] +dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] + [[package]] name = "backcall" version = "0.2.0" @@ -168,33 +199,29 @@ lxml = ["lxml"] [[package]] name = "black" -version = "23.7.0" +version = "23.11.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" files = [ - {file = "black-23.7.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587"}, - {file = "black-23.7.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f"}, - {file = "black-23.7.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be"}, - {file = "black-23.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc"}, - {file = "black-23.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995"}, - {file = "black-23.7.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2"}, - {file = "black-23.7.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd"}, - {file = "black-23.7.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a"}, - {file = "black-23.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:831d8f54c3a8c8cf55f64d0422ee875eecac26f5f649fb6c1df65316b67c8926"}, - {file = "black-23.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad"}, - {file = "black-23.7.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f"}, - {file = "black-23.7.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3"}, - {file = "black-23.7.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6"}, - {file = "black-23.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1c6022b86f83b632d06f2b02774134def5d4d4f1dac8bef16d90cda18ba28a"}, - {file = "black-23.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320"}, - {file = "black-23.7.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9"}, - {file = "black-23.7.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3"}, - {file = "black-23.7.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087"}, - {file = "black-23.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:642496b675095d423f9b8448243336f8ec71c9d4d57ec17bf795b67f08132a91"}, - {file = "black-23.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491"}, - {file = "black-23.7.0-py3-none-any.whl", hash = "sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96"}, - {file = "black-23.7.0.tar.gz", hash = "sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb"}, + {file = "black-23.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dbea0bb8575c6b6303cc65017b46351dc5953eea5c0a59d7b7e3a2d2f433a911"}, + {file = "black-23.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:412f56bab20ac85927f3a959230331de5614aecda1ede14b373083f62ec24e6f"}, + {file = "black-23.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d136ef5b418c81660ad847efe0e55c58c8208b77a57a28a503a5f345ccf01394"}, + {file = "black-23.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:6c1cac07e64433f646a9a838cdc00c9768b3c362805afc3fce341af0e6a9ae9f"}, + {file = "black-23.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf57719e581cfd48c4efe28543fea3d139c6b6f1238b3f0102a9c73992cbb479"}, + {file = "black-23.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:698c1e0d5c43354ec5d6f4d914d0d553a9ada56c85415700b81dc90125aac244"}, + {file = "black-23.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:760415ccc20f9e8747084169110ef75d545f3b0932ee21368f63ac0fee86b221"}, + {file = "black-23.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:58e5f4d08a205b11800332920e285bd25e1a75c54953e05502052738fe16b3b5"}, + {file = "black-23.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:45aa1d4675964946e53ab81aeec7a37613c1cb71647b5394779e6efb79d6d187"}, + {file = "black-23.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c44b7211a3a0570cc097e81135faa5f261264f4dfaa22bd5ee2875a4e773bd6"}, + {file = "black-23.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a9acad1451632021ee0d146c8765782a0c3846e0e0ea46659d7c4f89d9b212b"}, + {file = "black-23.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:fc7f6a44d52747e65a02558e1d807c82df1d66ffa80a601862040a43ec2e3142"}, + {file = "black-23.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7f622b6822f02bfaf2a5cd31fdb7cd86fcf33dab6ced5185c35f5db98260b055"}, + {file = "black-23.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:250d7e60f323fcfc8ea6c800d5eba12f7967400eb6c2d21ae85ad31c204fb1f4"}, + {file = "black-23.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5133f5507007ba08d8b7b263c7aa0f931af5ba88a29beacc4b2dc23fcefe9c06"}, + {file = "black-23.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:421f3e44aa67138ab1b9bfbc22ee3780b22fa5b291e4db8ab7eee95200726b07"}, + {file = "black-23.11.0-py3-none-any.whl", hash = "sha256:54caaa703227c6e0c87b76326d0862184729a69b73d3b7305b6288e1d830067e"}, + {file = "black-23.11.0.tar.gz", hash = "sha256:4c68855825ff432d197229846f971bc4d6666ce90492e5b02013bcaca4d9ab05"}, ] [package.dependencies] @@ -204,7 +231,7 @@ packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] @@ -232,25 +259,26 @@ css = ["tinycss2 (>=1.1.0,<1.2)"] [[package]] name = "build" -version = "0.10.0" +version = "1.0.3" description = "A simple, correct Python build frontend" optional = false python-versions = ">= 3.7" files = [ - {file = "build-0.10.0-py3-none-any.whl", hash = "sha256:af266720050a66c893a6096a2f410989eeac74ff9a68ba194b3f6473e8e26171"}, - {file = "build-0.10.0.tar.gz", hash = "sha256:d5b71264afdb5951d6704482aac78de887c80691c52b88a9ad195983ca2c9269"}, + {file = "build-1.0.3-py3-none-any.whl", hash = "sha256:589bf99a67df7c9cf07ec0ac0e5e2ea5d4b37ac63301c4986d1acb126aa83f8f"}, + {file = "build-1.0.3.tar.gz", hash = "sha256:538aab1b64f9828977f84bc63ae570b060a8ed1be419e7870b8b4fc5e6ea553b"}, ] [package.dependencies] colorama = {version = "*", markers = "os_name == \"nt\""} +importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""} packaging = ">=19.0" pyproject_hooks = "*" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} [package.extras] -docs = ["furo (>=2021.08.31)", "sphinx (>=4.0,<5.0)", "sphinx-argparse-cli (>=1.5)", "sphinx-autodoc-typehints (>=1.10)"] -test = ["filelock (>=3)", "pytest (>=6.2.4)", "pytest-cov (>=2.12)", "pytest-mock (>=2)", "pytest-rerunfailures (>=9.1)", "pytest-xdist (>=1.34)", "setuptools (>=42.0.0)", "setuptools (>=56.0.0)", "toml (>=0.10.0)", "wheel (>=0.36.0)"] -typing = ["importlib-metadata (>=5.1)", "mypy (==0.991)", "tomli", "typing-extensions (>=3.7.4.3)"] +docs = ["furo (>=2023.08.17)", "sphinx (>=7.0,<8.0)", "sphinx-argparse-cli (>=1.5)", "sphinx-autodoc-typehints (>=1.10)", "sphinx-issues (>=3.0.0)"] +test = ["filelock (>=3)", "pytest (>=6.2.4)", "pytest-cov (>=2.12)", "pytest-mock (>=2)", "pytest-rerunfailures (>=9.1)", "pytest-xdist (>=1.34)", "setuptools (>=42.0.0)", "setuptools (>=56.0.0)", "setuptools (>=56.0.0)", "setuptools (>=67.8.0)", "wheel (>=0.36.0)"] +typing = ["importlib-metadata (>=5.1)", "mypy (>=1.5.0,<1.6.0)", "tomli", "typing-extensions (>=3.7.4.3)"] virtualenv = ["virtualenv (>=20.0.35)"] [[package]] @@ -727,19 +755,19 @@ testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "p [[package]] name = "flake8" -version = "6.0.0" +version = "6.1.0" description = "the modular source code checker: pep8 pyflakes and co" optional = false python-versions = ">=3.8.1" files = [ - {file = "flake8-6.0.0-py2.py3-none-any.whl", hash = "sha256:3833794e27ff64ea4e9cf5d410082a8b97ff1a06c16aa3d2027339cd0f1195c7"}, - {file = "flake8-6.0.0.tar.gz", hash = "sha256:c61007e76655af75e6785a931f452915b371dc48f56efd765247c8fe68f2b181"}, + {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, + {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, ] [package.dependencies] mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.10.0,<2.11.0" -pyflakes = ">=3.0.0,<3.1.0" +pycodestyle = ">=2.11.0,<2.12.0" +pyflakes = ">=3.1.0,<3.2.0" [[package]] name = "fonttools" @@ -985,17 +1013,6 @@ qtconsole = ["qtconsole"] test = ["pytest (<7.1)", "pytest-asyncio", "testpath"] test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pandas", "pytest (<7.1)", "pytest-asyncio", "testpath", "trio"] -[[package]] -name = "ipython-genutils" -version = "0.2.0" -description = "Vestigial utilities from IPython" -optional = false -python-versions = "*" -files = [ - {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, - {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, -] - [[package]] name = "isoduration" version = "20.11.0" @@ -1096,6 +1113,20 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "json5" +version = "0.9.14" +description = "A Python implementation of the JSON5 data format." +optional = false +python-versions = "*" +files = [ + {file = "json5-0.9.14-py2.py3-none-any.whl", hash = "sha256:740c7f1b9e584a468dbb2939d8d458db3427f2c93ae2139d05f47e453eae964f"}, + {file = "json5-0.9.14.tar.gz", hash = "sha256:9ed66c3a6ca3510a976a9ef9b8c0787de24802724ab1860bc0153c7fdd589b02"}, +] + +[package.extras] +dev = ["hypothesis"] + [[package]] name = "jsonpointer" version = "2.4" @@ -1220,6 +1251,21 @@ cli = ["click", "rich"] docs = ["jupyterlite-sphinx", "myst-parser", "pydata-sphinx-theme", "sphinxcontrib-spelling"] test = ["click", "coverage", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (>=0.19.0)", "pytest-console-scripts", "pytest-cov", "rich"] +[[package]] +name = "jupyter-lsp" +version = "2.2.0" +description = "Multi-Language Server WebSocket proxy for Jupyter Notebook/Lab server" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter-lsp-2.2.0.tar.gz", hash = "sha256:8ebbcb533adb41e5d635eb8fe82956b0aafbf0fd443b6c4bfa906edeeb8635a1"}, + {file = "jupyter_lsp-2.2.0-py3-none-any.whl", hash = "sha256:9e06b8b4f7dd50300b70dd1a78c0c3b0c3d8fa68e0f2d8a5d1fbab62072aca3f"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""} +jupyter-server = ">=1.1.2" + [[package]] name = "jupyter-server" version = "2.7.2" @@ -1275,6 +1321,39 @@ terminado = ">=0.8.3" docs = ["jinja2", "jupyter-server", "mistune (<3.0)", "myst-parser", "nbformat", "packaging", "pydata-sphinx-theme", "sphinxcontrib-github-alt", "sphinxcontrib-openapi", "sphinxcontrib-spelling", "sphinxemoji", "tornado"] test = ["coverage", "jupyter-server (>=2.0.0)", "pytest (>=7.0)", "pytest-cov", "pytest-jupyter[server] (>=0.5.3)", "pytest-timeout"] +[[package]] +name = "jupyterlab" +version = "4.0.7" +description = "JupyterLab computational environment" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyterlab-4.0.7-py3-none-any.whl", hash = "sha256:08683045117cc495531fdb39c22ababb9aaac6977a45e67cfad20046564c9c7c"}, + {file = "jupyterlab-4.0.7.tar.gz", hash = "sha256:48792efd9f962b2bcda1f87d72168ff122c288b1d97d32109e4a11b33dc862be"}, +] + +[package.dependencies] +async-lru = ">=1.0.0" +importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""} +importlib-resources = {version = ">=1.4", markers = "python_version < \"3.9\""} +ipykernel = "*" +jinja2 = ">=3.0.3" +jupyter-core = "*" +jupyter-lsp = ">=2.0.0" +jupyter-server = ">=2.4.0,<3" +jupyterlab-server = ">=2.19.0,<3" +notebook-shim = ">=0.2" +packaging = "*" +tomli = {version = "*", markers = "python_version < \"3.11\""} +tornado = ">=6.2.0" +traitlets = "*" + +[package.extras] +dev = ["black[jupyter] (==23.7.0)", "build", "bump2version", "coverage", "hatch", "pre-commit", "pytest-cov", "ruff (==0.0.286)"] +docs = ["jsx-lexer", "myst-parser", "pydata-sphinx-theme (>=0.13.0)", "pytest", "pytest-check-links", "pytest-tornasync", "sphinx (>=1.8,<7.2.0)", "sphinx-copybutton"] +docs-screenshots = ["altair (==5.0.1)", "ipython (==8.14.0)", "ipywidgets (==8.0.6)", "jupyterlab-geojson (==3.4.0)", "jupyterlab-language-pack-zh-cn (==4.0.post0)", "matplotlib (==3.7.1)", "nbconvert (>=7.0.0)", "pandas (==2.0.2)", "scipy (==1.10.1)", "vega-datasets (==0.9.0)"] +test = ["coverage", "pytest (>=7.0)", "pytest-check-links (>=0.7)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter (>=0.5.3)", "pytest-timeout", "pytest-tornasync", "requests", "requests-cache", "virtualenv"] + [[package]] name = "jupyterlab-pygments" version = "0.2.2" @@ -1286,6 +1365,32 @@ files = [ {file = "jupyterlab_pygments-0.2.2.tar.gz", hash = "sha256:7405d7fde60819d905a9fa8ce89e4cd830e318cdad22a0030f7a901da705585d"}, ] +[[package]] +name = "jupyterlab-server" +version = "2.25.0" +description = "A set of server components for JupyterLab and JupyterLab like applications." +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyterlab_server-2.25.0-py3-none-any.whl", hash = "sha256:c9f67a98b295c5dee87f41551b0558374e45d449f3edca153dd722140630dcb2"}, + {file = "jupyterlab_server-2.25.0.tar.gz", hash = "sha256:77c2f1f282d610f95e496e20d5bf1d2a7706826dfb7b18f3378ae2870d272fb7"}, +] + +[package.dependencies] +babel = ">=2.10" +importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""} +jinja2 = ">=3.0.3" +json5 = ">=0.9.0" +jsonschema = ">=4.18.0" +jupyter-server = ">=1.21,<3" +packaging = ">=21.3" +requests = ">=2.31" + +[package.extras] +docs = ["autodoc-traits", "jinja2 (<3.2.0)", "mistune (<4)", "myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-copybutton", "sphinxcontrib-openapi (>0.8)"] +openapi = ["openapi-core (>=0.18.0,<0.19.0)", "ruamel-yaml"] +test = ["hatch", "ipykernel", "openapi-core (>=0.18.0,<0.19.0)", "openapi-spec-validator (>=0.6.0,<0.7.0)", "pytest (>=7.0)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter[server] (>=0.6.2)", "pytest-timeout", "requests-mock", "ruamel-yaml", "sphinxcontrib-spelling", "strict-rfc3339", "werkzeug"] + [[package]] name = "keyring" version = "24.2.0" @@ -1499,52 +1604,58 @@ files = [ [[package]] name = "matplotlib" -version = "3.7.2" +version = "3.7.3" description = "Python plotting package" optional = false python-versions = ">=3.8" files = [ - {file = "matplotlib-3.7.2-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:2699f7e73a76d4c110f4f25be9d2496d6ab4f17345307738557d345f099e07de"}, - {file = "matplotlib-3.7.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a8035ba590658bae7562786c9cc6ea1a84aa49d3afab157e414c9e2ea74f496d"}, - {file = "matplotlib-3.7.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2f8e4a49493add46ad4a8c92f63e19d548b2b6ebbed75c6b4c7f46f57d36cdd1"}, - {file = "matplotlib-3.7.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71667eb2ccca4c3537d9414b1bc00554cb7f91527c17ee4ec38027201f8f1603"}, - {file = "matplotlib-3.7.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:152ee0b569a37630d8628534c628456b28686e085d51394da6b71ef84c4da201"}, - {file = "matplotlib-3.7.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:070f8dddd1f5939e60aacb8fa08f19551f4b0140fab16a3669d5cd6e9cb28fc8"}, - {file = "matplotlib-3.7.2-cp310-cp310-win32.whl", hash = "sha256:fdbb46fad4fb47443b5b8ac76904b2e7a66556844f33370861b4788db0f8816a"}, - {file = "matplotlib-3.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:23fb1750934e5f0128f9423db27c474aa32534cec21f7b2153262b066a581fd1"}, - {file = "matplotlib-3.7.2-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:30e1409b857aa8a747c5d4f85f63a79e479835f8dffc52992ac1f3f25837b544"}, - {file = "matplotlib-3.7.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:50e0a55ec74bf2d7a0ebf50ac580a209582c2dd0f7ab51bc270f1b4a0027454e"}, - {file = "matplotlib-3.7.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ac60daa1dc83e8821eed155796b0f7888b6b916cf61d620a4ddd8200ac70cd64"}, - {file = "matplotlib-3.7.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:305e3da477dc8607336ba10bac96986d6308d614706cae2efe7d3ffa60465b24"}, - {file = "matplotlib-3.7.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c308b255efb9b06b23874236ec0f10f026673ad6515f602027cc8ac7805352d"}, - {file = "matplotlib-3.7.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60c521e21031632aa0d87ca5ba0c1c05f3daacadb34c093585a0be6780f698e4"}, - {file = "matplotlib-3.7.2-cp311-cp311-win32.whl", hash = "sha256:26bede320d77e469fdf1bde212de0ec889169b04f7f1179b8930d66f82b30cbc"}, - {file = "matplotlib-3.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:af4860132c8c05261a5f5f8467f1b269bf1c7c23902d75f2be57c4a7f2394b3e"}, - {file = "matplotlib-3.7.2-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:a1733b8e84e7e40a9853e505fe68cc54339f97273bdfe6f3ed980095f769ddc7"}, - {file = "matplotlib-3.7.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d9881356dc48e58910c53af82b57183879129fa30492be69058c5b0d9fddf391"}, - {file = "matplotlib-3.7.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f081c03f413f59390a80b3e351cc2b2ea0205839714dbc364519bcf51f4b56ca"}, - {file = "matplotlib-3.7.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1cd120fca3407a225168238b790bd5c528f0fafde6172b140a2f3ab7a4ea63e9"}, - {file = "matplotlib-3.7.2-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a2c1590b90aa7bd741b54c62b78de05d4186271e34e2377e0289d943b3522273"}, - {file = "matplotlib-3.7.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d2ff3c984b8a569bc1383cd468fc06b70d7b59d5c2854ca39f1436ae8394117"}, - {file = "matplotlib-3.7.2-cp38-cp38-win32.whl", hash = "sha256:5dea00b62d28654b71ca92463656d80646675628d0828e08a5f3b57e12869e13"}, - {file = "matplotlib-3.7.2-cp38-cp38-win_amd64.whl", hash = "sha256:0f506a1776ee94f9e131af1ac6efa6e5bc7cb606a3e389b0ccb6e657f60bb676"}, - {file = "matplotlib-3.7.2-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:6515e878f91894c2e4340d81f0911857998ccaf04dbc1bba781e3d89cbf70608"}, - {file = "matplotlib-3.7.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:71f7a8c6b124e904db550f5b9fe483d28b896d4135e45c4ea381ad3b8a0e3256"}, - {file = "matplotlib-3.7.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:12f01b92ecd518e0697da4d97d163b2b3aa55eb3eb4e2c98235b3396d7dad55f"}, - {file = "matplotlib-3.7.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7e28d6396563955f7af437894a36bf2b279462239a41028323e04b85179058b"}, - {file = "matplotlib-3.7.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbcf59334ff645e6a67cd5f78b4b2cdb76384cdf587fa0d2dc85f634a72e1a3e"}, - {file = "matplotlib-3.7.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:318c89edde72ff95d8df67d82aca03861240512994a597a435a1011ba18dbc7f"}, - {file = "matplotlib-3.7.2-cp39-cp39-win32.whl", hash = "sha256:ce55289d5659b5b12b3db4dc9b7075b70cef5631e56530f14b2945e8836f2d20"}, - {file = "matplotlib-3.7.2-cp39-cp39-win_amd64.whl", hash = "sha256:2ecb5be2b2815431c81dc115667e33da0f5a1bcf6143980d180d09a717c4a12e"}, - {file = "matplotlib-3.7.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:fdcd28360dbb6203fb5219b1a5658df226ac9bebc2542a9e8f457de959d713d0"}, - {file = "matplotlib-3.7.2-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c3cca3e842b11b55b52c6fb8bd6a4088693829acbfcdb3e815fa9b7d5c92c1b"}, - {file = "matplotlib-3.7.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebf577c7a6744e9e1bd3fee45fc74a02710b214f94e2bde344912d85e0c9af7c"}, - {file = "matplotlib-3.7.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:936bba394682049919dda062d33435b3be211dc3dcaa011e09634f060ec878b2"}, - {file = "matplotlib-3.7.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:bc221ffbc2150458b1cd71cdd9ddd5bb37962b036e41b8be258280b5b01da1dd"}, - {file = "matplotlib-3.7.2-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35d74ebdb3f71f112b36c2629cf32323adfbf42679e2751252acd468f5001c07"}, - {file = "matplotlib-3.7.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:717157e61b3a71d3d26ad4e1770dc85156c9af435659a25ee6407dc866cb258d"}, - {file = "matplotlib-3.7.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:20f844d6be031948148ba49605c8b96dfe7d3711d1b63592830d650622458c11"}, - {file = "matplotlib-3.7.2.tar.gz", hash = "sha256:a8cdb91dddb04436bd2f098b8fdf4b81352e68cf4d2c6756fcc414791076569b"}, + {file = "matplotlib-3.7.3-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:085c33b27561d9c04386789d5aa5eb4a932ddef43cfcdd0e01735f9a6e85ce0c"}, + {file = "matplotlib-3.7.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:c568e80e1c17f68a727f30f591926751b97b98314d8e59804f54f86ae6fa6a22"}, + {file = "matplotlib-3.7.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7baf98c5ad59c5c4743ea884bb025cbffa52dacdfdac0da3e6021a285a90377e"}, + {file = "matplotlib-3.7.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:236024f582e40dac39bca592258888b38ae47a9fed7b8de652d68d3d02d47d2b"}, + {file = "matplotlib-3.7.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12b4f6795efea037ce2d41e7c417ad8bd02d5719c6ad4a8450a0708f4a1cfb89"}, + {file = "matplotlib-3.7.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b2136cc6c5415b78977e0e8c608647d597204b05b1d9089ccf513c7d913733"}, + {file = "matplotlib-3.7.3-cp310-cp310-win32.whl", hash = "sha256:122dcbf9be0086e2a95d9e5e0632dbf3bd5b65eaa68c369363310a6c87753059"}, + {file = "matplotlib-3.7.3-cp310-cp310-win_amd64.whl", hash = "sha256:4aab27d9e33293389e3c1d7c881d414a72bdfda0fedc3a6bf46c6fa88d9b8015"}, + {file = "matplotlib-3.7.3-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:d5adc743de91e8e0b13df60deb1b1c285b8effea3d66223afceb14b63c9b05de"}, + {file = "matplotlib-3.7.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:55de4cf7cd0071b8ebf203981b53ab64f988a0a1f897a2dff300a1124e8bcd8b"}, + {file = "matplotlib-3.7.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ac03377fd908aaee2312d0b11735753e907adb6f4d1d102de5e2425249693f6c"}, + {file = "matplotlib-3.7.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:755bafc10a46918ce9a39980009b54b02dd249594e5adf52f9c56acfddb5d0b7"}, + {file = "matplotlib-3.7.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a6094c6f8e8d18db631754df4fe9a34dec3caf074f6869a7db09f18f9b1d6b2"}, + {file = "matplotlib-3.7.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:272dba2f1b107790ed78ebf5385b8d14b27ad9e90419de340364b49fe549a993"}, + {file = "matplotlib-3.7.3-cp311-cp311-win32.whl", hash = "sha256:591c123bed1cb4b9996fb60b41a6d89c2ec4943244540776c5f1283fb6960a53"}, + {file = "matplotlib-3.7.3-cp311-cp311-win_amd64.whl", hash = "sha256:3bf3a178c6504694cee8b88b353df0051583f2f6f8faa146f67115c27c856881"}, + {file = "matplotlib-3.7.3-cp312-cp312-macosx_10_12_universal2.whl", hash = "sha256:edf54cac8ee3603f3093616b40a931e8c063969756a4d78a86e82c2fea9659f7"}, + {file = "matplotlib-3.7.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:91e36a85ea639a1ba9f91427041eac064b04829945fe331a92617b6cb21d27e5"}, + {file = "matplotlib-3.7.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:caf5eaaf7c68f8d7df269dfbcaf46f48a70ff482bfcebdcc97519671023f2a7d"}, + {file = "matplotlib-3.7.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74bf57f505efea376097e948b7cdd87191a7ce8180616390aef496639edf601f"}, + {file = "matplotlib-3.7.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee152a88a0da527840a426535514b6ed8ac4240eb856b1da92cf48124320e346"}, + {file = "matplotlib-3.7.3-cp312-cp312-win_amd64.whl", hash = "sha256:67a410a9c9e07cbc83581eeea144bbe298870bf0ac0ee2f2e10a015ab7efee19"}, + {file = "matplotlib-3.7.3-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:259999c05285cb993d7f2a419cea547863fa215379eda81f7254c9e932963729"}, + {file = "matplotlib-3.7.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:3f4e7fd5a6157e1d018ce2166ec8e531a481dd4a36f035b5c23edfe05a25419a"}, + {file = "matplotlib-3.7.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:faa3d12d8811d08d14080a8b7b9caea9a457dc495350166b56df0db4b9909ef5"}, + {file = "matplotlib-3.7.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:336e88900c11441e458da01c8414fc57e04e17f9d3bb94958a76faa2652bcf6b"}, + {file = "matplotlib-3.7.3-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:12f4c0dd8aa280d796c8772ea8265a14f11a04319baa3a16daa5556065e8baea"}, + {file = "matplotlib-3.7.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1990955b11e7918d256cf3b956b10997f405b7917a3f1c7d8e69c1d15c7b1930"}, + {file = "matplotlib-3.7.3-cp38-cp38-win32.whl", hash = "sha256:e78707b751260b42b721507ad7aa60fe4026d7f51c74cca6b9cd8b123ebb633a"}, + {file = "matplotlib-3.7.3-cp38-cp38-win_amd64.whl", hash = "sha256:e594ee43c59ea39ca5c6244667cac9d017a3527febc31f5532ad9135cf7469ec"}, + {file = "matplotlib-3.7.3-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:6eaa1cf0e94c936a26b78f6d756c5fbc12e0a58c8a68b7248a2a31456ce4e234"}, + {file = "matplotlib-3.7.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:0a97af9d22e8ebedc9f00b043d9bbd29a375e9e10b656982012dded44c10fd77"}, + {file = "matplotlib-3.7.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1f9c6c16597af660433ab330b59ee2934b832ee1fabcaf5cbde7b2add840f31e"}, + {file = "matplotlib-3.7.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7240259b4b9cbc62381f6378cff4d57af539162a18e832c1e48042fabc40b6b"}, + {file = "matplotlib-3.7.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:747c6191d2e88ae854809e69aa358dbf852ff1a5738401b85c1cc9012309897a"}, + {file = "matplotlib-3.7.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec726b08a5275d827aa91bb951e68234a4423adb91cf65bc0fcdc0f2777663f7"}, + {file = "matplotlib-3.7.3-cp39-cp39-win32.whl", hash = "sha256:40e3b9b450c6534f07278310c4e34caff41c2a42377e4b9d47b0f8d3ac1083a2"}, + {file = "matplotlib-3.7.3-cp39-cp39-win_amd64.whl", hash = "sha256:dfc118642903a23e309b1da32886bb39a4314147d013e820c86b5fb4cb2e36d0"}, + {file = "matplotlib-3.7.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:165c8082bf8fc0360c24aa4724a22eaadbfd8c28bf1ccf7e94d685cad48261e4"}, + {file = "matplotlib-3.7.3-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ebd8470cc2a3594746ff0513aecbfa2c55ff6f58e6cef2efb1a54eb87c88ffa2"}, + {file = "matplotlib-3.7.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7153453669c9672b52095119fd21dd032d19225d48413a2871519b17db4b0fde"}, + {file = "matplotlib-3.7.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:498a08267dc69dd8f24c4b5d7423fa584d7ce0027ba71f7881df05fc09b89bb7"}, + {file = "matplotlib-3.7.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d48999c4b19b5a0c058c9cd828ff6fc7748390679f6cf9a2ad653a3e802c87d3"}, + {file = "matplotlib-3.7.3-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22d65d18b4ee8070a5fea5761d59293f1f9e2fac37ec9ce090463b0e629432fd"}, + {file = "matplotlib-3.7.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c40cde976c36693cc0767e27cf5f443f91c23520060bd9496678364adfafe9c"}, + {file = "matplotlib-3.7.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:39018a2b17592448fbfdf4b8352955e6c3905359939791d4ff429296494d1a0c"}, + {file = "matplotlib-3.7.3.tar.gz", hash = "sha256:f09b3dd6bdeb588de91f853bbb2d6f0ff8ab693485b0c49035eaa510cb4f142e"}, ] [package.dependencies] @@ -1553,11 +1664,12 @@ cycler = ">=0.10" fonttools = ">=4.22.0" importlib-resources = {version = ">=3.2.0", markers = "python_version < \"3.10\""} kiwisolver = ">=1.0.1" -numpy = ">=1.20" +numpy = ">=1.20,<2" packaging = ">=20.0" pillow = ">=6.2.0" -pyparsing = ">=2.3.1,<3.1" +pyparsing = ">=2.3.1" python-dateutil = ">=2.7" +setuptools_scm = ">=7" [[package]] name = "matplotlib-inline" @@ -1619,13 +1731,13 @@ files = [ [[package]] name = "mkdocs" -version = "1.4.3" +version = "1.5.3" description = "Project documentation with Markdown." optional = false python-versions = ">=3.7" files = [ - {file = "mkdocs-1.4.3-py3-none-any.whl", hash = "sha256:6ee46d309bda331aac915cd24aab882c179a933bd9e77b80ce7d2eaaa3f689dd"}, - {file = "mkdocs-1.4.3.tar.gz", hash = "sha256:5955093bbd4dd2e9403c5afaf57324ad8b04f16886512a3ee6ef828956481c57"}, + {file = "mkdocs-1.5.3-py3-none-any.whl", hash = "sha256:3b3a78e736b31158d64dbb2f8ba29bd46a379d0c6e324c2246c3bc3d2189cfc1"}, + {file = "mkdocs-1.5.3.tar.gz", hash = "sha256:eb7c99214dcb945313ba30426c2451b735992c73c2e10838f76d09e39ff4d0e2"}, ] [package.dependencies] @@ -1634,16 +1746,19 @@ colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} ghp-import = ">=1.0" importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} jinja2 = ">=2.11.1" -markdown = ">=3.2.1,<3.4" +markdown = ">=3.2.1" +markupsafe = ">=2.0.1" mergedeep = ">=1.3.4" packaging = ">=20.5" +pathspec = ">=0.11.1" +platformdirs = ">=2.2.0" pyyaml = ">=5.1" pyyaml-env-tag = ">=0.1" watchdog = ">=2.0" [package.extras] i18n = ["babel (>=2.9.0)"] -min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"] +min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pathspec (==0.11.1)", "platformdirs (==2.2.0)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"] [[package]] name = "mkdocs-autorefs" @@ -1662,56 +1777,67 @@ mkdocs = ">=1.1" [[package]] name = "mkdocs-material" -version = "9.1.19" +version = "9.4.8" description = "Documentation that simply works" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "mkdocs_material-9.1.19-py3-none-any.whl", hash = "sha256:fb0a149294b319aedf36983919d8c40c9e566db21ead16258e20ebd2e6c0961c"}, - {file = "mkdocs_material-9.1.19.tar.gz", hash = "sha256:73b94b08c765e92a80645aac58d6a741fc5f587deec2b715489c714827b15a6f"}, + {file = "mkdocs_material-9.4.8-py3-none-any.whl", hash = "sha256:8b20f6851bddeef37dced903893cd176cf13a21a482e97705a103c45f06ce9b9"}, + {file = "mkdocs_material-9.4.8.tar.gz", hash = "sha256:f0c101453e8bc12b040e8b64ca39a405d950d8402609b1378cc2b98976e74b5f"}, ] [package.dependencies] -colorama = ">=0.4" -jinja2 = ">=3.0" -markdown = ">=3.2" -mkdocs = ">=1.4.2" -mkdocs-material-extensions = ">=1.1" -pygments = ">=2.14" -pymdown-extensions = ">=9.9.1" -regex = ">=2022.4.24" -requests = ">=2.26" +babel = ">=2.10,<3.0" +colorama = ">=0.4,<1.0" +jinja2 = ">=3.0,<4.0" +markdown = ">=3.2,<4.0" +mkdocs = ">=1.5.3,<2.0" +mkdocs-material-extensions = ">=1.3,<2.0" +paginate = ">=0.5,<1.0" +pygments = ">=2.16,<3.0" +pymdown-extensions = ">=10.2,<11.0" +regex = ">=2022.4" +requests = ">=2.26,<3.0" + +[package.extras] +git = ["mkdocs-git-committers-plugin-2 (>=1.1,<2.0)", "mkdocs-git-revision-date-localized-plugin (>=1.2,<2.0)"] +imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=9.4,<10.0)"] +recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2.0)", "mkdocs-rss-plugin (>=1.6,<2.0)"] [[package]] name = "mkdocs-material-extensions" -version = "1.1.1" +version = "1.3" description = "Extension pack for Python Markdown and MkDocs Material." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "mkdocs_material_extensions-1.1.1-py3-none-any.whl", hash = "sha256:e41d9f38e4798b6617ad98ca8f7f1157b1e4385ac1459ca1e4ea219b556df945"}, - {file = "mkdocs_material_extensions-1.1.1.tar.gz", hash = "sha256:9c003da71e2cc2493d910237448c672e00cefc800d3d6ae93d2fc69979e3bd93"}, + {file = "mkdocs_material_extensions-1.3-py3-none-any.whl", hash = "sha256:0297cc48ba68a9fdd1ef3780a3b41b534b0d0df1d1181a44676fda5f464eeadc"}, + {file = "mkdocs_material_extensions-1.3.tar.gz", hash = "sha256:f0446091503acb110a7cab9349cbc90eeac51b58d1caa92a704a81ca1e24ddbd"}, ] [[package]] name = "mkdocstrings" -version = "0.20.0" +version = "0.24.0" description = "Automatic documentation from sources, for MkDocs." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "mkdocstrings-0.20.0-py3-none-any.whl", hash = "sha256:f17fc2c4f760ec302b069075ef9e31045aa6372ca91d2f35ded3adba8e25a472"}, - {file = "mkdocstrings-0.20.0.tar.gz", hash = "sha256:c757f4f646d4f939491d6bc9256bfe33e36c5f8026392f49eaa351d241c838e5"}, + {file = "mkdocstrings-0.24.0-py3-none-any.whl", hash = "sha256:f4908560c10f587326d8f5165d1908817b2e280bbf707607f601c996366a2264"}, + {file = "mkdocstrings-0.24.0.tar.gz", hash = "sha256:222b1165be41257b494a9d29b14135d2b7ca43f38161d5b10caae03b87bd4f7e"}, ] [package.dependencies] +click = ">=7.0" +importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""} Jinja2 = ">=2.11.1" Markdown = ">=3.3" MarkupSafe = ">=1.1" -mkdocs = ">=1.2" +mkdocs = ">=1.4" mkdocs-autorefs = ">=0.3.1" mkdocstrings-python = {version = ">=0.5.2", optional = true, markers = "extra == \"python\""} +platformdirs = ">=2.2.0" pymdown-extensions = ">=6.3" +typing-extensions = {version = ">=4.1", markers = "python_version < \"3.10\""} [package.extras] crystal = ["mkdocstrings-crystal (>=0.3.4)"] @@ -1746,37 +1872,38 @@ files = [ [[package]] name = "mypy" -version = "1.4.1" +version = "1.7.0" description = "Optional static typing for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"}, - {file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"}, - {file = "mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd"}, - {file = "mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc"}, - {file = "mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1"}, - {file = "mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462"}, - {file = "mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258"}, - {file = "mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2"}, - {file = "mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7"}, - {file = "mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01"}, - {file = "mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b"}, - {file = "mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"}, - {file = "mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7"}, - {file = "mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9"}, - {file = "mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042"}, - {file = "mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3"}, - {file = "mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6"}, - {file = "mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f"}, - {file = "mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc"}, - {file = "mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828"}, - {file = "mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3"}, - {file = "mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816"}, - {file = "mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c"}, - {file = "mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f"}, - {file = "mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4"}, - {file = "mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b"}, + {file = "mypy-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5da84d7bf257fd8f66b4f759a904fd2c5a765f70d8b52dde62b521972a0a2357"}, + {file = "mypy-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a3637c03f4025f6405737570d6cbfa4f1400eb3c649317634d273687a09ffc2f"}, + {file = "mypy-1.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b633f188fc5ae1b6edca39dae566974d7ef4e9aaaae00bc36efe1f855e5173ac"}, + {file = "mypy-1.7.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d6ed9a3997b90c6f891138e3f83fb8f475c74db4ccaa942a1c7bf99e83a989a1"}, + {file = "mypy-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:1fe46e96ae319df21359c8db77e1aecac8e5949da4773c0274c0ef3d8d1268a9"}, + {file = "mypy-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:df67fbeb666ee8828f675fee724cc2cbd2e4828cc3df56703e02fe6a421b7401"}, + {file = "mypy-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a79cdc12a02eb526d808a32a934c6fe6df07b05f3573d210e41808020aed8b5d"}, + {file = "mypy-1.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f65f385a6f43211effe8c682e8ec3f55d79391f70a201575def73d08db68ead1"}, + {file = "mypy-1.7.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e81ffd120ee24959b449b647c4b2fbfcf8acf3465e082b8d58fd6c4c2b27e46"}, + {file = "mypy-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:f29386804c3577c83d76520abf18cfcd7d68264c7e431c5907d250ab502658ee"}, + {file = "mypy-1.7.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:87c076c174e2c7ef8ab416c4e252d94c08cd4980a10967754f91571070bf5fbe"}, + {file = "mypy-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6cb8d5f6d0fcd9e708bb190b224089e45902cacef6f6915481806b0c77f7786d"}, + {file = "mypy-1.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d93e76c2256aa50d9c82a88e2f569232e9862c9982095f6d54e13509f01222fc"}, + {file = "mypy-1.7.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cddee95dea7990e2215576fae95f6b78a8c12f4c089d7e4367564704e99118d3"}, + {file = "mypy-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:d01921dbd691c4061a3e2ecdbfbfad029410c5c2b1ee88946bf45c62c6c91210"}, + {file = "mypy-1.7.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:185cff9b9a7fec1f9f7d8352dff8a4c713b2e3eea9c6c4b5ff7f0edf46b91e41"}, + {file = "mypy-1.7.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7a7b1e399c47b18feb6f8ad4a3eef3813e28c1e871ea7d4ea5d444b2ac03c418"}, + {file = "mypy-1.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc9fe455ad58a20ec68599139ed1113b21f977b536a91b42bef3ffed5cce7391"}, + {file = "mypy-1.7.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d0fa29919d2e720c8dbaf07d5578f93d7b313c3e9954c8ec05b6d83da592e5d9"}, + {file = "mypy-1.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b53655a295c1ed1af9e96b462a736bf083adba7b314ae775563e3fb4e6795f5"}, + {file = "mypy-1.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c1b06b4b109e342f7dccc9efda965fc3970a604db70f8560ddfdee7ef19afb05"}, + {file = "mypy-1.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bf7a2f0a6907f231d5e41adba1a82d7d88cf1f61a70335889412dec99feeb0f8"}, + {file = "mypy-1.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:551d4a0cdcbd1d2cccdcc7cb516bb4ae888794929f5b040bb51aae1846062901"}, + {file = "mypy-1.7.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:55d28d7963bef00c330cb6461db80b0b72afe2f3c4e2963c99517cf06454e665"}, + {file = "mypy-1.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:870bd1ffc8a5862e593185a4c169804f2744112b4a7c55b93eb50f48e7a77010"}, + {file = "mypy-1.7.0-py3-none-any.whl", hash = "sha256:96650d9a4c651bc2a4991cf46f100973f656d69edc7faf91844e87fe627f7e96"}, + {file = "mypy-1.7.0.tar.gz", hash = "sha256:1e280b5697202efa698372d2f39e9a6713a0395a756b1c6bd48995f8d72690dc"}, ] [package.dependencies] @@ -1787,7 +1914,7 @@ typing-extensions = ">=4.1.0" [package.extras] dmypy = ["psutil (>=4.0)"] install-types = ["pip"] -python2 = ["typed-ast (>=1.4.0,<2)"] +mypyc = ["setuptools (>=50)"] reports = ["lxml"] [[package]] @@ -1801,41 +1928,6 @@ files = [ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] -[[package]] -name = "nbclassic" -version = "1.0.0" -description = "Jupyter Notebook as a Jupyter Server extension." -optional = false -python-versions = ">=3.7" -files = [ - {file = "nbclassic-1.0.0-py3-none-any.whl", hash = "sha256:f99e4769b4750076cd4235c044b61232110733322384a94a63791d2e7beacc66"}, - {file = "nbclassic-1.0.0.tar.gz", hash = "sha256:0ae11eb2319455d805596bf320336cda9554b41d99ab9a3c31bf8180bffa30e3"}, -] - -[package.dependencies] -argon2-cffi = "*" -ipykernel = "*" -ipython-genutils = "*" -jinja2 = "*" -jupyter-client = ">=6.1.1" -jupyter-core = ">=4.6.1" -jupyter-server = ">=1.8" -nbconvert = ">=5" -nbformat = "*" -nest-asyncio = ">=1.5" -notebook-shim = ">=0.2.3" -prometheus-client = "*" -pyzmq = ">=17" -Send2Trash = ">=1.8.0" -terminado = ">=0.8.3" -tornado = ">=6.1" -traitlets = ">=4.2.1" - -[package.extras] -docs = ["myst-parser", "nbsphinx", "sphinx", "sphinx-rtd-theme", "sphinxcontrib-github-alt"] -json-logging = ["json-logging"] -test = ["coverage", "nbval", "pytest", "pytest-cov", "pytest-jupyter", "pytest-playwright", "pytest-tornasync", "requests", "requests-unixsocket", "testpath"] - [[package]] name = "nbclient" version = "0.8.0" @@ -1944,37 +2036,26 @@ setuptools = "*" [[package]] name = "notebook" -version = "6.5.4" -description = "A web-based notebook environment for interactive computing" +version = "7.0.6" +description = "Jupyter Notebook - A web-based notebook environment for interactive computing" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "notebook-6.5.4-py3-none-any.whl", hash = "sha256:dd17e78aefe64c768737b32bf171c1c766666a21cc79a44d37a1700771cab56f"}, - {file = "notebook-6.5.4.tar.gz", hash = "sha256:517209568bd47261e2def27a140e97d49070602eea0d226a696f42a7f16c9a4e"}, + {file = "notebook-7.0.6-py3-none-any.whl", hash = "sha256:0fe8f67102fea3744fedf652e4c15339390902ca70c5a31c4f547fa23da697cc"}, + {file = "notebook-7.0.6.tar.gz", hash = "sha256:ec6113b06529019f7f287819af06c97a2baf7a95ac21a8f6e32192898e9f9a58"}, ] [package.dependencies] -argon2-cffi = "*" -ipykernel = "*" -ipython-genutils = "*" -jinja2 = "*" -jupyter-client = ">=5.3.4" -jupyter-core = ">=4.6.1" -nbclassic = ">=0.4.7" -nbconvert = ">=5" -nbformat = "*" -nest-asyncio = ">=1.5" -prometheus-client = "*" -pyzmq = ">=17" -Send2Trash = ">=1.8.0" -terminado = ">=0.8.3" -tornado = ">=6.1" -traitlets = ">=4.2.1" +jupyter-server = ">=2.4.0,<3" +jupyterlab = ">=4.0.2,<5" +jupyterlab-server = ">=2.22.1,<3" +notebook-shim = ">=0.2,<0.3" +tornado = ">=6.2.0" [package.extras] -docs = ["myst-parser", "nbsphinx", "sphinx", "sphinx-rtd-theme", "sphinxcontrib-github-alt"] -json-logging = ["json-logging"] -test = ["coverage", "nbval", "pytest", "pytest-cov", "requests", "requests-unixsocket", "selenium (==4.1.5)", "testpath"] +dev = ["hatch", "pre-commit"] +docs = ["myst-parser", "nbsphinx", "pydata-sphinx-theme", "sphinx (>=1.3.6)", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] +test = ["importlib-resources (>=5.0)", "ipykernel", "jupyter-server[test] (>=2.4.0,<3)", "jupyterlab-server[test] (>=2.22.1,<3)", "nbval", "pytest (>=7.0)", "pytest-console-scripts", "pytest-timeout", "pytest-tornasync", "requests"] [[package]] name = "notebook-shim" @@ -2032,18 +2113,18 @@ files = [ [[package]] name = "opencv-python" -version = "4.8.0.74" +version = "4.8.1.78" description = "Wrapper package for OpenCV python bindings." optional = true python-versions = ">=3.6" files = [ - {file = "opencv-python-4.8.0.74.tar.gz", hash = "sha256:009e3ce356a0cd2d7423723e00a32fd3d3cc5bb5970ed27a9a1f8a8f221d1db5"}, - {file = "opencv_python-4.8.0.74-cp37-abi3-macosx_10_16_x86_64.whl", hash = "sha256:31d0d59fc8fdf703de4cec46c79b9f8d026fdde9d23d6e2e6a66809feeebbda9"}, - {file = "opencv_python-4.8.0.74-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:66eadb5882ee56848b67f9fb57aadcaca2f4c9d9d00a0ef11043041925b51291"}, - {file = "opencv_python-4.8.0.74-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:038ba7075e55cb8e2846663ae970f0fb776a45b48ee69a887bf4ee15e2570083"}, - {file = "opencv_python-4.8.0.74-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43dd0dfe331fb95767af581bf3b2781d7a72cf6560ddf2f55949fe547f3e5c9f"}, - {file = "opencv_python-4.8.0.74-cp37-abi3-win32.whl", hash = "sha256:458e5dc377f15fcf769d80314f3d885bd95457b1a2891bee67df2eb24a1d3a52"}, - {file = "opencv_python-4.8.0.74-cp37-abi3-win_amd64.whl", hash = "sha256:8fe0018d0056a5187c57120b6b3f6c3e706c13b45c48e54e86d245a9a16fac84"}, + {file = "opencv-python-4.8.1.78.tar.gz", hash = "sha256:cc7adbbcd1112877a39274106cb2752e04984bc01a031162952e97450d6117f6"}, + {file = "opencv_python-4.8.1.78-cp37-abi3-macosx_10_16_x86_64.whl", hash = "sha256:91d5f6f5209dc2635d496f6b8ca6573ecdad051a09e6b5de4c399b8e673c60da"}, + {file = "opencv_python-4.8.1.78-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc31f47e05447da8b3089faa0a07ffe80e114c91ce0b171e6424f9badbd1c5cd"}, + {file = "opencv_python-4.8.1.78-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9814beca408d3a0eca1bae7e3e5be68b07c17ecceb392b94170881216e09b319"}, + {file = "opencv_python-4.8.1.78-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4c406bdb41eb21ea51b4e90dfbc989c002786c3f601c236a99c59a54670a394"}, + {file = "opencv_python-4.8.1.78-cp37-abi3-win32.whl", hash = "sha256:a7aac3900fbacf55b551e7b53626c3dad4c71ce85643645c43e91fcb19045e47"}, + {file = "opencv_python-4.8.1.78-cp37-abi3-win_amd64.whl", hash = "sha256:b983197f97cfa6fcb74e1da1802c7497a6f94ed561aba6980f1f33123f904956"}, ] [package.dependencies] @@ -2058,18 +2139,18 @@ numpy = [ [[package]] name = "opencv-python-headless" -version = "4.8.0.74" +version = "4.8.1.78" description = "Wrapper package for OpenCV python bindings." optional = false python-versions = ">=3.6" files = [ - {file = "opencv-python-headless-4.8.0.74.tar.gz", hash = "sha256:a72770b8f2e08358b1faa41c8372b17d040aa9bb3e446ab7090e358f6f4e91ba"}, - {file = "opencv_python_headless-4.8.0.74-cp37-abi3-macosx_10_16_x86_64.whl", hash = "sha256:f645b17667fc9bffec8c3b3a72a217430c8d7090ed3ebb80fd8efc657f372d6c"}, - {file = "opencv_python_headless-4.8.0.74-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:7c88744ce922b81747c86f2b7f1b3ec84ff2f53477ee9219a5e75ae2012b443f"}, - {file = "opencv_python_headless-4.8.0.74-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34e92fef90e8f768d92dca875aefaff8b11556427f82484378a3848eb690d4c0"}, - {file = "opencv_python_headless-4.8.0.74-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8a88620c43a5d17f7df8317767da93b935656ab9236df92e1e2e0dd23195756"}, - {file = "opencv_python_headless-4.8.0.74-cp37-abi3-win32.whl", hash = "sha256:b52a6eb2d92fff0f660e6aade958cb4373fbfcbafa0488f7ae88c4e6ce3f47f6"}, - {file = "opencv_python_headless-4.8.0.74-cp37-abi3-win_amd64.whl", hash = "sha256:19d30269ae710f67e0ae549361a68b755e28599a572d5f72d0bbb177ab438a28"}, + {file = "opencv-python-headless-4.8.1.78.tar.gz", hash = "sha256:bc7197b42352f6f865c302a49140b889ec7cd957dd697e2d7fc016ad0d3f28f1"}, + {file = "opencv_python_headless-4.8.1.78-cp37-abi3-macosx_10_16_x86_64.whl", hash = "sha256:f3a33f644249f9ce1c913eac580e4b3ef4ce7cab0a71900274708959c2feb5e3"}, + {file = "opencv_python_headless-4.8.1.78-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:2c7d45721df9801c4dcd34683a15caa0e30f38b185263fec04a6eb274bc720f0"}, + {file = "opencv_python_headless-4.8.1.78-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b6bd6e1132b6f5dcb3a5bfe30fc4d341a7bfb26134da349a06c9255288ded94"}, + {file = "opencv_python_headless-4.8.1.78-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58e70d2f0915fe23e02c6e405588276c9397844a47d38b9c87fac5f7f9ba2dcc"}, + {file = "opencv_python_headless-4.8.1.78-cp37-abi3-win32.whl", hash = "sha256:382f8c7a6a14f80091284eecedd52cee4812231ee0eff1118592197b538d9252"}, + {file = "opencv_python_headless-4.8.1.78-cp37-abi3-win_amd64.whl", hash = "sha256:0a0f1e9f836f7d5bad1dd164694944c8761711cbdf4b36ebbd4815a8ef731079"}, ] [package.dependencies] @@ -2104,6 +2185,16 @@ files = [ {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, ] +[[package]] +name = "paginate" +version = "0.5.6" +description = "Divides large result sets into pages for easier browsing" +optional = false +python-versions = "*" +files = [ + {file = "paginate-0.5.6.tar.gz", hash = "sha256:5e6007b6a9398177a7e1648d04fdd9f8c9766a1a945bceac82f1929e8c78af2d"}, +] + [[package]] name = "pandocfilters" version = "1.5.0" @@ -2168,65 +2259,65 @@ files = [ [[package]] name = "pillow" -version = "10.0.1" +version = "10.1.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.8" files = [ - {file = "Pillow-10.0.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:8f06be50669087250f319b706decf69ca71fdecd829091a37cc89398ca4dc17a"}, - {file = "Pillow-10.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:50bd5f1ebafe9362ad622072a1d2f5850ecfa44303531ff14353a4059113b12d"}, - {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6a90167bcca1216606223a05e2cf991bb25b14695c518bc65639463d7db722d"}, - {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f11c9102c56ffb9ca87134bd025a43d2aba3f1155f508eff88f694b33a9c6d19"}, - {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:186f7e04248103482ea6354af6d5bcedb62941ee08f7f788a1c7707bc720c66f"}, - {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:0462b1496505a3462d0f35dc1c4d7b54069747d65d00ef48e736acda2c8cbdff"}, - {file = "Pillow-10.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d889b53ae2f030f756e61a7bff13684dcd77e9af8b10c6048fb2c559d6ed6eaf"}, - {file = "Pillow-10.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:552912dbca585b74d75279a7570dd29fa43b6d93594abb494ebb31ac19ace6bd"}, - {file = "Pillow-10.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:787bb0169d2385a798888e1122c980c6eff26bf941a8ea79747d35d8f9210ca0"}, - {file = "Pillow-10.0.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:fd2a5403a75b54661182b75ec6132437a181209b901446ee5724b589af8edef1"}, - {file = "Pillow-10.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2d7e91b4379f7a76b31c2dda84ab9e20c6220488e50f7822e59dac36b0cd92b1"}, - {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19e9adb3f22d4c416e7cd79b01375b17159d6990003633ff1d8377e21b7f1b21"}, - {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93139acd8109edcdeffd85e3af8ae7d88b258b3a1e13a038f542b79b6d255c54"}, - {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:92a23b0431941a33242b1f0ce6c88a952e09feeea9af4e8be48236a68ffe2205"}, - {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:cbe68deb8580462ca0d9eb56a81912f59eb4542e1ef8f987405e35a0179f4ea2"}, - {file = "Pillow-10.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:522ff4ac3aaf839242c6f4e5b406634bfea002469656ae8358644fc6c4856a3b"}, - {file = "Pillow-10.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:84efb46e8d881bb06b35d1d541aa87f574b58e87f781cbba8d200daa835b42e1"}, - {file = "Pillow-10.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:898f1d306298ff40dc1b9ca24824f0488f6f039bc0e25cfb549d3195ffa17088"}, - {file = "Pillow-10.0.1-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:bcf1207e2f2385a576832af02702de104be71301c2696d0012b1b93fe34aaa5b"}, - {file = "Pillow-10.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5d6c9049c6274c1bb565021367431ad04481ebb54872edecfcd6088d27edd6ed"}, - {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28444cb6ad49726127d6b340217f0627abc8732f1194fd5352dec5e6a0105635"}, - {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de596695a75496deb3b499c8c4f8e60376e0516e1a774e7bc046f0f48cd620ad"}, - {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:2872f2d7846cf39b3dbff64bc1104cc48c76145854256451d33c5faa55c04d1a"}, - {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4ce90f8a24e1c15465048959f1e94309dfef93af272633e8f37361b824532e91"}, - {file = "Pillow-10.0.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ee7810cf7c83fa227ba9125de6084e5e8b08c59038a7b2c9045ef4dde61663b4"}, - {file = "Pillow-10.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b1be1c872b9b5fcc229adeadbeb51422a9633abd847c0ff87dc4ef9bb184ae08"}, - {file = "Pillow-10.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:98533fd7fa764e5f85eebe56c8e4094db912ccbe6fbf3a58778d543cadd0db08"}, - {file = "Pillow-10.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:764d2c0daf9c4d40ad12fbc0abd5da3af7f8aa11daf87e4fa1b834000f4b6b0a"}, - {file = "Pillow-10.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fcb59711009b0168d6ee0bd8fb5eb259c4ab1717b2f538bbf36bacf207ef7a68"}, - {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:697a06bdcedd473b35e50a7e7506b1d8ceb832dc238a336bd6f4f5aa91a4b500"}, - {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f665d1e6474af9f9da5e86c2a3a2d2d6204e04d5af9c06b9d42afa6ebde3f21"}, - {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:2fa6dd2661838c66f1a5473f3b49ab610c98a128fc08afbe81b91a1f0bf8c51d"}, - {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:3a04359f308ebee571a3127fdb1bd01f88ba6f6fb6d087f8dd2e0d9bff43f2a7"}, - {file = "Pillow-10.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:723bd25051454cea9990203405fa6b74e043ea76d4968166dfd2569b0210886a"}, - {file = "Pillow-10.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:71671503e3015da1b50bd18951e2f9daf5b6ffe36d16f1eb2c45711a301521a7"}, - {file = "Pillow-10.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:44e7e4587392953e5e251190a964675f61e4dae88d1e6edbe9f36d6243547ff3"}, - {file = "Pillow-10.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:3855447d98cced8670aaa63683808df905e956f00348732448b5a6df67ee5849"}, - {file = "Pillow-10.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ed2d9c0704f2dc4fa980b99d565c0c9a543fe5101c25b3d60488b8ba80f0cce1"}, - {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5bb289bb835f9fe1a1e9300d011eef4d69661bb9b34d5e196e5e82c4cb09b37"}, - {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a0d3e54ab1df9df51b914b2233cf779a5a10dfd1ce339d0421748232cea9876"}, - {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:2cc6b86ece42a11f16f55fe8903595eff2b25e0358dec635d0a701ac9586588f"}, - {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:ca26ba5767888c84bf5a0c1a32f069e8204ce8c21d00a49c90dabeba00ce0145"}, - {file = "Pillow-10.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f0b4b06da13275bc02adfeb82643c4a6385bd08d26f03068c2796f60d125f6f2"}, - {file = "Pillow-10.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bc2e3069569ea9dbe88d6b8ea38f439a6aad8f6e7a6283a38edf61ddefb3a9bf"}, - {file = "Pillow-10.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:8b451d6ead6e3500b6ce5c7916a43d8d8d25ad74b9102a629baccc0808c54971"}, - {file = "Pillow-10.0.1-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:32bec7423cdf25c9038fef614a853c9d25c07590e1a870ed471f47fb80b244db"}, - {file = "Pillow-10.0.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7cf63d2c6928b51d35dfdbda6f2c1fddbe51a6bc4a9d4ee6ea0e11670dd981e"}, - {file = "Pillow-10.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f6d3d4c905e26354e8f9d82548475c46d8e0889538cb0657aa9c6f0872a37aa4"}, - {file = "Pillow-10.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:847e8d1017c741c735d3cd1883fa7b03ded4f825a6e5fcb9378fd813edee995f"}, - {file = "Pillow-10.0.1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7f771e7219ff04b79e231d099c0a28ed83aa82af91fd5fa9fdb28f5b8d5addaf"}, - {file = "Pillow-10.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:459307cacdd4138edee3875bbe22a2492519e060660eaf378ba3b405d1c66317"}, - {file = "Pillow-10.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b059ac2c4c7a97daafa7dc850b43b2d3667def858a4f112d1aa082e5c3d6cf7d"}, - {file = "Pillow-10.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6caf3cd38449ec3cd8a68b375e0c6fe4b6fd04edb6c9766b55ef84a6e8ddf2d"}, - {file = "Pillow-10.0.1.tar.gz", hash = "sha256:d72967b06be9300fed5cfbc8b5bafceec48bf7cdc7dab66b1d2549035287191d"}, + {file = "Pillow-10.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1ab05f3db77e98f93964697c8efc49c7954b08dd61cff526b7f2531a22410106"}, + {file = "Pillow-10.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6932a7652464746fcb484f7fc3618e6503d2066d853f68a4bd97193a3996e273"}, + {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f63b5a68daedc54c7c3464508d8c12075e56dcfbd42f8c1bf40169061ae666"}, + {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0949b55eb607898e28eaccb525ab104b2d86542a85c74baf3a6dc24002edec2"}, + {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ae88931f93214777c7a3aa0a8f92a683f83ecde27f65a45f95f22d289a69e593"}, + {file = "Pillow-10.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b0eb01ca85b2361b09480784a7931fc648ed8b7836f01fb9241141b968feb1db"}, + {file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d27b5997bdd2eb9fb199982bb7eb6164db0426904020dc38c10203187ae2ff2f"}, + {file = "Pillow-10.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7df5608bc38bd37ef585ae9c38c9cd46d7c81498f086915b0f97255ea60c2818"}, + {file = "Pillow-10.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:41f67248d92a5e0a2076d3517d8d4b1e41a97e2df10eb8f93106c89107f38b57"}, + {file = "Pillow-10.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1fb29c07478e6c06a46b867e43b0bcdb241b44cc52be9bc25ce5944eed4648e7"}, + {file = "Pillow-10.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2cdc65a46e74514ce742c2013cd4a2d12e8553e3a2563c64879f7c7e4d28bce7"}, + {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50d08cd0a2ecd2a8657bd3d82c71efd5a58edb04d9308185d66c3a5a5bed9610"}, + {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062a1610e3bc258bff2328ec43f34244fcec972ee0717200cb1425214fe5b839"}, + {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:61f1a9d247317fa08a308daaa8ee7b3f760ab1809ca2da14ecc88ae4257d6172"}, + {file = "Pillow-10.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a646e48de237d860c36e0db37ecaecaa3619e6f3e9d5319e527ccbc8151df061"}, + {file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:47e5bf85b80abc03be7455c95b6d6e4896a62f6541c1f2ce77a7d2bb832af262"}, + {file = "Pillow-10.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a92386125e9ee90381c3369f57a2a50fa9e6aa8b1cf1d9c4b200d41a7dd8e992"}, + {file = "Pillow-10.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:0f7c276c05a9767e877a0b4c5050c8bee6a6d960d7f0c11ebda6b99746068c2a"}, + {file = "Pillow-10.1.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:a89b8312d51715b510a4fe9fc13686283f376cfd5abca8cd1c65e4c76e21081b"}, + {file = "Pillow-10.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:00f438bb841382b15d7deb9a05cc946ee0f2c352653c7aa659e75e592f6fa17d"}, + {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d929a19f5469b3f4df33a3df2983db070ebb2088a1e145e18facbc28cae5b27"}, + {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a92109192b360634a4489c0c756364c0c3a2992906752165ecb50544c251312"}, + {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:0248f86b3ea061e67817c47ecbe82c23f9dd5d5226200eb9090b3873d3ca32de"}, + {file = "Pillow-10.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9882a7451c680c12f232a422730f986a1fcd808da0fd428f08b671237237d651"}, + {file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1c3ac5423c8c1da5928aa12c6e258921956757d976405e9467c5f39d1d577a4b"}, + {file = "Pillow-10.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:806abdd8249ba3953c33742506fe414880bad78ac25cc9a9b1c6ae97bedd573f"}, + {file = "Pillow-10.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:eaed6977fa73408b7b8a24e8b14e59e1668cfc0f4c40193ea7ced8e210adf996"}, + {file = "Pillow-10.1.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:fe1e26e1ffc38be097f0ba1d0d07fcade2bcfd1d023cda5b29935ae8052bd793"}, + {file = "Pillow-10.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7a7e3daa202beb61821c06d2517428e8e7c1aab08943e92ec9e5755c2fc9ba5e"}, + {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24fadc71218ad2b8ffe437b54876c9382b4a29e030a05a9879f615091f42ffc2"}, + {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa1d323703cfdac2036af05191b969b910d8f115cf53093125e4058f62012c9a"}, + {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:912e3812a1dbbc834da2b32299b124b5ddcb664ed354916fd1ed6f193f0e2d01"}, + {file = "Pillow-10.1.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:7dbaa3c7de82ef37e7708521be41db5565004258ca76945ad74a8e998c30af8d"}, + {file = "Pillow-10.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9d7bc666bd8c5a4225e7ac71f2f9d12466ec555e89092728ea0f5c0c2422ea80"}, + {file = "Pillow-10.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:baada14941c83079bf84c037e2d8b7506ce201e92e3d2fa0d1303507a8538212"}, + {file = "Pillow-10.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:2ef6721c97894a7aa77723740a09547197533146fba8355e86d6d9a4a1056b14"}, + {file = "Pillow-10.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0a026c188be3b443916179f5d04548092e253beb0c3e2ee0a4e2cdad72f66099"}, + {file = "Pillow-10.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:04f6f6149f266a100374ca3cc368b67fb27c4af9f1cc8cb6306d849dcdf12616"}, + {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb40c011447712d2e19cc261c82655f75f32cb724788df315ed992a4d65696bb"}, + {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a8413794b4ad9719346cd9306118450b7b00d9a15846451549314a58ac42219"}, + {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c9aeea7b63edb7884b031a35305629a7593272b54f429a9869a4f63a1bf04c34"}, + {file = "Pillow-10.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b4005fee46ed9be0b8fb42be0c20e79411533d1fd58edabebc0dd24626882cfd"}, + {file = "Pillow-10.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4d0152565c6aa6ebbfb1e5d8624140a440f2b99bf7afaafbdbf6430426497f28"}, + {file = "Pillow-10.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d921bc90b1defa55c9917ca6b6b71430e4286fc9e44c55ead78ca1a9f9eba5f2"}, + {file = "Pillow-10.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cfe96560c6ce2f4c07d6647af2d0f3c54cc33289894ebd88cfbb3bcd5391e256"}, + {file = "Pillow-10.1.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:937bdc5a7f5343d1c97dc98149a0be7eb9704e937fe3dc7140e229ae4fc572a7"}, + {file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c25762197144e211efb5f4e8ad656f36c8d214d390585d1d21281f46d556ba"}, + {file = "Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:afc8eef765d948543a4775f00b7b8c079b3321d6b675dde0d02afa2ee23000b4"}, + {file = "Pillow-10.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:883f216eac8712b83a63f41b76ddfb7b2afab1b74abbb413c5df6680f071a6b9"}, + {file = "Pillow-10.1.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b920e4d028f6442bea9a75b7491c063f0b9a3972520731ed26c83e254302eb1e"}, + {file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c41d960babf951e01a49c9746f92c5a7e0d939d1652d7ba30f6b3090f27e412"}, + {file = "Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1fafabe50a6977ac70dfe829b2d5735fd54e190ab55259ec8aea4aaea412fa0b"}, + {file = "Pillow-10.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3b834f4b16173e5b92ab6566f0473bfb09f939ba14b23b8da1f54fa63e4b623f"}, + {file = "Pillow-10.1.0.tar.gz", hash = "sha256:e6bf8de6c36ed96c86ea3b6e1d5273c53f46ef518a062464cd7ef5dd2cf92e38"}, ] [package.extras] @@ -2290,13 +2381,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "3.3.3" +version = "3.5.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.8" files = [ - {file = "pre_commit-3.3.3-py2.py3-none-any.whl", hash = "sha256:10badb65d6a38caff29703362271d7dca483d01da88f9d7e05d0b97171c136cb"}, - {file = "pre_commit-3.3.3.tar.gz", hash = "sha256:a2256f489cd913d575c145132ae196fe335da32d91a8294b7afe6622335dd023"}, + {file = "pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660"}, + {file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"}, ] [package.dependencies] @@ -2387,13 +2478,13 @@ tests = ["pytest"] [[package]] name = "pycodestyle" -version = "2.10.0" +version = "2.11.1" description = "Python style guide checker" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "pycodestyle-2.10.0-py2.py3-none-any.whl", hash = "sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610"}, - {file = "pycodestyle-2.10.0.tar.gz", hash = "sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053"}, + {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, + {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, ] [[package]] @@ -2409,24 +2500,24 @@ files = [ [[package]] name = "pyflakes" -version = "3.0.1" +version = "3.1.0" description = "passive checker of Python programs" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "pyflakes-3.0.1-py2.py3-none-any.whl", hash = "sha256:ec55bf7fe21fff7f1ad2f7da62363d749e2a470500eab1b555334b67aa1ef8cf"}, - {file = "pyflakes-3.0.1.tar.gz", hash = "sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd"}, + {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, + {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, ] [[package]] name = "pygments" -version = "2.15.1" +version = "2.16.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.7" files = [ - {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"}, - {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"}, + {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, + {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, ] [package.extras] @@ -2434,19 +2525,22 @@ plugins = ["importlib-metadata"] [[package]] name = "pymdown-extensions" -version = "10.1" +version = "10.3.1" description = "Extension pack for Python Markdown." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pymdown_extensions-10.1-py3-none-any.whl", hash = "sha256:ef25dbbae530e8f67575d222b75ff0649b1e841e22c2ae9a20bad9472c2207dc"}, - {file = "pymdown_extensions-10.1.tar.gz", hash = "sha256:508009b211373058debb8247e168de4cbcb91b1bff7b5e961b2c3e864e00b195"}, + {file = "pymdown_extensions-10.3.1-py3-none-any.whl", hash = "sha256:8cba67beb2a1318cdaf742d09dff7c0fc4cafcc290147ade0f8fb7b71522711a"}, + {file = "pymdown_extensions-10.3.1.tar.gz", hash = "sha256:f6c79941498a458852853872e379e7bab63888361ba20992fc8b4f8a9b61735e"}, ] [package.dependencies] markdown = ">=3.2" pyyaml = "*" +[package.extras] +extra = ["pygments (>=2.12)"] + [[package]] name = "pyparsing" version = "3.0.9" @@ -2477,13 +2571,13 @@ tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} [[package]] name = "pytest" -version = "7.4.0" +version = "7.4.3" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.0-py3-none-any.whl", hash = "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32"}, - {file = "pytest-7.4.0.tar.gz", hash = "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"}, + {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, + {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, ] [package.dependencies] @@ -2522,6 +2616,17 @@ files = [ {file = "python_json_logger-2.0.7-py3-none-any.whl", hash = "sha256:f380b826a991ebbe3de4d897aeec42760035ac760345e57b812938dc8b35e2bd"}, ] +[[package]] +name = "pytz" +version = "2023.3.post1" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, + {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, +] + [[package]] name = "pywin32" version = "306" @@ -3064,28 +3169,28 @@ files = [ [[package]] name = "ruff" -version = "0.0.280" -description = "An extremely fast Python linter, written in Rust." +version = "0.1.6" +description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.0.280-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:48ed5aca381050a4e2f6d232db912d2e4e98e61648b513c350990c351125aaec"}, - {file = "ruff-0.0.280-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:ef6ee3e429fd29d6a5ceed295809e376e6ece5b0f13c7e703efaf3d3bcb30b96"}, - {file = "ruff-0.0.280-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d878370f7e9463ac40c253724229314ff6ebe4508cdb96cb536e1af4d5a9cd4f"}, - {file = "ruff-0.0.280-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:83e8f372fa5627eeda5b83b5a9632d2f9c88fc6d78cead7e2a1f6fb05728d137"}, - {file = "ruff-0.0.280-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7008fc6ca1df18b21fa98bdcfc711dad5f94d0fc3c11791f65e460c48ef27c82"}, - {file = "ruff-0.0.280-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:fe7118c1eae3fda17ceb409629c7f3b5a22dffa7caf1f6796776936dca1fe653"}, - {file = "ruff-0.0.280-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:37359cd67d2af8e09110a546507c302cbea11c66a52d2a9b6d841d465f9962d4"}, - {file = "ruff-0.0.280-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd58af46b0221efb95966f1f0f7576df711cb53e50d2fdb0e83c2f33360116a4"}, - {file = "ruff-0.0.280-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e7c15828d09f90e97bea8feefcd2907e8c8ce3a1f959c99f9b4b3469679f33c"}, - {file = "ruff-0.0.280-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2dae8f2d9c44c5c49af01733c2f7956f808db682a4193180dedb29dd718d7bbe"}, - {file = "ruff-0.0.280-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5f972567163a20fb8c2d6afc60c2ea5ef8b68d69505760a8bd0377de8984b4f6"}, - {file = "ruff-0.0.280-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8ffa7347ad11643f29de100977c055e47c988cd6d9f5f5ff83027600b11b9189"}, - {file = "ruff-0.0.280-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7a37dab70114671d273f203268f6c3366c035fe0c8056614069e90a65e614bfc"}, - {file = "ruff-0.0.280-py3-none-win32.whl", hash = "sha256:7784e3606352fcfb193f3cd22b2e2117c444cb879ef6609ec69deabd662b0763"}, - {file = "ruff-0.0.280-py3-none-win_amd64.whl", hash = "sha256:4a7d52457b5dfcd3ab24b0b38eefaead8e2dca62b4fbf10de4cd0938cf20ce30"}, - {file = "ruff-0.0.280-py3-none-win_arm64.whl", hash = "sha256:b7de5b8689575918e130e4384ed9f539ce91d067c0a332aedef6ca7188adac2d"}, - {file = "ruff-0.0.280.tar.gz", hash = "sha256:581c43e4ac5e5a7117ad7da2120d960a4a99e68ec4021ec3cd47fe1cf78f8380"}, + {file = "ruff-0.1.6-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:88b8cdf6abf98130991cbc9f6438f35f6e8d41a02622cc5ee130a02a0ed28703"}, + {file = "ruff-0.1.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5c549ed437680b6105a1299d2cd30e4964211606eeb48a0ff7a93ef70b902248"}, + {file = "ruff-0.1.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cf5f701062e294f2167e66d11b092bba7af6a057668ed618a9253e1e90cfd76"}, + {file = "ruff-0.1.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:05991ee20d4ac4bb78385360c684e4b417edd971030ab12a4fbd075ff535050e"}, + {file = "ruff-0.1.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87455a0c1f739b3c069e2f4c43b66479a54dea0276dd5d4d67b091265f6fd1dc"}, + {file = "ruff-0.1.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:683aa5bdda5a48cb8266fcde8eea2a6af4e5700a392c56ea5fb5f0d4bfdc0240"}, + {file = "ruff-0.1.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:137852105586dcbf80c1717facb6781555c4e99f520c9c827bd414fac67ddfb6"}, + {file = "ruff-0.1.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd98138a98d48a1c36c394fd6b84cd943ac92a08278aa8ac8c0fdefcf7138f35"}, + {file = "ruff-0.1.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a0cd909d25f227ac5c36d4e7e681577275fb74ba3b11d288aff7ec47e3ae745"}, + {file = "ruff-0.1.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8fd1c62a47aa88a02707b5dd20c5ff20d035d634aa74826b42a1da77861b5ff"}, + {file = "ruff-0.1.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fd89b45d374935829134a082617954120d7a1470a9f0ec0e7f3ead983edc48cc"}, + {file = "ruff-0.1.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:491262006e92f825b145cd1e52948073c56560243b55fb3b4ecb142f6f0e9543"}, + {file = "ruff-0.1.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ea284789861b8b5ca9d5443591a92a397ac183d4351882ab52f6296b4fdd5462"}, + {file = "ruff-0.1.6-py3-none-win32.whl", hash = "sha256:1610e14750826dfc207ccbcdd7331b6bd285607d4181df9c1c6ae26646d6848a"}, + {file = "ruff-0.1.6-py3-none-win_amd64.whl", hash = "sha256:4558b3e178145491e9bc3b2ee3c4b42f19d19384eaa5c59d10acf6e8f8b57e33"}, + {file = "ruff-0.1.6-py3-none-win_arm64.whl", hash = "sha256:03910e81df0d8db0e30050725a5802441c2022ea3ae4fe0609b76081731accbc"}, + {file = "ruff-0.1.6.tar.gz", hash = "sha256:1b09f29b16c6ead5ea6b097ef2764b42372aebe363722f1605ecbcd2b9207184"}, ] [[package]] @@ -3173,6 +3278,28 @@ docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-g testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +[[package]] +name = "setuptools-scm" +version = "8.0.4" +description = "the blessed package to manage your versions by scm tags" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-scm-8.0.4.tar.gz", hash = "sha256:b5f43ff6800669595193fd09891564ee9d1d7dcb196cab4b2506d53a2e1c95c7"}, + {file = "setuptools_scm-8.0.4-py3-none-any.whl", hash = "sha256:b47844cd2a84b83b3187a5782c71128c28b4c94cad8bfb871da2784a5cb54c4f"}, +] + +[package.dependencies] +packaging = ">=20" +setuptools = "*" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} +typing-extensions = "*" + +[package.extras] +docs = ["entangled-cli[rich]", "mkdocs", "mkdocs-entangled-plugin", "mkdocs-material", "mkdocstrings[python]", "pygments"] +rich = ["rich"] +test = ["build", "pytest", "rich", "wheel"] + [[package]] name = "six" version = "1.16.0" @@ -3294,6 +3421,26 @@ files = [ {file = "tornado-6.3.3.tar.gz", hash = "sha256:e7d8db41c0181c80d76c982aacc442c0783a2c54d6400fe028954201a2e032fe"}, ] +[[package]] +name = "tqdm" +version = "4.66.1" +description = "Fast, Extensible Progress Meter" +optional = true +python-versions = ">=3.7" +files = [ + {file = "tqdm-4.66.1-py3-none-any.whl", hash = "sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386"}, + {file = "tqdm-4.66.1.tar.gz", hash = "sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + [[package]] name = "traitlets" version = "5.9.0" @@ -3358,13 +3505,13 @@ dev = ["flake8", "flake8-annotations", "flake8-bandit", "flake8-bugbear", "flake [[package]] name = "urllib3" -version = "2.0.6" +version = "2.0.7" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.7" files = [ - {file = "urllib3-2.0.6-py3-none-any.whl", hash = "sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2"}, - {file = "urllib3-2.0.6.tar.gz", hash = "sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564"}, + {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, + {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, ] [package.extras] @@ -3487,17 +3634,17 @@ test = ["websockets"] [[package]] name = "wheel" -version = "0.40.0" +version = "0.41.3" description = "A built-package format for Python" optional = false python-versions = ">=3.7" files = [ - {file = "wheel-0.40.0-py3-none-any.whl", hash = "sha256:d236b20e7cb522daf2390fa84c55eea81c5c30190f90f29ae2ca1ad8355bf247"}, - {file = "wheel-0.40.0.tar.gz", hash = "sha256:cd1196f3faee2b31968d626e1731c94f99cbdb67cf5a46e4f5656cbee7738873"}, + {file = "wheel-0.41.3-py3-none-any.whl", hash = "sha256:488609bc63a29322326e05560731bf7bfea8e48ad646e1f5e40d366607de0942"}, + {file = "wheel-0.41.3.tar.gz", hash = "sha256:4d4987ce51a49370ea65c0bfd2234e8ce80a12780820d9dc462597a6e60d0841"}, ] [package.extras] -test = ["pytest (>=6.0.0)"] +test = ["pytest (>=6.0.0)", "setuptools (>=65)"] [[package]] name = "zipp" @@ -3515,9 +3662,10 @@ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.link testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] [extras] +assets = ["requests", "tqdm"] desktop = ["opencv-python"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<3.12.0" -content-hash = "beb692d89611ce85f8b2992f4e6da71fd14fa2ca75742ddae1a1c7b77df134f7" +content-hash = "abbd8c3b0bbed2349aa2ba8f4a822ab44d1092ea8b60118dc83fe11737e4ca12" diff --git a/pyproject.toml b/pyproject.toml index d78a50b7..c15e65b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "supervision" -version = "0.16.0rc2" +version = "0.17.0rc4" description = "A set of easy-to-use utils that will come in handy in any Computer Vision project" authors = ["Piotr Skalski "] maintainers = ["Piotr Skalski "] @@ -43,19 +43,21 @@ pillow = ">=9.4,<11.0" opencv-python = { version = "^4.8.0.74", optional = true } opencv-python-headless = "^4.8.0.74" scipy = "^1.9.0" +requests = { version = "^2.31.0", optional = true } +tqdm = { version = "^4.66.1", optional = true } [tool.poetry.extras] desktop = ["opencv-python"] - +assets = ["requests","tqdm"] [tool.poetry.group.dev.dependencies] twine = "^4.0.2" pytest = "^7.2.2" -wheel = "^0.40.0" -notebook = "^6.5.3" -build = "^0.10.0" -ruff = "^0.0.280" +wheel = ">=0.40,<0.42" +notebook = ">=6.5.3,<8.0.0" +build = ">=0.10,<1.1" +ruff = ">=0.0.280,<0.1.7" isort = "^5.12.0" black = "^23.7.0" mypy = "^1.4.1" @@ -65,7 +67,7 @@ flake8 = { version = "*", python = ">=3.8.1,<3.12.0" } [tool.poetry.group.docs.dependencies] mkdocs-material = "^9.1.4" -mkdocstrings = {extras = ["python"], version = "^0.20.0"} +mkdocstrings = {extras = ["python"], version = ">=0.20,<0.25"} [tool.flake8] exclude = ".venv" @@ -79,6 +81,7 @@ extend-ignore = """ """ per-file-ignores = """ __init__.py: F401 + supervision/assets/list.py: E501 """ [tool.isort] @@ -149,6 +152,7 @@ exclude = [ "yarn-error.log", "yarn.lock", "docs", + "supervision/assets/list.py" ] # Same as Black. @@ -167,6 +171,8 @@ convention = "google" [tool.ruff.per-file-ignores] "__init__.py" = ["E402","F401"] +"supervision/assets/list.py" = ["E501"] + [tool.ruff.pylint] max-args = 20 diff --git a/supervision/__init__.py b/supervision/__init__.py index ea241e2e..3e59a0b0 100644 --- a/supervision/__init__.py +++ b/supervision/__init__.py @@ -13,10 +13,13 @@ from supervision.annotators.core import ( BoxMaskAnnotator, CircleAnnotator, ClassificationAnnotator, + DotAnnotator, EllipseAnnotator, HaloAnnotator, + HeatMapAnnotator, LabelAnnotator, MaskAnnotator, + PolygonAnnotator, TraceAnnotator, ) from supervision.annotators.utils import ColorLookup @@ -33,6 +36,7 @@ from supervision.detection.tools.inference_slicer import InferenceSlicer from supervision.detection.tools.polygon_zone import PolygonZone, PolygonZoneAnnotator from supervision.detection.utils import ( box_iou_batch, + calculate_masks_centroids, filter_polygons_by_area, mask_to_polygons, mask_to_xyxy, @@ -41,7 +45,14 @@ from supervision.detection.utils import ( polygon_to_xyxy, ) from supervision.draw.color import Color, ColorPalette -from supervision.draw.utils import draw_filled_rectangle, draw_polygon, draw_text +from supervision.draw.utils import ( + draw_filled_rectangle, + draw_image, + draw_line, + draw_polygon, + draw_rectangle, + draw_text, +) from supervision.geometry.core import Point, Position, Rect from supervision.geometry.utils import get_polygon_center from supervision.metrics.detection import ConfusionMatrix, MeanAveragePrecision diff --git a/supervision/annotators/core.py b/supervision/annotators/core.py index 09e5c26d..427c645c 100644 --- a/supervision/annotators/core.py +++ b/supervision/annotators/core.py @@ -8,7 +8,9 @@ from supervision.annotators.base import BaseAnnotator from supervision.annotators.utils import ColorLookup, Trace, resolve_color from supervision.classification.core import Classifications from supervision.detection.core import Detections +from supervision.detection.utils import clip_boxes, mask_to_polygons from supervision.draw.color import Color, ColorPalette +from supervision.draw.utils import draw_polygon from supervision.geometry.core import Position @@ -29,7 +31,7 @@ class BoundingBoxAnnotator(BaseAnnotator): annotating detections. thickness (int): Thickness of the bounding box lines. color_lookup (str): Strategy for mapping colors to annotations. - Options are `INDEX`, `CLASS`, `TRACE`. + Options are `INDEX`, `CLASS`, `TRACK`. """ self.color: Union[Color, ColorPalette] = color self.thickness: int = thickness @@ -51,7 +53,7 @@ class BoundingBoxAnnotator(BaseAnnotator): Allows to override the default color mapping strategy. Returns: - np.ndarray: The annotated image. + The annotated image. Example: ```python @@ -93,6 +95,10 @@ class BoundingBoxAnnotator(BaseAnnotator): class MaskAnnotator(BaseAnnotator): """ A class for drawing masks on an image using provided detections. + + !!! warning + + This annotator utilizes the `sv.Detections.mask`. """ def __init__( @@ -107,7 +113,7 @@ class MaskAnnotator(BaseAnnotator): annotating detections. opacity (float): Opacity of the overlay mask. Must be between `0` and `1`. color_lookup (str): Strategy for mapping colors to annotations. - Options are `INDEX`, `CLASS`, `TRACE`. + Options are `INDEX`, `CLASS`, `TRACK`. """ self.color: Union[Color, ColorPalette] = color self.opacity = opacity @@ -129,7 +135,7 @@ class MaskAnnotator(BaseAnnotator): Allows to override the default color mapping strategy. Returns: - np.ndarray: The annotated image. + The annotated image. Example: ```python @@ -151,6 +157,8 @@ class MaskAnnotator(BaseAnnotator): if detections.mask is None: return scene + colored_mask = np.array(scene, copy=True, dtype=np.uint8) + for detection_idx in np.flip(np.argsort(detections.area)): color = resolve_color( color=self.color, @@ -161,11 +169,94 @@ class MaskAnnotator(BaseAnnotator): else custom_color_lookup, ) mask = detections.mask[detection_idx] - colored_mask = np.zeros_like(scene, dtype=np.uint8) - colored_mask[:] = color.as_bgr() - scene[mask] = cv2.addWeighted( - colored_mask, self.opacity, scene, 1 - self.opacity, 0 - )[mask] + colored_mask[mask] = color.as_bgr() + + scene = cv2.addWeighted(colored_mask, self.opacity, scene, 1 - self.opacity, 0) + return scene.astype(np.uint8) + + +class PolygonAnnotator(BaseAnnotator): + """ + A class for drawing polygons on an image using provided detections. + + !!! warning + + This annotator utilizes the `sv.Detections.mask`. + """ + + def __init__( + self, + color: Union[Color, ColorPalette] = ColorPalette.default(), + thickness: int = 2, + color_lookup: ColorLookup = ColorLookup.CLASS, + ): + """ + Args: + color (Union[Color, ColorPalette]): The color or color palette to use for + annotating detections. + thickness (int): Thickness of the polygon lines. + color_lookup (str): Strategy for mapping colors to annotations. + Options are `INDEX`, `CLASS`, `TRACK`. + """ + self.color: Union[Color, ColorPalette] = color + self.thickness: int = thickness + self.color_lookup: ColorLookup = color_lookup + + def annotate( + self, + scene: np.ndarray, + detections: Detections, + custom_color_lookup: Optional[np.ndarray] = None, + ) -> np.ndarray: + """ + Annotates the given scene with polygons based on the provided detections. + + Args: + scene (np.ndarray): The image where polygons will be drawn. + detections (Detections): Object detections to annotate. + custom_color_lookup (Optional[np.ndarray]): Custom color lookup array. + Allows to override the default color mapping strategy. + + Returns: + The annotated image. + + Example: + ```python + >>> import supervision as sv + + >>> image = ... + >>> detections = sv.Detections(...) + + >>> polygon_annotator = sv.PolygonAnnotator() + >>> annotated_frame = polygon_annotator.annotate( + ... scene=image.copy(), + ... detections=detections + ... ) + ``` + + ![polygon-annotator-example](https://media.roboflow.com/ + supervision-annotator-examples/polygon-annotator-example-purple.png) + """ + if detections.mask is None: + return scene + + for detection_idx in range(len(detections)): + mask = detections.mask[detection_idx] + color = resolve_color( + color=self.color, + detections=detections, + detection_idx=detection_idx, + color_lookup=self.color_lookup + if custom_color_lookup is None + else custom_color_lookup, + ) + for polygon in mask_to_polygons(mask=mask): + scene = draw_polygon( + scene=scene, + polygon=polygon, + color=color, + thickness=self.thickness, + ) return scene @@ -187,7 +278,7 @@ class BoxMaskAnnotator(BaseAnnotator): annotating detections. opacity (float): Opacity of the overlay mask. Must be between `0` and `1`. color_lookup (str): Strategy for mapping colors to annotations. - Options are `INDEX`, `CLASS`, `TRACE`. + Options are `INDEX`, `CLASS`, `TRACK`. """ self.color: Union[Color, ColorPalette] = color self.color_lookup: ColorLookup = color_lookup @@ -209,7 +300,7 @@ class BoxMaskAnnotator(BaseAnnotator): Allows to override the default color mapping strategy. Returns: - np.ndarray: The annotated image. + The annotated image. Example: ```python @@ -255,6 +346,10 @@ class BoxMaskAnnotator(BaseAnnotator): class HaloAnnotator(BaseAnnotator): """ A class for drawing Halos on an image using provided detections. + + !!! warning + + This annotator utilizes the `sv.Detections.mask`. """ def __init__( @@ -272,7 +367,7 @@ class HaloAnnotator(BaseAnnotator): kernel_size (int): The size of the average pooling kernel used for creating the halo. color_lookup (str): Strategy for mapping colors to annotations. - Options are `INDEX`, `CLASS`, `TRACE`. + Options are `INDEX`, `CLASS`, `TRACK`. """ self.color: Union[Color, ColorPalette] = color self.opacity = opacity @@ -295,7 +390,7 @@ class HaloAnnotator(BaseAnnotator): Allows to override the default color mapping strategy. Returns: - np.ndarray: The annotated image. + The annotated image. Example: ```python @@ -365,7 +460,7 @@ class EllipseAnnotator(BaseAnnotator): start_angle (int): Starting angle of the ellipse. end_angle (int): Ending angle of the ellipse. color_lookup (str): Strategy for mapping colors to annotations. - Options are `INDEX`, `CLASS`, `TRACE`. + Options are `INDEX`, `CLASS`, `TRACK`. """ self.color: Union[Color, ColorPalette] = color self.thickness: int = thickness @@ -389,7 +484,7 @@ class EllipseAnnotator(BaseAnnotator): Allows to override the default color mapping strategy. Returns: - np.ndarray: The annotated image. + The annotated image. Example: ```python @@ -453,7 +548,7 @@ class BoxCornerAnnotator(BaseAnnotator): thickness (int): Thickness of the corner lines. corner_length (int): Length of each corner line. color_lookup (str): Strategy for mapping colors to annotations. - Options are `INDEX`, `CLASS`, `TRACE`. + Options are `INDEX`, `CLASS`, `TRACK`. """ self.color: Union[Color, ColorPalette] = color self.thickness: int = thickness @@ -476,7 +571,7 @@ class BoxCornerAnnotator(BaseAnnotator): Allows to override the default color mapping strategy. Returns: - np.ndarray: The annotated image. + The annotated image. Example: ```python @@ -537,7 +632,7 @@ class CircleAnnotator(BaseAnnotator): annotating detections. thickness (int): Thickness of the circle line. color_lookup (str): Strategy for mapping colors to annotations. - Options are `INDEX`, `CLASS`, `TRACE`. + Options are `INDEX`, `CLASS`, `TRACK`. """ self.color: Union[Color, ColorPalette] = color @@ -560,7 +655,7 @@ class CircleAnnotator(BaseAnnotator): Allows to override the default color mapping strategy. Returns: - np.ndarray: The annotated image. + The annotated image. Example: ```python @@ -603,6 +698,83 @@ class CircleAnnotator(BaseAnnotator): return scene +class DotAnnotator(BaseAnnotator): + """ + A class for drawing dots on an image at specific coordinates based on provided + detections. + """ + + def __init__( + self, + color: Union[Color, ColorPalette] = ColorPalette.default(), + radius: int = 4, + position: Position = Position.CENTER, + color_lookup: ColorLookup = ColorLookup.CLASS, + ): + """ + Args: + color (Union[Color, ColorPalette]): The color or color palette to use for + annotating detections. + radius (int): Radius of the drawn dots. + position (Position): The anchor position for placing the dot. + color_lookup (ColorLookup): Strategy for mapping colors to annotations. + Options are `INDEX`, `CLASS`, `TRACK`. + """ + self.color: Union[Color, ColorPalette] = color + self.radius: int = radius + self.position: Position = position + self.color_lookup: ColorLookup = color_lookup + + def annotate( + self, + scene: np.ndarray, + detections: Detections, + custom_color_lookup: Optional[np.ndarray] = None, + ) -> np.ndarray: + """ + Annotates the given scene with dots based on the provided detections. + + Args: + scene (np.ndarray): The image where dots will be drawn. + detections (Detections): Object detections to annotate. + custom_color_lookup (Optional[np.ndarray]): Custom color lookup array. + Allows to override the default color mapping strategy. + + Returns: + The annotated image. + + Example: + ```python + >>> import supervision as sv + + >>> image = ... + >>> detections = sv.Detections(...) + + >>> dot_annotator = sv.DotAnnotator() + >>> annotated_frame = dot_annotator.annotate( + ... scene=image.copy(), + ... detections=detections + ... ) + ``` + + ![dot-annotator-example](https://media.roboflow.com/ + supervision-annotator-examples/dot-annotator-example-purple.png) + """ + xy = detections.get_anchors_coordinates(anchor=self.position) + for detection_idx in range(len(detections)): + color = resolve_color( + color=self.color, + detections=detections, + detection_idx=detection_idx, + color_lookup=self.color_lookup + if custom_color_lookup is None + else custom_color_lookup, + ) + center = (int(xy[detection_idx, 0]), int(xy[detection_idx, 1])) + cv2.circle(scene, center, self.radius, color.as_bgr(), -1) + return scene + + class LabelAnnotator: """ A class for annotating labels on an image using provided detections. @@ -629,56 +801,53 @@ class LabelAnnotator: text_position (Position): Position of the text relative to the detection. Possible values are defined in the `Position` enum. color_lookup (str): Strategy for mapping colors to annotations. - Options are `INDEX`, `CLASS`, `TRACE`. + Options are `INDEX`, `CLASS`, `TRACK`. """ self.color: Union[Color, ColorPalette] = color self.text_color: Color = text_color self.text_scale: float = text_scale self.text_thickness: int = text_thickness self.text_padding: int = text_padding - self.text_position: Position = text_position + self.text_anchor: Position = text_position self.color_lookup: ColorLookup = color_lookup @staticmethod def resolve_text_background_xyxy( - detection_xyxy: Tuple[int, int, int, int], + center_coordinates: Tuple[int, int], text_wh: Tuple[int, int], - text_padding: int, position: Position, ) -> Tuple[int, int, int, int]: - padded_text_wh = (text_wh[0] + 2 * text_padding, text_wh[1] + 2 * text_padding) - x1, y1, x2, y2 = detection_xyxy - center_x = (x1 + x2) // 2 - center_y = (y1 + y2) // 2 + center_x, center_y = center_coordinates + text_w, text_h = text_wh if position == Position.TOP_LEFT: - return x1, y1 - padded_text_wh[1], x1 + padded_text_wh[0], y1 + return center_x, center_y - text_h, center_x + text_w, center_y elif position == Position.TOP_RIGHT: - return x2 - padded_text_wh[0], y1 - padded_text_wh[1], x2, y1 + return center_x - text_w, center_y - text_h, center_x, center_y elif position == Position.TOP_CENTER: return ( - center_x - padded_text_wh[0] // 2, - y1 - padded_text_wh[1], - center_x + padded_text_wh[0] // 2, - y1, + center_x - text_w // 2, + center_y - text_h, + center_x + text_w // 2, + center_y, ) - elif position == Position.CENTER: + elif position == Position.CENTER or position == Position.CENTER_OF_MASS: return ( - center_x - padded_text_wh[0] // 2, - center_y - padded_text_wh[1] // 2, - center_x + padded_text_wh[0] // 2, - center_y + padded_text_wh[1] // 2, + center_x - text_w // 2, + center_y - text_h // 2, + center_x + text_w // 2, + center_y + text_h // 2, ) elif position == Position.BOTTOM_LEFT: - return x1, y2, x1 + padded_text_wh[0], y2 + padded_text_wh[1] + return center_x, center_y, center_x + text_w, center_y + text_h elif position == Position.BOTTOM_RIGHT: - return x2 - padded_text_wh[0], y2, x2, y2 + padded_text_wh[1] + return center_x - text_w, center_y, center_x, center_y + text_h elif position == Position.BOTTOM_CENTER: return ( - center_x - padded_text_wh[0] // 2, - y2, - center_x + padded_text_wh[0] // 2, - y2 + padded_text_wh[1], + center_x - text_w // 2, + center_y, + center_x + text_w // 2, + center_y + text_h, ) def annotate( @@ -699,7 +868,7 @@ class LabelAnnotator: Allows to override the default color mapping strategy. Returns: - np.ndarray: The annotated image. + The annotated image. Example: ```python @@ -719,8 +888,10 @@ class LabelAnnotator: supervision-annotator-examples/label-annotator-example-purple.png) """ font = cv2.FONT_HERSHEY_SIMPLEX - for detection_idx in range(len(detections)): - detection_xyxy = detections.xyxy[detection_idx].astype(int) + anchors_coordinates = detections.get_anchors_coordinates( + anchor=self.text_anchor + ).astype(int) + for detection_idx, center_coordinates in enumerate(anchors_coordinates): color = resolve_color( color=self.color, detections=detections, @@ -734,22 +905,22 @@ class LabelAnnotator: if (labels is None or len(detections) != len(labels)) else labels[detection_idx] ) - text_wh = cv2.getTextSize( + text_w, text_h = cv2.getTextSize( text=text, fontFace=font, fontScale=self.text_scale, thickness=self.text_thickness, )[0] - + text_w_padded = text_w + 2 * self.text_padding + text_h_padded = text_h + 2 * self.text_padding text_background_xyxy = self.resolve_text_background_xyxy( - detection_xyxy=detection_xyxy, - text_wh=text_wh, - text_padding=self.text_padding, - position=self.text_position, + center_coordinates=tuple(center_coordinates), + text_wh=(text_w_padded, text_h_padded), + position=self.text_anchor, ) text_x = text_background_xyxy[0] + self.text_padding - text_y = text_background_xyxy[1] + self.text_padding + text_wh[1] + text_y = text_background_xyxy[1] + self.text_padding + text_h cv2.rectangle( img=scene, @@ -796,7 +967,7 @@ class BlurAnnotator(BaseAnnotator): detections (Detections): Object detections to annotate. Returns: - np.ndarray: The annotated image. + The annotated image. Example: ```python @@ -806,7 +977,7 @@ class BlurAnnotator(BaseAnnotator): >>> detections = sv.Detections(...) >>> blur_annotator = sv.BlurAnnotator() - >>> annotated_frame = blur_annotator.annotate( + >>> annotated_frame = circle_annotator.annotate( ... scene=image.copy(), ... detections=detections ... ) @@ -815,10 +986,13 @@ class BlurAnnotator(BaseAnnotator): ![blur-annotator-example](https://media.roboflow.com/ supervision-annotator-examples/blur-annotator-example-purple.png) """ - for detection_idx in range(len(detections)): - x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int) - roi = scene[y1:y2, x1:x2] + image_height, image_width = scene.shape[:2] + clipped_xyxy = clip_boxes( + xyxy=detections.xyxy, resolution_wh=(image_width, image_height) + ).astype(int) + for x1, y1, x2, y2 in clipped_xyxy: + roi = scene[y1:y2, x1:x2] roi = cv2.blur(roi, (self.kernel_size, self.kernel_size)) scene[y1:y2, x1:x2] = roi @@ -831,7 +1005,7 @@ class TraceAnnotator: !!! warning - This annotator utilizes the `tracker_id`. Read + This annotator utilizes the `sv.Detections.tracker_id`. Read [here](https://supervision.roboflow.com/trackers/) to learn how to plug tracking into your inference pipeline. """ @@ -839,7 +1013,7 @@ class TraceAnnotator: def __init__( self, color: Union[Color, ColorPalette] = ColorPalette.default(), - position: Optional[Position] = Position.CENTER, + position: Position = Position.CENTER, trace_length: int = 30, thickness: int = 2, color_lookup: ColorLookup = ColorLookup.CLASS, @@ -848,13 +1022,13 @@ class TraceAnnotator: Args: color (Union[Color, ColorPalette]): The color to draw the trace, can be a single color or a color palette. - position (Optional[Position]): The position of the trace. + position (Position): The position of the trace. Defaults to `CENTER`. trace_length (int): The maximum length of the trace in terms of historical points. Defaults to `30`. thickness (int): The thickness of the trace lines. Defaults to `2`. color_lookup (str): Strategy for mapping colors to annotations. - Options are `INDEX`, `CLASS`, `TRACE`. + Options are `INDEX`, `CLASS`, `TRACK`. """ self.color: Union[Color, ColorPalette] = color self.position = position @@ -879,20 +1053,30 @@ class TraceAnnotator: Allows to override the default color mapping strategy. Returns: - np.ndarray: The image with the trace paths drawn on it. + The annotated image. Example: ```python >>> import supervision as sv + >>> from ultralytics import YOLO - >>> image = ... - >>> detections = sv.Detections(...) + >>> model = YOLO('yolov8x.pt') >>> trace_annotator = sv.TraceAnnotator() - >>> annotated_frame = trace_annotator.annotate( - ... scene=image.copy(), - ... detections=detections - ... ) + + >>> video_info = sv.VideoInfo.from_video_path(video_path='...') + >>> frames_generator = sv.get_video_frames_generator(source_path='...') + >>> tracker = sv.ByteTrack() + + >>> with sv.VideoSink(target_path='...', video_info=video_info) as sink: + ... for frame in frames_generator: + ... result = model(frame)[0] + ... detections = sv.Detections.from_ultralytics(result) + ... detections = tracker.update_with_detections(detections) + ... annotated_frame = trace_annotator.annotate( + ... scene=frame.copy(), + ... detections=detections) + ... sink.write_frame(frame=annotated_frame) ``` ![trace-annotator-example](https://media.roboflow.com/ @@ -922,6 +1106,100 @@ class TraceAnnotator: return scene +class HeatMapAnnotator: + """ + A class for drawing heatmaps on an image based on provided detections. + Heat accumulates over time and is drawn as a semi-transparent overlay + of blurred circles. + """ + + def __init__( + self, + position: Position = Position.BOTTOM_CENTER, + opacity: float = 0.2, + radius: int = 40, + kernel_size: int = 25, + top_hue: int = 0, + low_hue: int = 125, + ): + """ + Args: + position (Position): The position of the heatmap. Defaults to + `BOTTOM_CENTER`. + opacity (float): Opacity of the overlay mask, between 0 and 1. + radius (int): Radius of the heat circle. + kernel_size (int): Kernel size for blurring the heatmap. + top_hue (int): Hue at the top of the heatmap. Defaults to 0 (red). + low_hue (int): Hue at the bottom of the heatmap. Defaults to 125 (blue). + """ + self.position = position + self.opacity = opacity + self.radius = radius + self.kernel_size = kernel_size + self.heat_mask = None + self.top_hue = top_hue + self.low_hue = low_hue + + def annotate(self, scene: np.ndarray, detections: Detections) -> np.ndarray: + """ + Annotates the scene with a heatmap based on the provided detections. + + Args: + scene (np.ndarray): The image where the heatmap will be drawn. + detections (Detections): Object detections to annotate. + + Returns: + Annotated image. + + Example: + ```python + >>> import supervision as sv + >>> from ultralytics import YOLO + + >>> model = YOLO('yolov8x.pt') + + >>> heat_map_annotator = sv.HeatMapAnnotator() + + >>> video_info = sv.VideoInfo.from_video_path(video_path='...') + >>> frames_generator = get_video_frames_generator(source_path='...') + + >>> with sv.VideoSink(target_path='...', video_info=video_info) as sink: + ... for frame in frames_generator: + ... result = model(frame)[0] + ... detections = sv.Detections.from_ultralytics(result) + ... annotated_frame = heat_map_annotator.annotate( + ... scene=frame.copy(), + ... detections=detections) + ... sink.write_frame(frame=annotated_frame) + ``` + + ![heatmap-annotator-example](https://media.roboflow.com/ + supervision-annotator-examples/heat-map-annotator-example-purple.png) + """ + + if self.heat_mask is None: + self.heat_mask = np.zeros(scene.shape[:2]) + mask = np.zeros(scene.shape[:2]) + for xy in detections.get_anchors_coordinates(self.position): + cv2.circle(mask, (int(xy[0]), int(xy[1])), self.radius, 1, -1) + self.heat_mask = mask + self.heat_mask + temp = self.heat_mask.copy() + temp = self.low_hue - temp / temp.max() * (self.low_hue - self.top_hue) + temp = temp.astype(np.uint8) + if self.kernel_size is not None: + temp = cv2.blur(temp, (self.kernel_size, self.kernel_size)) + hsv = np.zeros(scene.shape) + hsv[..., 0] = temp + hsv[..., 1] = 255 + hsv[..., 2] = 255 + temp = cv2.cvtColor(hsv.astype(np.uint8), cv2.COLOR_HSV2BGR) + mask = cv2.cvtColor(self.heat_mask.astype(np.uint8), cv2.COLOR_GRAY2BGR) > 0 + scene[mask] = cv2.addWeighted(temp, self.opacity, scene, 1 - self.opacity, 0)[ + mask + ] + return scene + + class ClassificationAnnotator: """ Annotate classification results on an image. diff --git a/supervision/annotators/utils.py b/supervision/annotators/utils.py index 6b6b9837..e206c8cb 100644 --- a/supervision/annotators/utils.py +++ b/supervision/annotators/utils.py @@ -10,13 +10,22 @@ from supervision.geometry.core import Position class ColorLookup(Enum): """ - Enum for annotator color lookup. + Enumeration class to define strategies for mapping colors to annotations. + + This enum supports three different lookup strategies: + - `INDEX`: Colors are determined by the index of the detection within the scene. + - `CLASS`: Colors are determined by the class label of the detected object. + - `TRACK`: Colors are determined by the tracking identifier of the object. """ INDEX = "index" CLASS = "class" TRACK = "track" + @classmethod + def list(cls): + return list(map(lambda c: c.value, cls)) + def resolve_color_idx( detections: Detections, @@ -93,7 +102,7 @@ class Trace: frame_id = np.full(len(detections), self.current_frame_id, dtype=int) self.frame_id = np.concatenate([self.frame_id, frame_id]) self.xy = np.concatenate( - [self.xy, detections.get_anchor_coordinates(self.anchor)] + [self.xy, detections.get_anchors_coordinates(self.anchor)] ) self.tracker_id = np.concatenate([self.tracker_id, detections.tracker_id]) diff --git a/supervision/assets/__init__.py b/supervision/assets/__init__.py new file mode 100644 index 00000000..3b76c5ab --- /dev/null +++ b/supervision/assets/__init__.py @@ -0,0 +1,2 @@ +from supervision.assets.downloader import download_assets +from supervision.assets.list import VideoAssets diff --git a/supervision/assets/downloader.py b/supervision/assets/downloader.py new file mode 100644 index 00000000..9b12264a --- /dev/null +++ b/supervision/assets/downloader.py @@ -0,0 +1,94 @@ +import os +from hashlib import md5 +from pathlib import Path +from shutil import copyfileobj +from typing import Union + +from supervision.assets.list import VIDEO_ASSETS, VideoAssets + +try: + from requests import get + from tqdm.auto import tqdm +except ImportError: + raise ValueError( + "\n" + "Please install requests and tqdm to download assets \n" + "or install supervision with assets \n" + "pip install supervision[assets] \n" + "\n" + ) + + +def is_md5_hash_matching(filename: str, original_md5_hash: str) -> bool: + """ + Check if the MD5 hash of a file matches the original hash. + + Parameters: + filename (str): The path to the file to be checked as a string. + original_md5_hash (str): The original MD5 hash to compare against. + + Returns: + bool: True if the hashes match, False otherwise. + """ + if not os.path.exists(filename): + return False + + with open(filename, "rb") as file: + file_contents = file.read() + computed_md5_hash = md5(file_contents).hexdigest() + + return computed_md5_hash == original_md5_hash + + +def download_assets(asset_name: Union[VideoAssets, str]) -> str: + """ + Download a specified asset if it doesn't already exist or is corrupted. + + Parameters: + asset_name (Union[VideoAssets, str]): The name or type of the asset to be + downloaded. + + Returns: + str: The filename of the downloaded asset. + + Example: + ```python + >>> from supervision.assets import download_assets, VideoAssets + + >>> download_assets(VideoAssets.VEHICLES) + "vehicles.mp4" + ``` + """ + + filename = asset_name.value if isinstance(asset_name, VideoAssets) else asset_name + + if not Path(filename).exists() and filename in VIDEO_ASSETS: + print(f"Downloading {filename} assets \n") + response = get(VIDEO_ASSETS[filename][0], stream=True, allow_redirects=True) + response.raise_for_status() + + file_size = int(response.headers.get("Content-Length", 0)) + folder_path = Path(filename).expanduser().resolve() + folder_path.parent.mkdir(parents=True, exist_ok=True) + + with tqdm.wrapattr( + response.raw, "read", total=file_size, desc="", colour="#a351fb" + ) as raw_resp: + with folder_path.open("wb") as file: + copyfileobj(raw_resp, file) + + elif Path(filename).exists(): + if not is_md5_hash_matching(filename, VIDEO_ASSETS[filename][1]): + print("File corrupted. Re-downloading... \n") + os.remove(filename) + return download_assets(filename) + + print(f"{filename} asset download complete. \n") + + else: + valid_assets = ", ".join(asset.value for asset in VideoAssets) + raise ValueError( + f"Invalid asset. It should be one of the following: {valid_assets}." + ) + + return filename diff --git a/supervision/assets/list.py b/supervision/assets/list.py new file mode 100644 index 00000000..a8ba566b --- /dev/null +++ b/supervision/assets/list.py @@ -0,0 +1,65 @@ +from enum import Enum +from typing import Dict, Tuple + +BASE_VIDEO_URL = "https://media.roboflow.com/supervision/video-examples/" + + +class VideoAssets(Enum): + """ + Each member of this enum represents a video asset. The value associated with each + member is the filename of the video. + + | Enum Member | Video Filename | Video URL | + |------------------------|----------------------------|---------------------------------------------------------------------------------------| + | `VEHICLES` | `vehicles.mp4` | [Link](https://media.roboflow.com/supervision/video-examples/vehicles.mp4) | + | `MILK_BOTTLING_PLANT` | `milk-bottling-plant.mp4` | [Link](https://media.roboflow.com/supervision/video-examples/milk-bottling-plant.mp4) | + | `VEHICLES_2` | `vehicles-2.mp4` | [Link](https://media.roboflow.com/supervision/video-examples/vehicles-2.mp4) | + | `GROCERY_STORE` | `grocery-store.mp4` | [Link](https://media.roboflow.com/supervision/video-examples/grocery-store.mp4) | + | `SUBWAY` | `subway.mp4` | [Link](https://media.roboflow.com/supervision/video-examples/subway.mp4) | + | `MARKET_SQUARE` | `market-square.mp4` | [Link](https://media.roboflow.com/supervision/video-examples/market-square.mp4) | + | `PEOPLE_WALKING` | `people-walking.mp4` | [Link](https://media.roboflow.com/supervision/video-examples/people-walking.mp4) | + """ + + VEHICLES = "vehicles.mp4" + MILK_BOTTLING_PLANT = "milk-bottling-plant.mp4" + VEHICLES_2 = "vehicles-2.mp4" + GROCERY_STORE = "grocery-store.mp4" + SUBWAY = "subway.mp4" + MARKET_SQUARE = "market-square.mp4" + PEOPLE_WALKING = "people-walking.mp4" + + @classmethod + def list(cls): + return list(map(lambda c: c.value, cls)) + + +VIDEO_ASSETS: Dict[str, Tuple[str, str]] = { + VideoAssets.VEHICLES.value: ( + f"{BASE_VIDEO_URL}{VideoAssets.VEHICLES.value}", + "8155ff4e4de08cfa25f39de96483f918", + ), + VideoAssets.VEHICLES_2.value: ( + f"{BASE_VIDEO_URL}{VideoAssets.VEHICLES_2.value}", + "830af6fba21ffbf14867a7fea595937b", + ), + VideoAssets.MILK_BOTTLING_PLANT.value: ( + f"{BASE_VIDEO_URL}{VideoAssets.MILK_BOTTLING_PLANT.value}", + "9e8fb6e883f842a38b3d34267290bdc7", + ), + VideoAssets.GROCERY_STORE.value: ( + f"{BASE_VIDEO_URL}{VideoAssets.GROCERY_STORE.value}", + "11402e7b861c1980527d3d74cbe3b366", + ), + VideoAssets.SUBWAY.value: ( + f"{BASE_VIDEO_URL}{VideoAssets.SUBWAY.value}", + "453475750691fb23c56a0cffef089194", + ), + VideoAssets.MARKET_SQUARE.value: ( + f"{BASE_VIDEO_URL}{VideoAssets.MARKET_SQUARE.value}", + "859179bf4a21f80a8baabfdb2ed716dc", + ), + VideoAssets.PEOPLE_WALKING.value: ( + f"{BASE_VIDEO_URL}{VideoAssets.PEOPLE_WALKING.value}", + "0574c053c8686c3f1dc0aa3743e45cb9", + ), +} diff --git a/supervision/dataset/formats/yolo.py b/supervision/dataset/formats/yolo.py index d93b0465..254f034d 100644 --- a/supervision/dataset/formats/yolo.py +++ b/supervision/dataset/formats/yolo.py @@ -149,7 +149,7 @@ def load_yolo_annotations( annotations[image_path] = Detections.empty() continue - lines = read_txt_file(str(annotation_path)) + lines = read_txt_file(file_path=annotation_path, skip_empty=True) h, w, _ = image.shape resolution_wh = (w, h) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index 77bfca9d..49a3f883 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -6,6 +6,7 @@ from typing import Any, Iterator, List, Optional, Tuple, Union import numpy as np from supervision.detection.utils import ( + calculate_masks_centroids, extract_ultralytics_masks, non_max_suppression, process_roboflow_result, @@ -323,7 +324,7 @@ class Detections: >>> inferencer = DetInferencer(model_name, checkpoint, device) >>> mmdet_result = inferencer(SOURCE_IMAGE_PATH, out_dir='./output', - ... return_datasample=True)["predictions"][0] + ... return_datasamples=True)["predictions"][0] >>> detections = sv.Detections.from_mmdet(mmdet_result) ``` """ @@ -600,7 +601,7 @@ class Detections: tracker_id=tracker_id, ) - def get_anchor_coordinates(self, anchor: Position) -> np.ndarray: + def get_anchors_coordinates(self, anchor: Position) -> np.ndarray: """ Calculates and returns the coordinates of a specific anchor point within the bounding boxes defined by the `xyxy` attribute. The anchor @@ -627,6 +628,12 @@ class Detections: (self.xyxy[:, 1] + self.xyxy[:, 3]) / 2, ] ).transpose() + elif anchor == Position.CENTER_OF_MASS: + if self.mask is None: + raise ValueError( + "Cannot use `Position.CENTER_OF_MASS` without a detection mask." + ) + return calculate_masks_centroids(masks=self.mask) elif anchor == Position.CENTER_LEFT: return np.array( [ diff --git a/supervision/detection/line_counter.py b/supervision/detection/line_counter.py index ea97fcb8..851d1d4a 100644 --- a/supervision/detection/line_counter.py +++ b/supervision/detection/line_counter.py @@ -1,4 +1,4 @@ -from typing import Dict, Optional +from typing import Dict, Optional, Tuple import cv2 import numpy as np @@ -10,37 +10,54 @@ from supervision.geometry.core import Point, Rect, Vector class LineZone: """ - Count the number of objects that cross a line. + This class is responsible for counting the number of objects that cross a + predefined line. + + !!! warning + + LineZone utilizes the `tracker_id`. Read + [here](https://supervision.roboflow.com/trackers/) to learn how to plug + tracking into your inference pipeline. + + Attributes: + in_count (int): The number of objects that have crossed the line from outside + to inside. + out_count (int): The number of objects that have crossed the line from inside + to outside. """ def __init__(self, start: Point, end: Point): """ - Initialize a LineCounter object. - - Attributes: + Args: start (Point): The starting point of the line. end (Point): The ending point of the line. - """ self.vector = Vector(start=start, end=end) self.tracker_state: Dict[str, bool] = {} self.in_count: int = 0 self.out_count: int = 0 - def trigger(self, detections: Detections): + def trigger(self, detections: Detections) -> Tuple[np.ndarray, np.ndarray]: """ - Update the in_count and out_count for the detections that cross the line. + Update the `in_count` and `out_count` based on the objects that cross the line. - Attributes: - detections (Detections): The detections for which to update the counts. + Args: + detections (Detections): A list of detections for which to update the + counts. + Returns: + A tuple of two boolean NumPy arrays. The first array indicates which + detections have crossed the line from outside to inside. The second + array indicates which detections have crossed the line from inside to + outside. """ - for xyxy, _, confidence, class_id, tracker_id in detections: - # handle detections with no tracker_id + crossed_in = np.full(len(detections), False) + crossed_out = np.full(len(detections), False) + + for i, (xyxy, _, confidence, class_id, tracker_id) in enumerate(detections): if tracker_id is None: continue - # we check if all four anchors of bbox are on the same side of vector x1, y1, x2, y2 = xyxy anchors = [ Point(x=x1, y=y1), @@ -50,25 +67,27 @@ class LineZone: ] triggers = [self.vector.is_in(point=anchor) for anchor in anchors] - # detection is partially in and partially out if len(set(triggers)) == 2: continue tracker_state = triggers[0] - # handle new detection + if tracker_id not in self.tracker_state: self.tracker_state[tracker_id] = tracker_state continue - # handle detection on the same side of the line if self.tracker_state.get(tracker_id) == tracker_state: continue self.tracker_state[tracker_id] = tracker_state if tracker_state: self.in_count += 1 + crossed_in[i] = True else: self.out_count += 1 + crossed_out[i] = True + + return crossed_in, crossed_out class LineZoneAnnotator: diff --git a/supervision/detection/tools/polygon_zone.py b/supervision/detection/tools/polygon_zone.py index f1dba839..8e18d8bd 100644 --- a/supervision/detection/tools/polygon_zone.py +++ b/supervision/detection/tools/polygon_zone.py @@ -56,11 +56,11 @@ class PolygonZone: """ clipped_xyxy = clip_boxes( - boxes_xyxy=detections.xyxy, frame_resolution_wh=self.frame_resolution_wh + xyxy=detections.xyxy, resolution_wh=self.frame_resolution_wh ) clipped_detections = replace(detections, xyxy=clipped_xyxy) clipped_anchors = np.ceil( - clipped_detections.get_anchor_coordinates(anchor=self.triggering_position) + clipped_detections.get_anchors_coordinates(anchor=self.triggering_position) ).astype(int) is_in_zone = self.mask[clipped_anchors[:, 1], clipped_anchors[:, 0]] self.current_count = int(np.sum(is_in_zone)) diff --git a/supervision/detection/utils.py b/supervision/detection/utils.py index 7a5eb546..1e5fdc35 100644 --- a/supervision/detection/utils.py +++ b/supervision/detection/utils.py @@ -110,17 +110,15 @@ def non_max_suppression( return keep[sort_index.argsort()] -def clip_boxes( - boxes_xyxy: np.ndarray, frame_resolution_wh: Tuple[int, int] -) -> np.ndarray: +def clip_boxes(xyxy: np.ndarray, resolution_wh: Tuple[int, int]) -> np.ndarray: """ Clips bounding boxes coordinates to fit within the frame resolution. Args: - boxes_xyxy (np.ndarray): A numpy array of shape `(N, 4)` where each + xyxy (np.ndarray): A numpy array of shape `(N, 4)` where each row corresponds to a bounding box in the format `(x_min, y_min, x_max, y_max)`. - frame_resolution_wh (Tuple[int, int]): A tuple of the form `(width, height)` + resolution_wh (Tuple[int, int]): A tuple of the form `(width, height)` representing the resolution of the frame. Returns: @@ -128,8 +126,8 @@ def clip_boxes( corresponds to a bounding box with coordinates clipped to fit within the frame resolution. """ - result = np.copy(boxes_xyxy) - width, height = frame_resolution_wh + result = np.copy(xyxy) + width, height = resolution_wh result[:, [0, 2]] = result[:, [0, 2]].clip(0, width) result[:, [1, 3]] = result[:, [1, 3]].clip(0, height) return result @@ -395,3 +393,33 @@ def move_boxes(xyxy: np.ndarray, offset: np.ndarray) -> np.ndarray: (np.ndarray) repositioned bounding boxes """ return xyxy + np.hstack([offset, offset]) + + +def calculate_masks_centroids(masks: np.ndarray) -> np.ndarray: + """ + Calculate the centroids of binary masks in a tensor. + + Parameters: + masks (np.ndarray): A 3D NumPy array of shape (num_masks, height, width). + Each 2D array in the tensor represents a binary mask. + + Returns: + A 2D NumPy array of shape (num_masks, 2), where each row contains the x and y + coordinates (in that order) of the centroid of the corresponding mask. + """ + num_masks, height, width = masks.shape + total_pixels = masks.sum(axis=(1, 2)) + + # offset for 1-based indexing + vertical_indices, horizontal_indices = np.indices((height, width)) + 0.5 + # avoid division by zero for empty masks + total_pixels[total_pixels == 0] = 1 + + def sum_over_mask(indices: np.ndarray, axis: tuple) -> np.ndarray: + return np.tensordot(masks, indices, axes=axis) + + aggregation_axis = ([1, 2], [0, 1]) + centroid_x = sum_over_mask(horizontal_indices, aggregation_axis) / total_pixels + centroid_y = sum_over_mask(vertical_indices, aggregation_axis) / total_pixels + + return np.column_stack((centroid_x, centroid_y)).astype(int) diff --git a/supervision/draw/color.py b/supervision/draw/color.py index ed28e78b..9d1b4ad9 100644 --- a/supervision/draw/color.py +++ b/supervision/draw/color.py @@ -34,21 +34,35 @@ def _validate_color_hex(color_hex: str): @dataclass class Color: + """ + Represents a color in RGB format. + + Attributes: + r (int): Red channel. + g (int): Green channel. + b (int): Blue channel. + """ + r: int g: int b: int @classmethod - def from_hex(cls, color_hex: str): + def from_hex(cls, color_hex: str) -> Color: """ - Creates a Color instance from a color hex string + Create a Color instance from a hex string. - :param color_hex: str : The color hex string in the format - of "fff", "ffffff", "#fff", or "#ffffff" - :return: Color : A Color instance representing the color + Args: + color_hex (str): Hex string of the color. + + Returns: + Color: Instance representing the color. Example: - color = Color.from_hex('#ff00ff') + ``` + >>> Color.from_hex('#ff00ff') + Color(r=255, g=0, b=255) + ``` """ _validate_color_hex(color_hex) color_hex = color_hex.lstrip("#") @@ -57,19 +71,48 @@ class Color: r, g, b = (int(color_hex[i : i + 2], 16) for i in range(0, 6, 2)) return cls(r, g, b) + def as_hex(self) -> str: + """ + Converts the Color instance to a hex string. + + Returns: + str: The hexadecimal color string. + + Example: + ``` + >>> Color(r=255, g=0, b=255).as_hex() + '#ff00ff' + ``` + """ + return f"#{self.r:02x}{self.g:02x}{self.b:02x}" + def as_rgb(self) -> Tuple[int, int, int]: """ - Returns the color as a tuple of integers in the RGB format + Returns the color as an RGB tuple. - :return: Tuple[int, int, int] : The color in the RGB format + Returns: + Tuple[int, int, int]: RGB tuple. + + Example: + ``` + >>> color.as_rgb() + (255, 0, 255) + ``` """ return self.r, self.g, self.b def as_bgr(self) -> Tuple[int, int, int]: """ - Returns the color as a tuple of integers in the BGR format + Returns the color as a BGR tuple. - :return: Tuple[int, int, int] : The color in the BGR format + Returns: + Tuple[int, int, int]: BGR tuple. + + Example: + ``` + >>> color.as_bgr() + (255, 0, 255) + ``` """ return self.b, self.g, self.r @@ -100,33 +143,55 @@ class ColorPalette: @classmethod def default(cls) -> ColorPalette: + """ + Returns a default color palette. + + Returns: + ColorPalette: A ColorPalette instance with default colors. + + Example: + ``` + >>> ColorPalette.default() + ColorPalette(colors=[Color(r=255, g=0, b=0), Color(r=0, g=255, b=0), ...]) + ``` + """ return ColorPalette.from_hex(color_hex_list=DEFAULT_COLOR_PALETTE) @classmethod - def from_hex(cls, color_hex_list: List[str]): + def from_hex(cls, color_hex_list: List[str]) -> ColorPalette: """ - Creates a ColorPalette instance from a list of color hex strings + Create a ColorPalette instance from a list of hex strings. - :param color_hex_list: List[str] : A list of color hex strings in the - format of "fff", "ffffff", "#fff", or "#ffffff" - :return: ColorPalette : A ColorPalette instance representing the color palette + Args: + color_hex_list (List[str]): List of color hex strings. + + Returns: + ColorPalette: A ColorPalette instance. Example: - color_palette = ColorPalette.from_hex(['#ff0000', '#00ff00', '#0000ff']) + ``` + >>> ColorPalette.from_hex(['#ff0000', '#00ff00', '#0000ff']) + ColorPalette(colors=[Color(r=255, g=0, b=0), Color(r=0, g=255, b=0), ...]) + ``` """ colors = [Color.from_hex(color_hex) for color_hex in color_hex_list] return cls(colors) def by_idx(self, idx: int) -> Color: """ - Returns the color at a given index in the color palette. + Return the color at a given index in the palette. - :param idx: int : The index of the color in the color palette - :return: Color : The color at the given index + Args: + idx (int): Index of the color in the palette. + + Returns: + Color: Color at the given index. Example: - color_palette = ColorPalette.from_hex(['#ff0000', '#00ff00', '#0000ff']) - color = color_palette.by_idx(1) + ``` + >>> color_palette.by_idx(1) + Color(r=0, g=255, b=0) + ``` """ if idx < 0: raise ValueError("idx argument should not be negative") diff --git a/supervision/draw/utils.py b/supervision/draw/utils.py index 9d75690d..1d4d5ec6 100644 --- a/supervision/draw/utils.py +++ b/supervision/draw/utils.py @@ -1,4 +1,5 @@ -from typing import Optional +import os +from typing import Optional, Union import cv2 import numpy as np @@ -168,3 +169,65 @@ def draw_text( lineType=cv2.LINE_AA, ) return scene + + +def draw_image( + scene: np.ndarray, image: Union[str, np.ndarray], opacity: float, rect: Rect +) -> np.ndarray: + """ + Draws an image onto a given scene with specified opacity and dimensions. + + Args: + scene (np.ndarray): Background image where the new image will be drawn. + image (Union[str, np.ndarray]): Image to draw. + opacity (float): Opacity of the image to be drawn. + rect (Rect): Rectangle specifying where to draw the image. + + Returns: + np.ndarray: The updated scene. + + Raises: + FileNotFoundError: If the image path does not exist. + ValueError: For invalid opacity or rectangle dimensions. + """ + + # Validate and load image + if isinstance(image, str): + if not os.path.exists(image): + raise FileNotFoundError(f"Image path ('{image}') does not exist.") + image = cv2.imread(image, cv2.IMREAD_UNCHANGED) + + # Validate opacity + if not 0.0 <= opacity <= 1.0: + raise ValueError("Opacity must be between 0.0 and 1.0.") + + # Validate rectangle dimensions + if ( + rect.x < 0 + or rect.y < 0 + or rect.x + rect.width > scene.shape[1] + or rect.y + rect.height > scene.shape[0] + ): + raise ValueError("Invalid rectangle dimensions.") + + # Resize and isolate alpha channel + image = cv2.resize(image, (rect.width, rect.height)) + alpha_channel = ( + image[:, :, 3] + if image.shape[2] == 4 + else np.ones((rect.height, rect.width), dtype=image.dtype) * 255 + ) + alpha_scaled = cv2.convertScaleAbs(alpha_channel * opacity) + + # Perform blending + scene_roi = scene[rect.y : rect.y + rect.height, rect.x : rect.x + rect.width] + alpha_float = alpha_scaled.astype(np.float32) / 255.0 + blended_roi = cv2.convertScaleAbs( + (1 - alpha_float[..., np.newaxis]) * scene_roi + + alpha_float[..., np.newaxis] * image[:, :, :3] + ) + + # Update the scene + scene[rect.y : rect.y + rect.height, rect.x : rect.x + rect.width] = blended_roi + + return scene diff --git a/supervision/geometry/core.py b/supervision/geometry/core.py index 1771a356..d118b5a4 100644 --- a/supervision/geometry/core.py +++ b/supervision/geometry/core.py @@ -19,6 +19,7 @@ class Position(Enum): BOTTOM_LEFT = "BOTTOM_LEFT" BOTTOM_CENTER = "BOTTOM_CENTER" BOTTOM_RIGHT = "BOTTOM_RIGHT" + CENTER_OF_MASS = "CENTER_OF_MASS" @classmethod def list(cls): diff --git a/supervision/utils/file.py b/supervision/utils/file.py index 3954f9d5..c0521236 100644 --- a/supervision/utils/file.py +++ b/supervision/utils/file.py @@ -57,19 +57,24 @@ def list_files_with_extensions( return files_with_extensions -def read_txt_file(file_path: str) -> List[str]: +def read_txt_file(file_path: str, skip_empty: bool = False) -> List[str]: """ Read a text file and return a list of strings without newline characters. + Optionally skip empty lines. Args: file_path (str): The path to the text file. + skip_empty (bool): If True, skip lines that are empty or contain only + whitespace. Default is False. Returns: List[str]: A list of strings representing the lines in the text file. """ with open(file_path, "r") as file: - lines = file.readlines() - lines = [line.rstrip("\n") for line in lines] + if skip_empty: + lines = [line.rstrip("\n") for line in file if line.strip()] + else: + lines = [line.rstrip("\n") for line in file] return lines diff --git a/test/annotators/test_utils.py b/test/annotators/test_utils.py index 98d9be6b..1344d213 100644 --- a/test/annotators/test_utils.py +++ b/test/annotators/test_utils.py @@ -1,5 +1,5 @@ from contextlib import ExitStack as DoesNotRaise -from test.utils import mock_detections +from test.test_utils import mock_detections from typing import Optional import numpy as np diff --git a/test/dataset/formats/test_pascal_voc.py b/test/dataset/formats/test_pascal_voc.py index 67d26c57..042748e9 100644 --- a/test/dataset/formats/test_pascal_voc.py +++ b/test/dataset/formats/test_pascal_voc.py @@ -1,6 +1,6 @@ import xml.etree.ElementTree as ET from contextlib import ExitStack as DoesNotRaise -from test.utils import mock_detections +from test.test_utils import mock_detections from typing import List, Optional import numpy as np diff --git a/test/dataset/test_core.py b/test/dataset/test_core.py index 72ae27f3..4a75225c 100644 --- a/test/dataset/test_core.py +++ b/test/dataset/test_core.py @@ -1,5 +1,5 @@ from contextlib import ExitStack as DoesNotRaise -from test.utils import mock_detections +from test.test_utils import mock_detections from typing import List, Optional import numpy as np diff --git a/test/dataset/test_utils.py b/test/dataset/test_utils.py index 6d0db2e5..5ca96ca5 100644 --- a/test/dataset/test_utils.py +++ b/test/dataset/test_utils.py @@ -1,5 +1,5 @@ from contextlib import ExitStack as DoesNotRaise -from test.utils import mock_detections +from test.test_utils import mock_detections from typing import Dict, List, Optional, Tuple, TypeVar import pytest diff --git a/test/detection/test_core.py b/test/detection/test_core.py index 02595d97..0d17e09b 100644 --- a/test/detection/test_core.py +++ b/test/detection/test_core.py @@ -1,5 +1,5 @@ from contextlib import ExitStack as DoesNotRaise -from test.utils import mock_detections +from test.test_utils import mock_detections from typing import List, Optional, Union import numpy as np @@ -270,6 +270,6 @@ def test_get_anchor_coordinates( expected_result: np.ndarray, exception: Exception, ) -> None: - result = detections.get_anchor_coordinates(anchor) + result = detections.get_anchors_coordinates(anchor) with exception: assert np.array_equal(result, expected_result) diff --git a/test/detection/test_utils.py b/test/detection/test_utils.py index 09691adf..dc8e9e16 100644 --- a/test/detection/test_utils.py +++ b/test/detection/test_utils.py @@ -5,6 +5,7 @@ import numpy as np import pytest from supervision.detection.utils import ( + calculate_masks_centroids, clip_boxes, filter_polygons_by_area, move_boxes, @@ -122,7 +123,7 @@ def test_non_max_suppression( @pytest.mark.parametrize( - "boxes_xyxy, frame_resolution_wh, expected_result", + "xyxy, resolution_wh, expected_result", [ ( np.empty(shape=(0, 4)), @@ -157,11 +158,11 @@ def test_non_max_suppression( ], ) def test_clip_boxes( - boxes_xyxy: np.ndarray, - frame_resolution_wh: Tuple[int, int], + xyxy: np.ndarray, + resolution_wh: Tuple[int, int], expected_result: np.ndarray, ) -> None: - result = clip_boxes(boxes_xyxy=boxes_xyxy, frame_resolution_wh=frame_resolution_wh) + result = clip_boxes(xyxy=xyxy, resolution_wh=resolution_wh) assert np.array_equal(result, expected_result) @@ -498,5 +499,97 @@ def test_move_boxes( expected_result: np.ndarray, exception: Exception, ) -> None: - result = move_boxes(xyxy=xyxy, offset=offset) - assert np.array_equal(result, expected_result) + with exception: + result = move_boxes(xyxy=xyxy, offset=offset) + assert np.array_equal(result, expected_result) + + +@pytest.mark.parametrize( + "masks, expected_result, exception", + [ + ( + np.array( + [ + [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + ] + ] + ), + np.array([[0, 0]]), + DoesNotRaise(), + ), # single mask with all zeros + ( + np.array( + [ + [ + [1, 1, 1, 1], + [1, 1, 1, 1], + [1, 1, 1, 1], + [1, 1, 1, 1], + ] + ] + ), + np.array([[2, 2]]), + DoesNotRaise(), + ), # single mask with all ones + ( + np.array( + [ + [ + [0, 1, 1, 0], + [1, 1, 1, 1], + [1, 1, 1, 1], + [0, 1, 1, 0], + ] + ] + ), + np.array([[2, 2]]), + DoesNotRaise(), + ), # single mask with symmetric ones + ( + np.array( + [ + [ + [0, 0, 0, 0], + [0, 0, 1, 1], + [0, 0, 1, 1], + [0, 0, 0, 0], + ] + ] + ), + np.array([[3, 2]]), + DoesNotRaise(), + ), # single mask with asymmetric ones + ( + np.array( + [ + [ + [0, 1, 1, 0], + [1, 1, 1, 1], + [1, 1, 1, 1], + [0, 1, 1, 0], + ], + [ + [0, 0, 0, 0], + [0, 0, 1, 1], + [0, 0, 1, 1], + [0, 0, 0, 0], + ], + ] + ), + np.array([[2, 2], [3, 2]]), + DoesNotRaise(), + ), # two masks + ], +) +def test_calculate_masks_centroids( + masks: np.ndarray, + expected_result: np.ndarray, + exception: Exception, +) -> None: + with exception: + result = calculate_masks_centroids(masks=masks) + assert np.array_equal(result, expected_result) diff --git a/test/draw/test_color.py b/test/draw/test_color.py index 723f7043..eb4c3657 100644 --- a/test/draw/test_color.py +++ b/test/draw/test_color.py @@ -30,3 +30,22 @@ def test_color_from_hex( with exception: result = Color.from_hex(color_hex=color_hex) assert result == expected_result + + +@pytest.mark.parametrize( + "color, expected_result, exception", + [ + (Color.white(), "#ffffff", DoesNotRaise()), + (Color.black(), "#000000", DoesNotRaise()), + (Color.red(), "#ff0000", DoesNotRaise()), + (Color.green(), "#00ff00", DoesNotRaise()), + (Color.blue(), "#0000ff", DoesNotRaise()), + (Color(r=128, g=128, b=0), "#808000", DoesNotRaise()), + ], +) +def test_color_as_hex( + color: Color, expected_result: Optional[str], exception: Exception +) -> None: + with exception: + result = color.as_hex() + assert result == expected_result diff --git a/test/metrics/test_detection.py b/test/metrics/test_detection.py index 796e6c81..7cb92c46 100644 --- a/test/metrics/test_detection.py +++ b/test/metrics/test_detection.py @@ -1,5 +1,5 @@ from contextlib import ExitStack as DoesNotRaise -from test.utils import assert_almost_equal, mock_detections +from test.test_utils import assert_almost_equal, mock_detections from typing import Optional, Union import numpy as np diff --git a/test/utils.py b/test/test_utils.py similarity index 100% rename from test/utils.py rename to test/test_utils.py diff --git a/test/utils/__init__.py b/test/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/utils/test_file.py b/test/utils/test_file.py new file mode 100644 index 00000000..7a8dd37f --- /dev/null +++ b/test/utils/test_file.py @@ -0,0 +1,64 @@ +import os +from contextlib import ExitStack as DoesNotRaise +from typing import List, Optional + +import pytest + +from supervision.utils.file import read_txt_file + +FILE_1_CONTENT = """Line 1 +Line 2 +Line 3 +""" + +FILE_2_CONTENT = """ +Line 2 + +Line 4 + +""" # noqa + +FILE_3_CONTENT = """ +Line 2 + +Line 4 + +""" + + +@pytest.fixture(scope="module", autouse=True) +def setup_and_teardown_files(): + with open("file_1.txt", "w") as file: + file.write(FILE_1_CONTENT) + with open("file_2.txt", "w") as file: + file.write(FILE_2_CONTENT) + with open("file_3.txt", "w") as file: + file.write(FILE_3_CONTENT) + + yield + + os.remove("file_1.txt") + os.remove("file_2.txt") + os.remove("file_3.txt") + + +@pytest.mark.parametrize( + "file_name, skip_empty, expected_result, exception", + [ + ("file_1.txt", False, ["Line 1", "Line 2", "Line 3"], DoesNotRaise()), + ("file_2.txt", True, ["Line 2", "Line 4"], DoesNotRaise()), + ("file_2.txt", False, [" ", "Line 2", "", "Line 4", ""], DoesNotRaise()), + ("file_3.txt", True, ["Line 2", "Line 4"], DoesNotRaise()), + ("file_3.txt", False, ["", "Line 2", "", "Line 4", ""], DoesNotRaise()), + ("file_4.txt", True, None, pytest.raises(FileNotFoundError)), + ], +) +def test_read_txt_file( + file_name: str, + skip_empty: bool, + expected_result: Optional[List[str]], + exception: Exception, +): + with exception: + result = read_txt_file(file_name, skip_empty) + assert result == expected_result