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
+ ... )
+ ```
+
+
+
+ { 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
+ ... )
+ ```
+
+
+
+ { 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)
+ ```
+
+
+
+ { 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)
+```
+
+
+
+## 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
+ ... )
+ ```
+
+ 
+ """
+ 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
+ ... )
+ ```
+
+ 
+ """
+ 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):

"""
- 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)
```
:
+ """
+ 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)
+ ```
+
+ 
+ """
+
+ 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