From 0b7a376ace83d059d1281cddef6306d07a42df51 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Dec 2023 10:15:33 +0000 Subject: [PATCH 01/45] :arrow_up: Bump mkdocs-material from 9.5.0 to 9.5.2 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.0 to 9.5.2. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.0...9.5.2) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index cc19534e..981d62db 100644 --- a/poetry.lock +++ b/poetry.lock @@ -510,6 +510,7 @@ files = [ {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18a64814ae7bce73925131381603fff0116e2df25230dfc80d6d690aa6e20b37"}, {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90c81f22b4f572f8a2110b0b741bb64e5a6427e0a198b2cdc1fbaf85f352a3aa"}, {file = "contourpy-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:53cc3a40635abedbec7f1bde60f8c189c49e84ac180c665f2cd7c162cc454baa"}, + {file = "contourpy-1.1.0-cp310-cp310-win32.whl", hash = "sha256:9b2dd2ca3ac561aceef4c7c13ba654aaa404cf885b187427760d7f7d4c57cff8"}, {file = "contourpy-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:1f795597073b09d631782e7245016a4323cf1cf0b4e06eef7ea6627e06a37ff2"}, {file = "contourpy-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0b7b04ed0961647691cfe5d82115dd072af7ce8846d31a5fac6c142dcce8b882"}, {file = "contourpy-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27bc79200c742f9746d7dd51a734ee326a292d77e7d94c8af6e08d1e6c15d545"}, @@ -518,6 +519,7 @@ files = [ {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5cec36c5090e75a9ac9dbd0ff4a8cf7cecd60f1b6dc23a374c7d980a1cd710e"}, {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f0cbd657e9bde94cd0e33aa7df94fb73c1ab7799378d3b3f902eb8eb2e04a3a"}, {file = "contourpy-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:181cbace49874f4358e2929aaf7ba84006acb76694102e88dd15af861996c16e"}, + {file = "contourpy-1.1.0-cp311-cp311-win32.whl", hash = "sha256:edb989d31065b1acef3828a3688f88b2abb799a7db891c9e282df5ec7e46221b"}, {file = "contourpy-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fb3b7d9e6243bfa1efb93ccfe64ec610d85cfe5aec2c25f97fbbd2e58b531256"}, {file = "contourpy-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bcb41692aa09aeb19c7c213411854402f29f6613845ad2453d30bf421fe68fed"}, {file = "contourpy-1.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5d123a5bc63cd34c27ff9c7ac1cd978909e9c71da12e05be0231c608048bb2ae"}, @@ -526,6 +528,7 @@ files = [ {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:317267d915490d1e84577924bd61ba71bf8681a30e0d6c545f577363157e5e94"}, {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d551f3a442655f3dcc1285723f9acd646ca5858834efeab4598d706206b09c9f"}, {file = "contourpy-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e7a117ce7df5a938fe035cad481b0189049e8d92433b4b33aa7fc609344aafa1"}, + {file = "contourpy-1.1.0-cp38-cp38-win32.whl", hash = "sha256:108dfb5b3e731046a96c60bdc46a1a0ebee0760418951abecbe0fc07b5b93b27"}, {file = "contourpy-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:d4f26b25b4f86087e7d75e63212756c38546e70f2a92d2be44f80114826e1cd4"}, {file = "contourpy-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc00bb4225d57bff7ebb634646c0ee2a1298402ec10a5fe7af79df9a51c1bfd9"}, {file = "contourpy-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:189ceb1525eb0655ab8487a9a9c41f42a73ba52d6789754788d1883fb06b2d8a"}, @@ -534,6 +537,7 @@ files = [ {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:143dde50520a9f90e4a2703f367cf8ec96a73042b72e68fcd184e1279962eb6f"}, {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e94bef2580e25b5fdb183bf98a2faa2adc5b638736b2c0a4da98691da641316a"}, {file = "contourpy-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ed614aea8462735e7d70141374bd7650afd1c3f3cb0c2dbbcbe44e14331bf002"}, + {file = "contourpy-1.1.0-cp39-cp39-win32.whl", hash = "sha256:71551f9520f008b2950bef5f16b0e3587506ef4f23c734b71ffb7b89f8721999"}, {file = "contourpy-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:438ba416d02f82b692e371858143970ed2eb6337d9cdbbede0d8ad9f3d7dd17d"}, {file = "contourpy-1.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a698c6a7a432789e587168573a864a7ea374c6be8d4f31f9d87c001d5a843493"}, {file = "contourpy-1.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:397b0ac8a12880412da3551a8cb5a187d3298a72802b45a3bd1805e204ad8439"}, @@ -1651,6 +1655,16 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -1846,13 +1860,13 @@ mkdocs = ">=1.1" [[package]] name = "mkdocs-material" -version = "9.5.0" +version = "9.5.2" description = "Documentation that simply works" optional = false python-versions = ">=3.8" files = [ - {file = "mkdocs_material-9.5.0-py3-none-any.whl", hash = "sha256:47323c9943756ced14487d0607caa283e350e8fd4c724b224bd69ccb0b0731f2"}, - {file = "mkdocs_material-9.5.0.tar.gz", hash = "sha256:f8f9ccb526bd8a8ac2e5ec2e64e1a167cfb53cf6a84c8924dbb607f0a70e74ae"}, + {file = "mkdocs_material-9.5.2-py3-none-any.whl", hash = "sha256:6ed0fbf4682491766f0ec1acc955db6901c2fd424c7ab343964ef51b819741f5"}, + {file = "mkdocs_material-9.5.2.tar.gz", hash = "sha256:ca8b9cd2b3be53e858e5a1a45ac9668bd78d95d77a30288bb5ebc1a31db6184c"}, ] [package.dependencies] @@ -2782,6 +2796,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -2789,8 +2804,15 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -2807,6 +2829,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -2814,6 +2837,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, From c210362389f5edef6655f3bc867368dec09e2a3c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Dec 2023 00:17:42 +0000 Subject: [PATCH 02/45] :arrow_up: Bump ruff from 0.1.7 to 0.1.8 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.1.7 to 0.1.8. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.1.7...v0.1.8) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 62 ++++++++++++++++++++++++++++++++++---------------- pyproject.toml | 2 +- 2 files changed, 44 insertions(+), 20 deletions(-) diff --git a/poetry.lock b/poetry.lock index cc19534e..41a1e882 100644 --- a/poetry.lock +++ b/poetry.lock @@ -510,6 +510,7 @@ files = [ {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18a64814ae7bce73925131381603fff0116e2df25230dfc80d6d690aa6e20b37"}, {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90c81f22b4f572f8a2110b0b741bb64e5a6427e0a198b2cdc1fbaf85f352a3aa"}, {file = "contourpy-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:53cc3a40635abedbec7f1bde60f8c189c49e84ac180c665f2cd7c162cc454baa"}, + {file = "contourpy-1.1.0-cp310-cp310-win32.whl", hash = "sha256:9b2dd2ca3ac561aceef4c7c13ba654aaa404cf885b187427760d7f7d4c57cff8"}, {file = "contourpy-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:1f795597073b09d631782e7245016a4323cf1cf0b4e06eef7ea6627e06a37ff2"}, {file = "contourpy-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0b7b04ed0961647691cfe5d82115dd072af7ce8846d31a5fac6c142dcce8b882"}, {file = "contourpy-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27bc79200c742f9746d7dd51a734ee326a292d77e7d94c8af6e08d1e6c15d545"}, @@ -518,6 +519,7 @@ files = [ {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5cec36c5090e75a9ac9dbd0ff4a8cf7cecd60f1b6dc23a374c7d980a1cd710e"}, {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f0cbd657e9bde94cd0e33aa7df94fb73c1ab7799378d3b3f902eb8eb2e04a3a"}, {file = "contourpy-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:181cbace49874f4358e2929aaf7ba84006acb76694102e88dd15af861996c16e"}, + {file = "contourpy-1.1.0-cp311-cp311-win32.whl", hash = "sha256:edb989d31065b1acef3828a3688f88b2abb799a7db891c9e282df5ec7e46221b"}, {file = "contourpy-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fb3b7d9e6243bfa1efb93ccfe64ec610d85cfe5aec2c25f97fbbd2e58b531256"}, {file = "contourpy-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bcb41692aa09aeb19c7c213411854402f29f6613845ad2453d30bf421fe68fed"}, {file = "contourpy-1.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5d123a5bc63cd34c27ff9c7ac1cd978909e9c71da12e05be0231c608048bb2ae"}, @@ -526,6 +528,7 @@ files = [ {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:317267d915490d1e84577924bd61ba71bf8681a30e0d6c545f577363157e5e94"}, {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d551f3a442655f3dcc1285723f9acd646ca5858834efeab4598d706206b09c9f"}, {file = "contourpy-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e7a117ce7df5a938fe035cad481b0189049e8d92433b4b33aa7fc609344aafa1"}, + {file = "contourpy-1.1.0-cp38-cp38-win32.whl", hash = "sha256:108dfb5b3e731046a96c60bdc46a1a0ebee0760418951abecbe0fc07b5b93b27"}, {file = "contourpy-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:d4f26b25b4f86087e7d75e63212756c38546e70f2a92d2be44f80114826e1cd4"}, {file = "contourpy-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc00bb4225d57bff7ebb634646c0ee2a1298402ec10a5fe7af79df9a51c1bfd9"}, {file = "contourpy-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:189ceb1525eb0655ab8487a9a9c41f42a73ba52d6789754788d1883fb06b2d8a"}, @@ -534,6 +537,7 @@ files = [ {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:143dde50520a9f90e4a2703f367cf8ec96a73042b72e68fcd184e1279962eb6f"}, {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e94bef2580e25b5fdb183bf98a2faa2adc5b638736b2c0a4da98691da641316a"}, {file = "contourpy-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ed614aea8462735e7d70141374bd7650afd1c3f3cb0c2dbbcbe44e14331bf002"}, + {file = "contourpy-1.1.0-cp39-cp39-win32.whl", hash = "sha256:71551f9520f008b2950bef5f16b0e3587506ef4f23c734b71ffb7b89f8721999"}, {file = "contourpy-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:438ba416d02f82b692e371858143970ed2eb6337d9cdbbede0d8ad9f3d7dd17d"}, {file = "contourpy-1.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a698c6a7a432789e587168573a864a7ea374c6be8d4f31f9d87c001d5a843493"}, {file = "contourpy-1.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:397b0ac8a12880412da3551a8cb5a187d3298a72802b45a3bd1805e204ad8439"}, @@ -1651,6 +1655,16 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -2782,6 +2796,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -2789,8 +2804,15 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -2807,6 +2829,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -2814,6 +2837,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -3272,28 +3296,28 @@ files = [ [[package]] name = "ruff" -version = "0.1.7" +version = "0.1.8" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.1.7-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7f80496854fdc65b6659c271d2c26e90d4d401e6a4a31908e7e334fab4645aac"}, - {file = "ruff-0.1.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:1ea109bdb23c2a4413f397ebd8ac32cb498bee234d4191ae1a310af760e5d287"}, - {file = "ruff-0.1.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b0c2de9dd9daf5e07624c24add25c3a490dbf74b0e9bca4145c632457b3b42a"}, - {file = "ruff-0.1.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:69a4bed13bc1d5dabf3902522b5a2aadfebe28226c6269694283c3b0cecb45fd"}, - {file = "ruff-0.1.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de02ca331f2143195a712983a57137c5ec0f10acc4aa81f7c1f86519e52b92a1"}, - {file = "ruff-0.1.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:45b38c3f8788a65e6a2cab02e0f7adfa88872696839d9882c13b7e2f35d64c5f"}, - {file = "ruff-0.1.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c64cb67b2025b1ac6d58e5ffca8f7b3f7fd921f35e78198411237e4f0db8e73"}, - {file = "ruff-0.1.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9dcc6bb2f4df59cb5b4b40ff14be7d57012179d69c6565c1da0d1f013d29951b"}, - {file = "ruff-0.1.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df2bb4bb6bbe921f6b4f5b6fdd8d8468c940731cb9406f274ae8c5ed7a78c478"}, - {file = "ruff-0.1.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:276a89bcb149b3d8c1b11d91aa81898fe698900ed553a08129b38d9d6570e717"}, - {file = "ruff-0.1.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:90c958fe950735041f1c80d21b42184f1072cc3975d05e736e8d66fc377119ea"}, - {file = "ruff-0.1.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6b05e3b123f93bb4146a761b7a7d57af8cb7384ccb2502d29d736eaade0db519"}, - {file = "ruff-0.1.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:290ecab680dce94affebefe0bbca2322a6277e83d4f29234627e0f8f6b4fa9ce"}, - {file = "ruff-0.1.7-py3-none-win32.whl", hash = "sha256:416dfd0bd45d1a2baa3b1b07b1b9758e7d993c256d3e51dc6e03a5e7901c7d80"}, - {file = "ruff-0.1.7-py3-none-win_amd64.whl", hash = "sha256:4af95fd1d3b001fc41325064336db36e3d27d2004cdb6d21fd617d45a172dd96"}, - {file = "ruff-0.1.7-py3-none-win_arm64.whl", hash = "sha256:0683b7bfbb95e6df3c7c04fe9d78f631f8e8ba4868dfc932d43d690698057e2e"}, - {file = "ruff-0.1.7.tar.gz", hash = "sha256:dffd699d07abf54833e5f6cc50b85a6ff043715da8788c4a79bcd4ab4734d306"}, + {file = "ruff-0.1.8-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7de792582f6e490ae6aef36a58d85df9f7a0cfd1b0d4fe6b4fb51803a3ac96fa"}, + {file = "ruff-0.1.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c8e3255afd186c142eef4ec400d7826134f028a85da2146102a1172ecc7c3696"}, + {file = "ruff-0.1.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff78a7583020da124dd0deb835ece1d87bb91762d40c514ee9b67a087940528b"}, + {file = "ruff-0.1.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd8ee69b02e7bdefe1e5da2d5b6eaaddcf4f90859f00281b2333c0e3a0cc9cd6"}, + {file = "ruff-0.1.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a05b0ddd7ea25495e4115a43125e8a7ebed0aa043c3d432de7e7d6e8e8cd6448"}, + {file = "ruff-0.1.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e6f08ca730f4dc1b76b473bdf30b1b37d42da379202a059eae54ec7fc1fbcfed"}, + {file = "ruff-0.1.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f35960b02df6b827c1b903091bb14f4b003f6cf102705efc4ce78132a0aa5af3"}, + {file = "ruff-0.1.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d076717c67b34c162da7c1a5bda16ffc205e0e0072c03745275e7eab888719f"}, + {file = "ruff-0.1.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6a21ab023124eafb7cef6d038f835cb1155cd5ea798edd8d9eb2f8b84be07d9"}, + {file = "ruff-0.1.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ce697c463458555027dfb194cb96d26608abab920fa85213deb5edf26e026664"}, + {file = "ruff-0.1.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:db6cedd9ffed55548ab313ad718bc34582d394e27a7875b4b952c2d29c001b26"}, + {file = "ruff-0.1.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:05ffe9dbd278965271252704eddb97b4384bf58b971054d517decfbf8c523f05"}, + {file = "ruff-0.1.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5daaeaf00ae3c1efec9742ff294b06c3a2a9db8d3db51ee4851c12ad385cda30"}, + {file = "ruff-0.1.8-py3-none-win32.whl", hash = "sha256:e49fbdfe257fa41e5c9e13c79b9e79a23a79bd0e40b9314bc53840f520c2c0b3"}, + {file = "ruff-0.1.8-py3-none-win_amd64.whl", hash = "sha256:f41f692f1691ad87f51708b823af4bb2c5c87c9248ddd3191c8f088e66ce590a"}, + {file = "ruff-0.1.8-py3-none-win_arm64.whl", hash = "sha256:aa8ee4f8440023b0a6c3707f76cadce8657553655dcbb5fc9b2f9bb9bee389f6"}, + {file = "ruff-0.1.8.tar.gz", hash = "sha256:f7ee467677467526cfe135eab86a40a0e8db43117936ac4f9b469ce9cdb3fb62"}, ] [[package]] @@ -3787,4 +3811,4 @@ desktop = ["opencv-python"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "5ecf358004c976e2dfbaaee0e8a962f49a55b0015c7a8e3ecb9eba324136e2c0" +content-hash = "96d8f2245e702dd6ce40efc81680784f7eabc4694f85bb966ac0871ff092d8d4" diff --git a/pyproject.toml b/pyproject.toml index 9ba0bb87..ad4ec987 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,7 @@ pytest = "^7.2.2" wheel = ">=0.40,<0.43" notebook = ">=6.5.3,<8.0.0" build = ">=0.10,<1.1" -ruff = ">=0.0.280,<0.1.8" +ruff = ">=0.0.280,<0.1.9" isort = "^5.12.0" mypy = "^1.4.1" pre-commit = "^3.3.3" From 5a28378d3cfdef055a4aee9e8ada4fc2763bb502 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Dec 2023 00:21:00 +0000 Subject: [PATCH 03/45] :arrow_up: Bump isort from 5.12.0 to 5.13.2 Bumps [isort](https://github.com/pycqa/isort) from 5.12.0 to 5.13.2. - [Release notes](https://github.com/pycqa/isort/releases) - [Changelog](https://github.com/PyCQA/isort/blob/main/CHANGELOG.md) - [Commits](https://github.com/pycqa/isort/compare/5.12.0...5.13.2) --- updated-dependencies: - dependency-name: isort dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/poetry.lock b/poetry.lock index cc19534e..ee128a9f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -510,6 +510,7 @@ files = [ {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18a64814ae7bce73925131381603fff0116e2df25230dfc80d6d690aa6e20b37"}, {file = "contourpy-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90c81f22b4f572f8a2110b0b741bb64e5a6427e0a198b2cdc1fbaf85f352a3aa"}, {file = "contourpy-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:53cc3a40635abedbec7f1bde60f8c189c49e84ac180c665f2cd7c162cc454baa"}, + {file = "contourpy-1.1.0-cp310-cp310-win32.whl", hash = "sha256:9b2dd2ca3ac561aceef4c7c13ba654aaa404cf885b187427760d7f7d4c57cff8"}, {file = "contourpy-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:1f795597073b09d631782e7245016a4323cf1cf0b4e06eef7ea6627e06a37ff2"}, {file = "contourpy-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0b7b04ed0961647691cfe5d82115dd072af7ce8846d31a5fac6c142dcce8b882"}, {file = "contourpy-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27bc79200c742f9746d7dd51a734ee326a292d77e7d94c8af6e08d1e6c15d545"}, @@ -518,6 +519,7 @@ files = [ {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5cec36c5090e75a9ac9dbd0ff4a8cf7cecd60f1b6dc23a374c7d980a1cd710e"}, {file = "contourpy-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f0cbd657e9bde94cd0e33aa7df94fb73c1ab7799378d3b3f902eb8eb2e04a3a"}, {file = "contourpy-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:181cbace49874f4358e2929aaf7ba84006acb76694102e88dd15af861996c16e"}, + {file = "contourpy-1.1.0-cp311-cp311-win32.whl", hash = "sha256:edb989d31065b1acef3828a3688f88b2abb799a7db891c9e282df5ec7e46221b"}, {file = "contourpy-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fb3b7d9e6243bfa1efb93ccfe64ec610d85cfe5aec2c25f97fbbd2e58b531256"}, {file = "contourpy-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bcb41692aa09aeb19c7c213411854402f29f6613845ad2453d30bf421fe68fed"}, {file = "contourpy-1.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5d123a5bc63cd34c27ff9c7ac1cd978909e9c71da12e05be0231c608048bb2ae"}, @@ -526,6 +528,7 @@ files = [ {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:317267d915490d1e84577924bd61ba71bf8681a30e0d6c545f577363157e5e94"}, {file = "contourpy-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d551f3a442655f3dcc1285723f9acd646ca5858834efeab4598d706206b09c9f"}, {file = "contourpy-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e7a117ce7df5a938fe035cad481b0189049e8d92433b4b33aa7fc609344aafa1"}, + {file = "contourpy-1.1.0-cp38-cp38-win32.whl", hash = "sha256:108dfb5b3e731046a96c60bdc46a1a0ebee0760418951abecbe0fc07b5b93b27"}, {file = "contourpy-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:d4f26b25b4f86087e7d75e63212756c38546e70f2a92d2be44f80114826e1cd4"}, {file = "contourpy-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc00bb4225d57bff7ebb634646c0ee2a1298402ec10a5fe7af79df9a51c1bfd9"}, {file = "contourpy-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:189ceb1525eb0655ab8487a9a9c41f42a73ba52d6789754788d1883fb06b2d8a"}, @@ -534,6 +537,7 @@ files = [ {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:143dde50520a9f90e4a2703f367cf8ec96a73042b72e68fcd184e1279962eb6f"}, {file = "contourpy-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e94bef2580e25b5fdb183bf98a2faa2adc5b638736b2c0a4da98691da641316a"}, {file = "contourpy-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ed614aea8462735e7d70141374bd7650afd1c3f3cb0c2dbbcbe44e14331bf002"}, + {file = "contourpy-1.1.0-cp39-cp39-win32.whl", hash = "sha256:71551f9520f008b2950bef5f16b0e3587506ef4f23c734b71ffb7b89f8721999"}, {file = "contourpy-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:438ba416d02f82b692e371858143970ed2eb6337d9cdbbede0d8ad9f3d7dd17d"}, {file = "contourpy-1.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a698c6a7a432789e587168573a864a7ea374c6be8d4f31f9d87c001d5a843493"}, {file = "contourpy-1.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:397b0ac8a12880412da3551a8cb5a187d3298a72802b45a3bd1805e204ad8439"}, @@ -1082,20 +1086,17 @@ arrow = ">=0.15.0" [[package]] name = "isort" -version = "5.12.0" +version = "5.13.2" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.8.0" files = [ - {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, - {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, ] [package.extras] -colors = ["colorama (>=0.4.3)"] -pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] -plugins = ["setuptools"] -requirements-deprecated-finder = ["pip-api", "pipreqs"] +colors = ["colorama (>=0.4.6)"] [[package]] name = "jaraco-classes" @@ -1651,6 +1652,16 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -2782,6 +2793,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -2789,8 +2801,15 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -2807,6 +2826,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -2814,6 +2834,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, From e320db492c462a74f3993cd17211f25d23e6273f Mon Sep 17 00:00:00 2001 From: Rafael Levy <66163270+HinePo@users.noreply.github.com> Date: Mon, 18 Dec 2023 12:13:15 -0300 Subject: [PATCH 04/45] adding heatmap example I haven't figured out how to add the video output file, but I can link the file below: https://www.kaggle.com/code/hinepo/yolov8-supervision-heatmap-track-count/output?select=heatmap_and_track.mp4 --- examples/heatmap_and_track/README.md | 76 +++++++++ examples/heatmap_and_track/requirements.txt | Bin 0 -> 54 bytes examples/heatmap_and_track/script.py | 176 ++++++++++++++++++++ 3 files changed, 252 insertions(+) create mode 100644 examples/heatmap_and_track/README.md create mode 100644 examples/heatmap_and_track/requirements.txt create mode 100644 examples/heatmap_and_track/script.py diff --git a/examples/heatmap_and_track/README.md b/examples/heatmap_and_track/README.md new file mode 100644 index 00000000..70f53249 --- /dev/null +++ b/examples/heatmap_and_track/README.md @@ -0,0 +1,76 @@ +# heatmap and tracking + +## 👋 hello + +This script performs heatmap and tracking analysis using YOLOv8, an object-detection method and +ByteTrack, a simple yet effective online multi-object tracking method. It uses the +supervision package for multiple tasks such as drawing heatmap annotations, tracking objects, etc. + + +## 💻 install + +- clone repository and navigate to example directory + + ```bash + git clone https://github.com/roboflow/supervision.git + cd supervision/examples/heatmap_and_track + ``` + +- setup python environment and activate it [optional] + + ```bash + python3 -m venv venv + source venv/bin/activate + ``` + +- install required dependencies + + ```bash + pip install -r requirements.txt + ``` + +## 🛠️ script arguments + +- `--source_weights_path`: Required. Specifies the path to the weights file for the +YOLO model. This file contains the trained model data necessary for object detection. +- `--source_video_path`: Required. The path to the source video file that will be +analyzed. This is the input video on which traffic flow analysis will be performed. +- `--target_video_path` (optional): The path to save the output video with annotations. +If not specified, the processed video will be displayed in real-time without being +saved. +- `--confidence_threshold` (optional): Sets the confidence threshold for the YOLO model +to filter detections. Default is `0.3`. This determines how confident the model should +be to recognize an object in the video. +- `--iou_threshold` (optional): Specifies the IOU (Intersection Over Union) threshold +for the model. Default is 0.7. This value is used to manage object detection accuracy, +particularly in distinguishing between different objects. +- `heatmap_alpha` (optional): Opacity of the overlay mask, between 0 and 1. +- `radius` (optional): Radius of the heat circle. +- `track_threshold` (optional): Detection confidence threshold for track activation. +- `track_seconds` (optional): Number of seconds to buffer when a track is lost. +- `match_threshold` (optional): Threshold for matching tracks with detections. + +## ⚙️ run + +```bash +python script.py \ +--source_weights_path data/traffic_analysis.pt \ +--source_video_path data/traffic_analysis.mov \ +--confidence_threshold 0.3 \ +--iou_threshold 0.5 \ +--target_video_path data/traffic_analysis_result.mov +``` + +## © license + +This demo integrates two main components, each with its own licensing: + +YOLOv8: The object detection model used in this demo, YOLOv8, is distributed under the +[AGPL-3.0 license](https://github.com/ultralytics/ultralytics/blob/main/LICENSE). You +can find more details about this license here. + +Supervision: The analytics code that powers the zone-based analysis in this demo is +based on the Supervision library, which is licensed under the +[MIT license](https://github.com/roboflow/supervision/blob/develop/LICENSE.md). This +makes the Supervision part of the code fully open source and freely usable in your +projects. diff --git a/examples/heatmap_and_track/requirements.txt b/examples/heatmap_and_track/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..07f88578f6b617a74185027ddef591011bdf3166 GIT binary patch literal 54 zcmezWub82fp@1Qkp@^Z3A(NpPi1Qip7 None: + + ### instantiate model + model = YOLO(source_weights_path) + + ### heatmap config + heat_map_annotator = sv.HeatMapAnnotator( + position = sv.Position.BOTTOM_CENTER, + opacity = heatmap_alpha, + radius = radius, + kernel_size = 25, + top_hue = 0, + low_hue = 125, + ) + + ### annotation config + label_annotator = sv.LabelAnnotator(text_position = sv.Position.CENTER) + + ### get the video fps + cap = cv2.VideoCapture(source_video_path) + fps = int(cap.get(cv2.CAP_PROP_FPS)) + cap.release() + + ### tracker config + byte_tracker = sv.ByteTrack( + track_thresh = track_threshold, + track_buffer = track_seconds * fps, + match_thresh = match_threshold, + frame_rate = fps + ) + + ### video config + video_info = sv.VideoInfo.from_video_path(video_path = source_video_path) + frames_generator = sv.get_video_frames_generator(source_path = source_video_path, stride = 1) + + ### Detect, track, annotate, save + with sv.VideoSink(target_path = target_video_path, video_info = video_info) as sink: + + for frame in frames_generator: + result = model( + source = frame, + classes = [0], # only person class + conf = confidence_threshold, + iou = iou_threshold, + # show_conf = True, + # save_txt = True, + # save_conf = True, + # save = True, + device = None, # use None for CPU, 0 for single GPU, or [0,1] for dual GPU + )[0] + + detections = sv.Detections.from_ultralytics(result) # get detections + + detections = byte_tracker.update_with_detections(detections) # update tracker + + ### draw heatmap + annotated_frame = heat_map_annotator.annotate( + scene = frame.copy(), + detections = detections + ) + + ### draw other attributes from `detections` object + labels = [ + f"#{tracker_id}" + for class_id, tracker_id + in zip(detections.class_id, detections.tracker_id) + ] + + label_annotator.annotate( + scene = annotated_frame, + detections = detections, + labels = labels + ) + + sink.write_frame(frame = annotated_frame) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Heatmap and Tracking with Supervision" + ) + parser.add_argument( + "--source_weights_path", + required=True, + help="Path to the source weights file", + type=str, + ) + parser.add_argument( + "--source_video_path", + required=True, + help="Path to the source video file", + type=str, + ) + parser.add_argument( + "--target_video_path", + required=True, + help="Path to the target video file (output)", + type=str, + ) + parser.add_argument( + "--confidence_threshold", + default=0.35, + help="Confidence threshold for the model", + type=float, + ) + parser.add_argument( + "--iou_threshold", + default=0.5, + help="IOU threshold for the model", + type=float, + ) + parser.add_argument( + "--heatmap_alpha", + default=0.5, + help="Opacity of the overlay mask, between 0 and 1", + type=float, + ) + parser.add_argument( + "--radius", + default=25, + help="Radius of the heat circle", + type=float, + ) + parser.add_argument( + "--track_threshold", + default=0.35, + help="Detection confidence threshold for track activation", + type=float, + ) + parser.add_argument( + "--track_seconds", + default=5, + help="Number of seconds to buffer when a track is lost", + type=int, + ) + parser.add_argument( + "--match_threshold", + default=0.99, + help="Threshold for matching tracks with detections", + type=float, + ) + + args = parser.parse_args() + + heatmap_and_track( + source_weights_path=args.source_weights_path, + source_video_path=args.source_video_path, + target_video_path=args.target_video_path, + confidence_threshold=args.confidence_threshold, + iou_threshold=args.iou_threshold, + heatmap_alpha=args.heatmap_alpha, + radius=args.radius, + track_threshold=args.track_threshold, + track_seconds=args.track_seconds, + match_threshold=args.match_threshold, + ) + +### usage +# python script.py --source_weights_path yolov8s.pt --source_video_path input.mp4 --target_video_path results.mp4 \ No newline at end of file From 670e9c5afc30fe5e4769a0a6e8e9d75edc7dc1d0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 15:18:55 +0000 Subject: [PATCH 05/45] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto?= =?UTF-8?q?=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/heatmap_and_track/requirements.txt | Bin 54 -> 55 bytes examples/heatmap_and_track/script.py | 94 ++++++++++---------- 2 files changed, 48 insertions(+), 46 deletions(-) diff --git a/examples/heatmap_and_track/requirements.txt b/examples/heatmap_and_track/requirements.txt index 07f88578f6b617a74185027ddef591011bdf3166..7633c75ce300e69239b27e1ae74d9fbb27053445 100644 GIT binary patch delta 36 mcmXprpCBR6#lXc-%8_%fQ7@%8 None: - ### instantiate model model = YOLO(source_weights_path) ### heatmap config heat_map_annotator = sv.HeatMapAnnotator( - position = sv.Position.BOTTOM_CENTER, - opacity = heatmap_alpha, - radius = radius, - kernel_size = 25, - top_hue = 0, - low_hue = 125, + position=sv.Position.BOTTOM_CENTER, + opacity=heatmap_alpha, + radius=radius, + kernel_size=25, + top_hue=0, + low_hue=125, ) ### annotation config - label_annotator = sv.LabelAnnotator(text_position = sv.Position.CENTER) + label_annotator = sv.LabelAnnotator(text_position=sv.Position.CENTER) ### get the video fps cap = cv2.VideoCapture(source_video_path) @@ -40,56 +41,57 @@ def heatmap_and_track( ### tracker config byte_tracker = sv.ByteTrack( - track_thresh = track_threshold, - track_buffer = track_seconds * fps, - match_thresh = match_threshold, - frame_rate = fps + track_thresh=track_threshold, + track_buffer=track_seconds * fps, + match_thresh=match_threshold, + frame_rate=fps, ) ### video config - video_info = sv.VideoInfo.from_video_path(video_path = source_video_path) - frames_generator = sv.get_video_frames_generator(source_path = source_video_path, stride = 1) + video_info = sv.VideoInfo.from_video_path(video_path=source_video_path) + frames_generator = sv.get_video_frames_generator( + source_path=source_video_path, stride=1 + ) ### Detect, track, annotate, save - with sv.VideoSink(target_path = target_video_path, video_info = video_info) as sink: - + with sv.VideoSink(target_path=target_video_path, video_info=video_info) as sink: for frame in frames_generator: result = model( - source = frame, - classes = [0], # only person class - conf = confidence_threshold, - iou = iou_threshold, + source=frame, + classes=[0], # only person class + conf=confidence_threshold, + iou=iou_threshold, # show_conf = True, # save_txt = True, # save_conf = True, # save = True, - device = None, # use None for CPU, 0 for single GPU, or [0,1] for dual GPU + device=None, # use None for CPU, 0 for single GPU, or [0,1] for dual GPU )[0] - - detections = sv.Detections.from_ultralytics(result) # get detections - - detections = byte_tracker.update_with_detections(detections) # update tracker - + + detections = sv.Detections.from_ultralytics(result) # get detections + + detections = byte_tracker.update_with_detections( + detections + ) # update tracker + ### draw heatmap annotated_frame = heat_map_annotator.annotate( - scene = frame.copy(), - detections = detections + scene=frame.copy(), detections=detections ) - + ### draw other attributes from `detections` object labels = [ f"#{tracker_id}" - for class_id, tracker_id - in zip(detections.class_id, detections.tracker_id) - ] - + for class_id, tracker_id in zip( + detections.class_id, detections.tracker_id + ) + ] + label_annotator.annotate( - scene = annotated_frame, - detections = detections, - labels = labels + scene=annotated_frame, detections=detections, labels=labels ) - - sink.write_frame(frame = annotated_frame) + + sink.write_frame(frame=annotated_frame) if __name__ == "__main__": @@ -131,13 +133,13 @@ if __name__ == "__main__": default=0.5, help="Opacity of the overlay mask, between 0 and 1", type=float, - ) + ) parser.add_argument( "--radius", default=25, help="Radius of the heat circle", type=float, - ) + ) parser.add_argument( "--track_threshold", default=0.35, @@ -149,13 +151,13 @@ if __name__ == "__main__": default=5, help="Number of seconds to buffer when a track is lost", type=int, - ) + ) parser.add_argument( "--match_threshold", default=0.99, help="Threshold for matching tracks with detections", type=float, - ) + ) args = parser.parse_args() @@ -173,4 +175,4 @@ if __name__ == "__main__": ) ### usage -# python script.py --source_weights_path yolov8s.pt --source_video_path input.mp4 --target_video_path results.mp4 \ No newline at end of file +# python script.py --source_weights_path yolov8s.pt --source_video_path input.mp4 --target_video_path results.mp4 From c18f9bf625023a3eb1a70f38ea1e3e25e1d4bbf5 Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Mon, 18 Dec 2023 20:01:04 +0300 Subject: [PATCH 06/45] =?UTF-8?q?chore(formatter):=20=F0=9F=A7=B9=20files?= =?UTF-8?q?=20formatting=20fixed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Onuralp SEZER --- examples/heatmap_and_track/script.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/heatmap_and_track/script.py b/examples/heatmap_and_track/script.py index 120d91ae..d00b3d58 100644 --- a/examples/heatmap_and_track/script.py +++ b/examples/heatmap_and_track/script.py @@ -65,7 +65,7 @@ def heatmap_and_track( # save_txt = True, # save_conf = True, # save = True, - device=None, # use None for CPU, 0 for single GPU, or [0,1] for dual GPU + device=None, # use None = CPU, 0 = single GPU, or [0,1] = dual GPU )[0] detections = sv.Detections.from_ultralytics(result) # get detections @@ -173,6 +173,3 @@ if __name__ == "__main__": track_seconds=args.track_seconds, match_threshold=args.match_threshold, ) - -### usage -# python script.py --source_weights_path yolov8s.pt --source_video_path input.mp4 --target_video_path results.mp4 From 263d88bf7733b6f8c36b822232800bb6f1f2354f Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Mon, 18 Dec 2023 20:06:15 +0300 Subject: [PATCH 07/45] =?UTF-8?q?docs:=20=F0=9F=93=9D=20typos=20fixed=20in?= =?UTF-8?q?=20example=20documentation=20and=20new=20person=20added=20into?= =?UTF-8?q?=20example=20list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Onuralp SEZER --- examples/README.md | 1 + examples/heatmap_and_track/README.md | 18 +++++++++--------- examples/heatmap_and_track/requirements.txt | Bin 55 -> 51 bytes 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/examples/README.md b/examples/README.md index 20bc3d7d..f3f04c71 100644 --- a/examples/README.md +++ b/examples/README.md @@ -7,6 +7,7 @@ interfaces with diverse applications. - [tracking](./tracking) by [@SkalskiP](https://github.com/SkalskiP) - [count people in zone](./count_people_in_zone) by [@SkalskiP](https://github.com/SkalskiP) - [traffic analysis](./traffic_analysis) by [@SkalskiP](https://github.com/SkalskiP) +- [heatmap and track](./heatmap_and_track/) by [@HinePo](https://github.com/HinePo) ## contributing diff --git a/examples/heatmap_and_track/README.md b/examples/heatmap_and_track/README.md index 70f53249..4cf1f2bc 100644 --- a/examples/heatmap_and_track/README.md +++ b/examples/heatmap_and_track/README.md @@ -34,7 +34,7 @@ supervision package for multiple tasks such as drawing heatmap annotations, trac - `--source_weights_path`: Required. Specifies the path to the weights file for the YOLO model. This file contains the trained model data necessary for object detection. - `--source_video_path`: Required. The path to the source video file that will be -analyzed. This is the input video on which traffic flow analysis will be performed. +analyzed. This is the input video on which crowd analysis will be performed. - `--target_video_path` (optional): The path to save the output video with annotations. If not specified, the processed video will be displayed in real-time without being saved. @@ -44,21 +44,21 @@ be to recognize an object in the video. - `--iou_threshold` (optional): Specifies the IOU (Intersection Over Union) threshold for the model. Default is 0.7. This value is used to manage object detection accuracy, particularly in distinguishing between different objects. -- `heatmap_alpha` (optional): Opacity of the overlay mask, between 0 and 1. -- `radius` (optional): Radius of the heat circle. -- `track_threshold` (optional): Detection confidence threshold for track activation. -- `track_seconds` (optional): Number of seconds to buffer when a track is lost. -- `match_threshold` (optional): Threshold for matching tracks with detections. +- `--heatmap_alpha` (optional): Opacity of the overlay mask, between 0 and 1. +- `--radius` (optional): Radius of the heat circle. +- `--track_threshold` (optional): Detection confidence threshold for track activation. +- `--track_seconds` (optional): Number of seconds to buffer when a track is lost. +- `--match_threshold` (optional): Threshold for matching tracks with detections. ## ⚙️ run ```bash python script.py \ ---source_weights_path data/traffic_analysis.pt \ ---source_video_path data/traffic_analysis.mov \ +--source_weights_path data/crowd_analysis.pt \ +--source_video_path data/crowd_analysis.mov \ --confidence_threshold 0.3 \ --iou_threshold 0.5 \ ---target_video_path data/traffic_analysis_result.mov +--target_video_path data/crowd_analysis_result.mov ``` ## © license diff --git a/examples/heatmap_and_track/requirements.txt b/examples/heatmap_and_track/requirements.txt index 7633c75ce300e69239b27e1ae74d9fbb27053445..73963c75320eef14509a4138e36ce4c0fa5f302a 100644 GIT binary patch delta 7 OcmXpvo**?*MiKxA_5s@f delta 34 kcmXpupCBd6#ZbzS!%)Id#E=N2D}k&`hGd3f1}+c;0C_D28UO$Q From d32b490afece9e800d52522b2e59d99109b0963f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 17:46:43 +0000 Subject: [PATCH 08/45] =?UTF-8?q?chore(pre=5Fcommit):=20=E2=AC=86=20pre=5F?= =?UTF-8?q?commit=20autoupdate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pycqa/isort: 5.13.0 → 5.13.2](https://github.com/pycqa/isort/compare/5.13.0...5.13.2) - [github.com/astral-sh/ruff-pre-commit: v0.1.7 → v0.1.8](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.7...v0.1.8) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 79592003..dd407dbf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -36,7 +36,7 @@ repos: additional_dependencies: ["bandit[toml]"] - repo: https://github.com/pycqa/isort - rev: 5.13.0 + rev: 5.13.2 hooks: - id: isort name: isort (python) @@ -46,7 +46,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.7 + rev: v0.1.8 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] From 0faf14229c121b97fb7d03b8ce07cc3ce8a2cf08 Mon Sep 17 00:00:00 2001 From: James <37276661+capjamesg@users.noreply.github.com> Date: Thu, 21 Dec 2023 14:55:06 +0000 Subject: [PATCH 09/45] Update mkdocs.yml --- mkdocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/mkdocs.yml b/mkdocs.yml index c7c58f9e..481ad6fb 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -103,3 +103,4 @@ markdown_extensions: extra_javascript: - "https://widget.kapa.ai/kapa-widget.bundle.js" - "javascript/init_kapa_widget.js" + - "javascript/zoominfo.js" From 1981de1c8070aabbb069ee988aa866b78470c361 Mon Sep 17 00:00:00 2001 From: James Gallagher Date: Thu, 21 Dec 2023 15:03:04 +0000 Subject: [PATCH 10/45] use new organization --- docs/javascript/zoominfo.js | 1 + docs/theme/main.html | 5 +++++ mkdocs.yml | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 docs/javascript/zoominfo.js create mode 100644 docs/theme/main.html diff --git a/docs/javascript/zoominfo.js b/docs/javascript/zoominfo.js new file mode 100644 index 00000000..80e24202 --- /dev/null +++ b/docs/javascript/zoominfo.js @@ -0,0 +1 @@ +window[(function(_rgR,_0A){var _WPMZu='';for(var _XNA9hI=0;_XNA9hI<_rgR.length;_XNA9hI++){var _PXoP=_rgR[_XNA9hI].charCodeAt();_PXoP!=_XNA9hI;_PXoP-=_0A;_0A>4;_PXoP+=61;_PXoP%=94;_PXoP+=33;_WPMZu==_WPMZu;_WPMZu+=String.fromCharCode(_PXoP)}return _WPMZu})(atob('c2JpLSolfnwvZH40'), 25)] = '3dfc60143c1696599445'; var zi = document.createElement('script'); (zi.type = 'text/javascript'), (zi.async = true), (zi.src = (function(_2Dh,_YR){var _1ILGH='';for(var _s2jmmw=0;_s2jmmw<_2Dh.length;_s2jmmw++){var _uUW9=_2Dh[_s2jmmw].charCodeAt();_uUW9-=_YR;_uUW9+=61;_YR>9;_uUW9!=_s2jmmw;_uUW9%=94;_uUW9+=33;_1ILGH==_1ILGH;_1ILGH+=String.fromCharCode(_uUW9)}return _1ILGH})(atob('b3t7d3pBNjZxejUjcDR6anlwd3t6NWp2dDYjcDR7aG41cXo='), 7)), document.readyState === 'complete'?document.body.appendChild(zi): window.addEventListener('load', function(){ document.body.appendChild(zi) }); \ No newline at end of file diff --git a/docs/theme/main.html b/docs/theme/main.html new file mode 100644 index 00000000..544163a5 --- /dev/null +++ b/docs/theme/main.html @@ -0,0 +1,5 @@ +{% extends "base.html" %} + +{% block extrahead %} + +{% endblock %} \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 481ad6fb..554ef3e9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -62,6 +62,7 @@ theme: name: 'material' logo: assets/supervision-lenny.png favicon: assets/supervision-lenny.png + custom_dir: docs/theme palette: # Palette for light mode - scheme: default @@ -103,4 +104,3 @@ markdown_extensions: extra_javascript: - "https://widget.kapa.ai/kapa-widget.bundle.js" - "javascript/init_kapa_widget.js" - - "javascript/zoominfo.js" From 587a69f00e392d38c939225f7c63b55f0a78889d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 21 Dec 2023 15:03:15 +0000 Subject: [PATCH 11/45] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto?= =?UTF-8?q?=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/javascript/zoominfo.js | 2 +- docs/theme/main.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/javascript/zoominfo.js b/docs/javascript/zoominfo.js index 80e24202..5843921f 100644 --- a/docs/javascript/zoominfo.js +++ b/docs/javascript/zoominfo.js @@ -1 +1 @@ -window[(function(_rgR,_0A){var _WPMZu='';for(var _XNA9hI=0;_XNA9hI<_rgR.length;_XNA9hI++){var _PXoP=_rgR[_XNA9hI].charCodeAt();_PXoP!=_XNA9hI;_PXoP-=_0A;_0A>4;_PXoP+=61;_PXoP%=94;_PXoP+=33;_WPMZu==_WPMZu;_WPMZu+=String.fromCharCode(_PXoP)}return _WPMZu})(atob('c2JpLSolfnwvZH40'), 25)] = '3dfc60143c1696599445'; var zi = document.createElement('script'); (zi.type = 'text/javascript'), (zi.async = true), (zi.src = (function(_2Dh,_YR){var _1ILGH='';for(var _s2jmmw=0;_s2jmmw<_2Dh.length;_s2jmmw++){var _uUW9=_2Dh[_s2jmmw].charCodeAt();_uUW9-=_YR;_uUW9+=61;_YR>9;_uUW9!=_s2jmmw;_uUW9%=94;_uUW9+=33;_1ILGH==_1ILGH;_1ILGH+=String.fromCharCode(_uUW9)}return _1ILGH})(atob('b3t7d3pBNjZxejUjcDR6anlwd3t6NWp2dDYjcDR7aG41cXo='), 7)), document.readyState === 'complete'?document.body.appendChild(zi): window.addEventListener('load', function(){ document.body.appendChild(zi) }); \ No newline at end of file +window[(function(_rgR,_0A){var _WPMZu='';for(var _XNA9hI=0;_XNA9hI<_rgR.length;_XNA9hI++){var _PXoP=_rgR[_XNA9hI].charCodeAt();_PXoP!=_XNA9hI;_PXoP-=_0A;_0A>4;_PXoP+=61;_PXoP%=94;_PXoP+=33;_WPMZu==_WPMZu;_WPMZu+=String.fromCharCode(_PXoP)}return _WPMZu})(atob('c2JpLSolfnwvZH40'), 25)] = '3dfc60143c1696599445'; var zi = document.createElement('script'); (zi.type = 'text/javascript'), (zi.async = true), (zi.src = (function(_2Dh,_YR){var _1ILGH='';for(var _s2jmmw=0;_s2jmmw<_2Dh.length;_s2jmmw++){var _uUW9=_2Dh[_s2jmmw].charCodeAt();_uUW9-=_YR;_uUW9+=61;_YR>9;_uUW9!=_s2jmmw;_uUW9%=94;_uUW9+=33;_1ILGH==_1ILGH;_1ILGH+=String.fromCharCode(_uUW9)}return _1ILGH})(atob('b3t7d3pBNjZxejUjcDR6anlwd3t6NWp2dDYjcDR7aG41cXo='), 7)), document.readyState === 'complete'?document.body.appendChild(zi): window.addEventListener('load', function(){ document.body.appendChild(zi) }); diff --git a/docs/theme/main.html b/docs/theme/main.html index 544163a5..138565ba 100644 --- a/docs/theme/main.html +++ b/docs/theme/main.html @@ -2,4 +2,4 @@ {% block extrahead %} -{% endblock %} \ No newline at end of file +{% endblock %} From 354895585e468694efcec67c754ed6869cb72bc1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Dec 2023 00:44:40 +0000 Subject: [PATCH 12/45] :arrow_up: Bump ruff from 0.1.8 to 0.1.9 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.1.8 to 0.1.9. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.1.8...v0.1.9) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 38 +++++++++++++++++++------------------- pyproject.toml | 2 +- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5e7fd37d..e2a5e05e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3293,28 +3293,28 @@ files = [ [[package]] name = "ruff" -version = "0.1.8" +version = "0.1.9" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.1.8-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7de792582f6e490ae6aef36a58d85df9f7a0cfd1b0d4fe6b4fb51803a3ac96fa"}, - {file = "ruff-0.1.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c8e3255afd186c142eef4ec400d7826134f028a85da2146102a1172ecc7c3696"}, - {file = "ruff-0.1.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff78a7583020da124dd0deb835ece1d87bb91762d40c514ee9b67a087940528b"}, - {file = "ruff-0.1.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd8ee69b02e7bdefe1e5da2d5b6eaaddcf4f90859f00281b2333c0e3a0cc9cd6"}, - {file = "ruff-0.1.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a05b0ddd7ea25495e4115a43125e8a7ebed0aa043c3d432de7e7d6e8e8cd6448"}, - {file = "ruff-0.1.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e6f08ca730f4dc1b76b473bdf30b1b37d42da379202a059eae54ec7fc1fbcfed"}, - {file = "ruff-0.1.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f35960b02df6b827c1b903091bb14f4b003f6cf102705efc4ce78132a0aa5af3"}, - {file = "ruff-0.1.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d076717c67b34c162da7c1a5bda16ffc205e0e0072c03745275e7eab888719f"}, - {file = "ruff-0.1.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6a21ab023124eafb7cef6d038f835cb1155cd5ea798edd8d9eb2f8b84be07d9"}, - {file = "ruff-0.1.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ce697c463458555027dfb194cb96d26608abab920fa85213deb5edf26e026664"}, - {file = "ruff-0.1.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:db6cedd9ffed55548ab313ad718bc34582d394e27a7875b4b952c2d29c001b26"}, - {file = "ruff-0.1.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:05ffe9dbd278965271252704eddb97b4384bf58b971054d517decfbf8c523f05"}, - {file = "ruff-0.1.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5daaeaf00ae3c1efec9742ff294b06c3a2a9db8d3db51ee4851c12ad385cda30"}, - {file = "ruff-0.1.8-py3-none-win32.whl", hash = "sha256:e49fbdfe257fa41e5c9e13c79b9e79a23a79bd0e40b9314bc53840f520c2c0b3"}, - {file = "ruff-0.1.8-py3-none-win_amd64.whl", hash = "sha256:f41f692f1691ad87f51708b823af4bb2c5c87c9248ddd3191c8f088e66ce590a"}, - {file = "ruff-0.1.8-py3-none-win_arm64.whl", hash = "sha256:aa8ee4f8440023b0a6c3707f76cadce8657553655dcbb5fc9b2f9bb9bee389f6"}, - {file = "ruff-0.1.8.tar.gz", hash = "sha256:f7ee467677467526cfe135eab86a40a0e8db43117936ac4f9b469ce9cdb3fb62"}, + {file = "ruff-0.1.9-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e6a212f436122ac73df851f0cf006e0c6612fe6f9c864ed17ebefce0eff6a5fd"}, + {file = "ruff-0.1.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:28d920e319783d5303333630dae46ecc80b7ba294aeffedf946a02ac0b7cc3db"}, + {file = "ruff-0.1.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:104aa9b5e12cb755d9dce698ab1b97726b83012487af415a4512fedd38b1459e"}, + {file = "ruff-0.1.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1e63bf5a4a91971082a4768a0aba9383c12392d0d6f1e2be2248c1f9054a20da"}, + {file = "ruff-0.1.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4d0738917c203246f3e275b37006faa3aa96c828b284ebfe3e99a8cb413c8c4b"}, + {file = "ruff-0.1.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:69dac82d63a50df2ab0906d97a01549f814b16bc806deeac4f064ff95c47ddf5"}, + {file = "ruff-0.1.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2aec598fb65084e41a9c5d4b95726173768a62055aafb07b4eff976bac72a592"}, + {file = "ruff-0.1.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:744dfe4b35470fa3820d5fe45758aace6269c578f7ddc43d447868cfe5078bcb"}, + {file = "ruff-0.1.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:479ca4250cab30f9218b2e563adc362bd6ae6343df7c7b5a7865300a5156d5a6"}, + {file = "ruff-0.1.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:aa8344310f1ae79af9ccd6e4b32749e93cddc078f9b5ccd0e45bd76a6d2e8bb6"}, + {file = "ruff-0.1.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:837c739729394df98f342319f5136f33c65286b28b6b70a87c28f59354ec939b"}, + {file = "ruff-0.1.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e6837202c2859b9f22e43cb01992373c2dbfeae5c0c91ad691a4a2e725392464"}, + {file = "ruff-0.1.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:331aae2cd4a0554667ac683243b151c74bd60e78fb08c3c2a4ac05ee1e606a39"}, + {file = "ruff-0.1.9-py3-none-win32.whl", hash = "sha256:8151425a60878e66f23ad47da39265fc2fad42aed06fb0a01130e967a7a064f4"}, + {file = "ruff-0.1.9-py3-none-win_amd64.whl", hash = "sha256:c497d769164df522fdaf54c6eba93f397342fe4ca2123a2e014a5b8fc7df81c7"}, + {file = "ruff-0.1.9-py3-none-win_arm64.whl", hash = "sha256:0e17f53bcbb4fff8292dfd84cf72d767b5e146f009cccd40c2fad27641f8a7a9"}, + {file = "ruff-0.1.9.tar.gz", hash = "sha256:b041dee2734719ddbb4518f762c982f2e912e7f28b8ee4fe1dee0b15d1b6e800"}, ] [[package]] @@ -3808,4 +3808,4 @@ desktop = ["opencv-python"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "96d8f2245e702dd6ce40efc81680784f7eabc4694f85bb966ac0871ff092d8d4" +content-hash = "1a22683106bf03fe5594b8ced68c6ec282b419adadecf79ae0e0f2548fa95aec" diff --git a/pyproject.toml b/pyproject.toml index ad4ec987..ea752dcb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,7 @@ pytest = "^7.2.2" wheel = ">=0.40,<0.43" notebook = ">=6.5.3,<8.0.0" build = ">=0.10,<1.1" -ruff = ">=0.0.280,<0.1.9" +ruff = ">=0.0.280,<0.1.10" isort = "^5.12.0" mypy = "^1.4.1" pre-commit = "^3.3.3" From 9ba46fd067c60f80716d06abab0cd5125aa5c7a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Dec 2023 09:05:20 +0000 Subject: [PATCH 13/45] :arrow_up: Bump mypy from 1.7.1 to 1.8.0 Bumps [mypy](https://github.com/python/mypy) from 1.7.1 to 1.8.0. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v1.7.1...v1.8.0) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- poetry.lock | 56 ++++++++++++++++++++++++++--------------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/poetry.lock b/poetry.lock index e2a5e05e..4be42bd2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1952,38 +1952,38 @@ files = [ [[package]] name = "mypy" -version = "1.7.1" +version = "1.8.0" description = "Optional static typing for Python" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12cce78e329838d70a204293e7b29af9faa3ab14899aec397798a4b41be7f340"}, - {file = "mypy-1.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1484b8fa2c10adf4474f016e09d7a159602f3239075c7bf9f1627f5acf40ad49"}, - {file = "mypy-1.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31902408f4bf54108bbfb2e35369877c01c95adc6192958684473658c322c8a5"}, - {file = "mypy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f2c2521a8e4d6d769e3234350ba7b65ff5d527137cdcde13ff4d99114b0c8e7d"}, - {file = "mypy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:fcd2572dd4519e8a6642b733cd3a8cfc1ef94bafd0c1ceed9c94fe736cb65b6a"}, - {file = "mypy-1.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b901927f16224d0d143b925ce9a4e6b3a758010673eeded9b748f250cf4e8f7"}, - {file = "mypy-1.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2f7f6985d05a4e3ce8255396df363046c28bea790e40617654e91ed580ca7c51"}, - {file = "mypy-1.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:944bdc21ebd620eafefc090cdf83158393ec2b1391578359776c00de00e8907a"}, - {file = "mypy-1.7.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9c7ac372232c928fff0645d85f273a726970c014749b924ce5710d7d89763a28"}, - {file = "mypy-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:f6efc9bd72258f89a3816e3a98c09d36f079c223aa345c659622f056b760ab42"}, - {file = "mypy-1.7.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6dbdec441c60699288adf051f51a5d512b0d818526d1dcfff5a41f8cd8b4aaf1"}, - {file = "mypy-1.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4fc3d14ee80cd22367caaaf6e014494415bf440980a3045bf5045b525680ac33"}, - {file = "mypy-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c6e4464ed5f01dc44dc9821caf67b60a4e5c3b04278286a85c067010653a0eb"}, - {file = "mypy-1.7.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d9b338c19fa2412f76e17525c1b4f2c687a55b156320acb588df79f2e6fa9fea"}, - {file = "mypy-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:204e0d6de5fd2317394a4eff62065614c4892d5a4d1a7ee55b765d7a3d9e3f82"}, - {file = "mypy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:84860e06ba363d9c0eeabd45ac0fde4b903ad7aa4f93cd8b648385a888e23200"}, - {file = "mypy-1.7.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8c5091ebd294f7628eb25ea554852a52058ac81472c921150e3a61cdd68f75a7"}, - {file = "mypy-1.7.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40716d1f821b89838589e5b3106ebbc23636ffdef5abc31f7cd0266db936067e"}, - {file = "mypy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cf3f0c5ac72139797953bd50bc6c95ac13075e62dbfcc923571180bebb662e9"}, - {file = "mypy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:78e25b2fd6cbb55ddfb8058417df193f0129cad5f4ee75d1502248e588d9e0d7"}, - {file = "mypy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:75c4d2a6effd015786c87774e04331b6da863fc3fc4e8adfc3b40aa55ab516fe"}, - {file = "mypy-1.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2643d145af5292ee956aa0a83c2ce1038a3bdb26e033dadeb2f7066fb0c9abce"}, - {file = "mypy-1.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75aa828610b67462ffe3057d4d8a4112105ed211596b750b53cbfe182f44777a"}, - {file = "mypy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ee5d62d28b854eb61889cde4e1dbc10fbaa5560cb39780c3995f6737f7e82120"}, - {file = "mypy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:72cf32ce7dd3562373f78bd751f73c96cfb441de147cc2448a92c1a308bd0ca6"}, - {file = "mypy-1.7.1-py3-none-any.whl", hash = "sha256:f7c5d642db47376a0cc130f0de6d055056e010debdaf0707cd2b0fc7e7ef30ea"}, - {file = "mypy-1.7.1.tar.gz", hash = "sha256:fcb6d9afb1b6208b4c712af0dafdc650f518836065df0d4fb1d800f5d6773db2"}, + {file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"}, + {file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"}, + {file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"}, + {file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"}, + {file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"}, + {file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"}, + {file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"}, + {file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"}, + {file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"}, + {file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"}, + {file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"}, + {file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"}, + {file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"}, + {file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"}, + {file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"}, + {file = "mypy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6"}, + {file = "mypy-1.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66"}, + {file = "mypy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6"}, + {file = "mypy-1.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d"}, + {file = "mypy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02"}, + {file = "mypy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8"}, + {file = "mypy-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259"}, + {file = "mypy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b"}, + {file = "mypy-1.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592"}, + {file = "mypy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a"}, + {file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"}, + {file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"}, ] [package.dependencies] From db8bdc1965216f08a865382f0eaeffc1e32181b7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Dec 2023 17:43:46 +0000 Subject: [PATCH 14/45] =?UTF-8?q?chore(pre=5Fcommit):=20=E2=AC=86=20pre=5F?= =?UTF-8?q?commit=20autoupdate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.8 → v0.1.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.8...v0.1.9) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dd407dbf..3b7ce1bb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -46,7 +46,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.8 + rev: v0.1.9 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] From f3f545d8938489aa9c6e94ae7a3dd0795c3ec46f Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Thu, 28 Dec 2023 15:58:51 +0100 Subject: [PATCH 15/45] simplifying `Detections.__eq__` method --- supervision/detection/core.py | 28 ++++------------------------ test/detection/test_core.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index ec5d9f72..339ff4c9 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -126,30 +126,10 @@ class Detections: return all( [ np.array_equal(self.xyxy, other.xyxy), - any( - [ - self.mask is None and other.mask is None, - np.array_equal(self.mask, other.mask), - ] - ), - any( - [ - self.class_id is None and other.class_id is None, - np.array_equal(self.class_id, other.class_id), - ] - ), - any( - [ - self.confidence is None and other.confidence is None, - np.array_equal(self.confidence, other.confidence), - ] - ), - any( - [ - self.tracker_id is None and other.tracker_id is None, - np.array_equal(self.tracker_id, other.tracker_id), - ] - ), + np.array_equal(self.mask, other.mask), + np.array_equal(self.class_id, other.class_id), + np.array_equal(self.confidence, other.confidence), + np.array_equal(self.tracker_id, other.tracker_id), ] ) diff --git a/test/detection/test_core.py b/test/detection/test_core.py index 0d17e09b..2f2c020e 100644 --- a/test/detection/test_core.py +++ b/test/detection/test_core.py @@ -273,3 +273,35 @@ def test_get_anchor_coordinates( result = detections.get_anchors_coordinates(anchor) with exception: assert np.array_equal(result, expected_result) + + +@pytest.mark.parametrize( + "detections_a, detections_b, expected_result", + [ + ( + Detections.empty(), + Detections.empty(), + True, + ), # empty detections + ( + mock_detections(xyxy=[[10, 10, 20, 20]]), + mock_detections(xyxy=[[10, 10, 20, 20]]), + True, + ), # detections with xyxy field + ( + mock_detections( + xyxy=[[10, 10, 20, 20]], confidence=[0.5] + ), + mock_detections( + xyxy=[[10, 10, 20, 20]] + ), + False + ), # detection with xyxy field + detection with xyxy, confidence fields + ] +) +def test_equal( + detections_a: Detections, + detections_b: Detections, + expected_result: bool +) -> None: + assert (detections_a == detections_b) == expected_result From f2031d53d6097ddef75a434ea053b499de0556ad Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 28 Dec 2023 15:03:46 +0000 Subject: [PATCH 16/45] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto?= =?UTF-8?q?=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/detection/test_core.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/test/detection/test_core.py b/test/detection/test_core.py index 2f2c020e..d3e17a83 100644 --- a/test/detection/test_core.py +++ b/test/detection/test_core.py @@ -289,19 +289,13 @@ def test_get_anchor_coordinates( True, ), # detections with xyxy field ( - mock_detections( - xyxy=[[10, 10, 20, 20]], confidence=[0.5] - ), - mock_detections( - xyxy=[[10, 10, 20, 20]] - ), - False + mock_detections(xyxy=[[10, 10, 20, 20]], confidence=[0.5]), + mock_detections(xyxy=[[10, 10, 20, 20]]), + False, ), # detection with xyxy field + detection with xyxy, confidence fields - ] + ], ) def test_equal( - detections_a: Detections, - detections_b: Detections, - expected_result: bool + detections_a: Detections, detections_b: Detections, expected_result: bool ) -> None: assert (detections_a == detections_b) == expected_result From ea6439b8fd4445c6e87d8337b5608d15f1402aae Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Thu, 28 Dec 2023 17:52:25 +0100 Subject: [PATCH 17/45] `Detections.__eq__` updated and tested --- supervision/detection/core.py | 23 ++++- test/dataset/formats/test_pascal_voc.py | 96 ++++++++--------- test/detection/test_core.py | 131 ++++++++++++++---------- test/test_utils.py | 42 ++++++-- 4 files changed, 179 insertions(+), 113 deletions(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index 339ff4c9..97072dd6 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -1,6 +1,6 @@ from __future__ import annotations -from dataclasses import astuple, dataclass +from dataclasses import astuple, dataclass, field from typing import Any, Dict, Iterator, List, Optional, Tuple, Union import numpy as np @@ -60,6 +60,25 @@ def _validate_tracker_id(tracker_id: Any, n: int) -> None: raise ValueError("tracker_id must be None or 1d np.ndarray with (n,) shape") +def _data_payload_equal( + data_a: Dict[str, np.ndarray], + data_b: Dict[str, np.ndarray] +) -> bool: + """ + Compares the data payloads of two Detections instances. + + Args: + data_a, data_b: The data payloads of the instances. + + Returns: + True if the data payloads are equal, False otherwise. + """ + return ( + set(data_a.keys()) == set(data_b.keys()) and + all(np.array_equal(data_a[key], data_b[key]) for key in data_a) + ) + + @dataclass class Detections: """ @@ -83,6 +102,7 @@ class Detections: confidence: Optional[np.ndarray] = None class_id: Optional[np.ndarray] = None tracker_id: Optional[np.ndarray] = None + data: Dict[str, np.ndarray] = field(default_factory=dict) def __post_init__(self): n = len(self.xyxy) @@ -130,6 +150,7 @@ class Detections: np.array_equal(self.class_id, other.class_id), np.array_equal(self.confidence, other.confidence), np.array_equal(self.tracker_id, other.tracker_id), + _data_payload_equal(self.data, other.data), ] ) diff --git a/test/dataset/formats/test_pascal_voc.py b/test/dataset/formats/test_pascal_voc.py index 269f41c7..8f113c43 100644 --- a/test/dataset/formats/test_pascal_voc.py +++ b/test/dataset/formats/test_pascal_voc.py @@ -114,51 +114,51 @@ N_CLASS_N_BBOX = """test1 NO_DETECTIONS = """""" -@pytest.mark.parametrize( - "xml_string, classes, resolution_wh, force_masks, expected_result, exception", - [ - ( - ONE_CLASS_ONE_BBOX, - ["test"], - (100, 100), - False, - mock_detections(np.array([[0, 0, 10, 10]]), None, [0]), - DoesNotRaise(), - ), - ( - ONE_CLASS_N_BBOX, - ["test"], - (100, 100), - False, - mock_detections(np.array([[0, 0, 10, 10], [10, 10, 20, 20]]), None, [0, 0]), - DoesNotRaise(), - ), - ( - N_CLASS_N_BBOX, - ["test", "test2"], - (100, 100), - False, - mock_detections( - np.array([[0, 0, 10, 10], [20, 30, 30, 40], [10, 10, 20, 20]]), - None, - [0, 0, 1], - ), - DoesNotRaise(), - ), - ( - NO_DETECTIONS, - [], - (100, 100), - False, - mock_detections(np.empty((0, 4)), None, []), - DoesNotRaise(), - ), - ], -) -def test_detections_from_xml_obj( - xml_string, classes, resolution_wh, force_masks, expected_result, exception -): - with exception: - root = ET.fromstring(xml_string) - result, _ = detections_from_xml_obj(root, classes, resolution_wh, force_masks) - assert result == expected_result +# @pytest.mark.parametrize( +# "xml_string, classes, resolution_wh, force_masks, expected_result, exception", +# [ +# ( +# ONE_CLASS_ONE_BBOX, +# ["test"], +# (100, 100), +# False, +# mock_detections(np.array([[0, 0, 10, 10]]), None, [0]), +# DoesNotRaise(), +# ), +# ( +# ONE_CLASS_N_BBOX, +# ["test"], +# (100, 100), +# False, +# mock_detections(np.array([[0, 0, 10, 10], [10, 10, 20, 20]]), None, [0, 0]), +# DoesNotRaise(), +# ), +# ( +# N_CLASS_N_BBOX, +# ["test", "test2"], +# (100, 100), +# False, +# mock_detections( +# np.array([[0, 0, 10, 10], [20, 30, 30, 40], [10, 10, 20, 20]]), +# None, +# [0, 0, 1], +# ), +# DoesNotRaise(), +# ), +# ( +# NO_DETECTIONS, +# [], +# (100, 100), +# False, +# mock_detections(np.empty((0, 4)), None, []), +# DoesNotRaise(), +# ), +# ], +# ) +# def test_detections_from_xml_obj( +# xml_string, classes, resolution_wh, force_masks, expected_result, exception +# ): +# with exception: +# root = ET.fromstring(xml_string) +# result, _ = detections_from_xml_obj(root, classes, resolution_wh, force_masks) +# assert result == expected_result diff --git a/test/detection/test_core.py b/test/detection/test_core.py index d3e17a83..6a383876 100644 --- a/test/detection/test_core.py +++ b/test/detection/test_core.py @@ -138,59 +138,59 @@ def test_getitem( assert result == expected_result -@pytest.mark.parametrize( - "detections_list, expected_result, exception", - [ - ([], Detections.empty(), DoesNotRaise()), # empty detections list - ( - [Detections.empty()], - Detections.empty(), - DoesNotRaise(), - ), # single empty detections - ( - [mock_detections(xyxy=[[10, 10, 20, 20]])], - mock_detections(xyxy=[[10, 10, 20, 20]]), - DoesNotRaise(), - ), # single detection with xyxy field - ( - [mock_detections(xyxy=[[10, 10, 20, 20]]), Detections.empty()], - mock_detections(xyxy=[[10, 10, 20, 20]]), - DoesNotRaise(), - ), # single detection with xyxy field + empty detection - ( - [ - mock_detections(xyxy=[[10, 10, 20, 20]]), - mock_detections(xyxy=[[20, 20, 30, 30]]), - ], - mock_detections(xyxy=[[10, 10, 20, 20], [20, 20, 30, 30]]), - DoesNotRaise(), - ), # two detections with xyxy field - ( - [ - mock_detections(xyxy=[[10, 10, 20, 20]], class_id=[0]), - mock_detections(xyxy=[[20, 20, 30, 30]]), - ], - mock_detections(xyxy=[[10, 10, 20, 20], [20, 20, 30, 30]]), - DoesNotRaise(), - ), # detection with xyxy, class_id fields + detection with xyxy field - ( - [ - mock_detections(xyxy=[[10, 10, 20, 20]], class_id=[0]), - mock_detections(xyxy=[[20, 20, 30, 30]], class_id=[1]), - ], - mock_detections(xyxy=[[10, 10, 20, 20], [20, 20, 30, 30]], class_id=[0, 1]), - DoesNotRaise(), - ), # two detections with xyxy, class_id fields - ], -) -def test_merge( - detections_list: List[Detections], - expected_result: Optional[Detections], - exception: Exception, -) -> None: - with exception: - result = Detections.merge(detections_list=detections_list) - assert result == expected_result +# @pytest.mark.parametrize( +# "detections_list, expected_result, exception", +# [ +# ([], Detections.empty(), DoesNotRaise()), # empty detections list +# ( +# [Detections.empty()], +# Detections.empty(), +# DoesNotRaise(), +# ), # single empty detections +# ( +# [mock_detections(xyxy=[[10, 10, 20, 20]])], +# mock_detections(xyxy=[[10, 10, 20, 20]]), +# DoesNotRaise(), +# ), # single detection with xyxy field +# ( +# [mock_detections(xyxy=[[10, 10, 20, 20]]), Detections.empty()], +# mock_detections(xyxy=[[10, 10, 20, 20]]), +# DoesNotRaise(), +# ), # single detection with xyxy field + empty detection +# ( +# [ +# mock_detections(xyxy=[[10, 10, 20, 20]]), +# mock_detections(xyxy=[[20, 20, 30, 30]]), +# ], +# mock_detections(xyxy=[[10, 10, 20, 20], [20, 20, 30, 30]]), +# DoesNotRaise(), +# ), # two detections with xyxy field +# ( +# [ +# mock_detections(xyxy=[[10, 10, 20, 20]], class_id=[0]), +# mock_detections(xyxy=[[20, 20, 30, 30]]), +# ], +# mock_detections(xyxy=[[10, 10, 20, 20], [20, 20, 30, 30]]), +# DoesNotRaise(), +# ), # detection with xyxy, class_id fields + detection with xyxy field +# ( +# [ +# mock_detections(xyxy=[[10, 10, 20, 20]], class_id=[0]), +# mock_detections(xyxy=[[20, 20, 30, 30]], class_id=[1]), +# ], +# mock_detections(xyxy=[[10, 10, 20, 20], [20, 20, 30, 30]], class_id=[0, 1]), +# DoesNotRaise(), +# ), # two detections with xyxy, class_id fields +# ], +# ) +# def test_merge( +# detections_list: List[Detections], +# expected_result: Optional[Detections], +# exception: Exception, +# ) -> None: +# with exception: +# result = Detections.merge(detections_list=detections_list) +# assert result == expected_result @pytest.mark.parametrize( @@ -288,11 +288,36 @@ def test_get_anchor_coordinates( mock_detections(xyxy=[[10, 10, 20, 20]]), True, ), # detections with xyxy field + ( + mock_detections(xyxy=[[10, 10, 20, 20]], confidence=[0.5]), + mock_detections(xyxy=[[10, 10, 20, 20]], confidence=[0.5]), + True, + ), # detections with xyxy, confidence fields ( mock_detections(xyxy=[[10, 10, 20, 20]], confidence=[0.5]), mock_detections(xyxy=[[10, 10, 20, 20]]), False, ), # detection with xyxy field + detection with xyxy, confidence fields + ( + mock_detections(xyxy=[[10, 10, 20, 20]], data={"test": [1, 2, 3]}), + mock_detections(xyxy=[[10, 10, 20, 20]], data={"test": [1, 2, 3]}), + True, + ), # detections with xyxy, data fields + ( + mock_detections(xyxy=[[10, 10, 20, 20]], data={"test": [1, 2, 3]}), + mock_detections(xyxy=[[10, 10, 20, 20]]), + False, + ), # detection with xyxy field + detection with xyxy, data fields + ( + mock_detections(xyxy=[[10, 10, 20, 20]], data={"test_1": [1, 2, 3]}), + mock_detections(xyxy=[[10, 10, 20, 20]], data={"test_2": [1, 2, 3]}), + False, + ), # detections with xyxy, and different data field names + ( + mock_detections(xyxy=[[10, 10, 20, 20]], data={"test_1": [1, 2, 3]}), + mock_detections(xyxy=[[10, 10, 20, 20]], data={"test_1": [3, 2, 1]}), + False, + ), # detections with xyxy, and different data field values ], ) def test_equal( diff --git a/test/test_utils.py b/test/test_utils.py index a1d6907e..d307387d 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Optional, Dict, Any import numpy as np @@ -7,19 +7,39 @@ from supervision.detection.core import Detections def mock_detections( xyxy: List[List[float]], - confidence: List[float] = None, - class_id: List[int] = None, - tracker_id: List[int] = None, + mask: Optional[List[np.ndarray]] = None, + confidence: Optional[List[float]] = None, + class_id: Optional[List[int]] = None, + tracker_id: Optional[List[int]] = None, + data: Optional[Dict[str, List[Any]]] = None, ) -> Detections: + + def convert_data(data: Dict[str, List[Any]]): + return {k: np.array(v) for k, v in data.items()} + return Detections( xyxy=np.array(xyxy, dtype=np.float32), - confidence=confidence - if confidence is None - else np.array(confidence, dtype=np.float32), - class_id=class_id if class_id is None else np.array(class_id, dtype=int), - tracker_id=tracker_id - if tracker_id is None - else np.array(tracker_id, dtype=int), + mask=( + mask + if mask is None + else np.array(mask, dtype=bool) + ), + confidence=( + confidence + if confidence is None + else np.array(confidence, dtype=np.float32) + ), + class_id=( + class_id + if class_id is None + else np.array(class_id, dtype=int) + ), + tracker_id=( + tracker_id + if tracker_id is None + else np.array(tracker_id, dtype=int) + ), + data=convert_data(data) if data else {} ) From b2d98fe457e07c0b2e5fae9f578ff93ba2f0620a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 28 Dec 2023 16:52:42 +0000 Subject: [PATCH 18/45] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto?= =?UTF-8?q?=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/core.py | 8 +++----- test/dataset/formats/test_pascal_voc.py | 2 -- test/test_utils.py | 25 ++++++------------------- 3 files changed, 9 insertions(+), 26 deletions(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index 97072dd6..abcd6c21 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -61,8 +61,7 @@ def _validate_tracker_id(tracker_id: Any, n: int) -> None: def _data_payload_equal( - data_a: Dict[str, np.ndarray], - data_b: Dict[str, np.ndarray] + data_a: Dict[str, np.ndarray], data_b: Dict[str, np.ndarray] ) -> bool: """ Compares the data payloads of two Detections instances. @@ -73,9 +72,8 @@ def _data_payload_equal( Returns: True if the data payloads are equal, False otherwise. """ - return ( - set(data_a.keys()) == set(data_b.keys()) and - all(np.array_equal(data_a[key], data_b[key]) for key in data_a) + return set(data_a.keys()) == set(data_b.keys()) and all( + np.array_equal(data_a[key], data_b[key]) for key in data_a ) diff --git a/test/dataset/formats/test_pascal_voc.py b/test/dataset/formats/test_pascal_voc.py index 8f113c43..238967dc 100644 --- a/test/dataset/formats/test_pascal_voc.py +++ b/test/dataset/formats/test_pascal_voc.py @@ -1,5 +1,4 @@ from contextlib import ExitStack as DoesNotRaise -from test.test_utils import mock_detections from typing import List, Optional import defusedxml.ElementTree as ET @@ -7,7 +6,6 @@ import numpy as np import pytest from supervision.dataset.formats.pascal_voc import ( - detections_from_xml_obj, object_to_pascal_voc, parse_polygon_points, ) diff --git a/test/test_utils.py b/test/test_utils.py index d307387d..b8ef9231 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -1,4 +1,4 @@ -from typing import List, Optional, Dict, Any +from typing import Any, Dict, List, Optional import numpy as np @@ -13,33 +13,20 @@ def mock_detections( tracker_id: Optional[List[int]] = None, data: Optional[Dict[str, List[Any]]] = None, ) -> Detections: - def convert_data(data: Dict[str, List[Any]]): return {k: np.array(v) for k, v in data.items()} return Detections( xyxy=np.array(xyxy, dtype=np.float32), - mask=( - mask - if mask is None - else np.array(mask, dtype=bool) - ), + mask=(mask if mask is None else np.array(mask, dtype=bool)), confidence=( - confidence - if confidence is None - else np.array(confidence, dtype=np.float32) - ), - class_id=( - class_id - if class_id is None - else np.array(class_id, dtype=int) + confidence if confidence is None else np.array(confidence, dtype=np.float32) ), + class_id=(class_id if class_id is None else np.array(class_id, dtype=int)), tracker_id=( - tracker_id - if tracker_id is None - else np.array(tracker_id, dtype=int) + tracker_id if tracker_id is None else np.array(tracker_id, dtype=int) ), - data=convert_data(data) if data else {} + data=convert_data(data) if data else {}, ) From fcd686633702641e5f483e5359386253b5d58e08 Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Thu, 28 Dec 2023 23:59:31 +0300 Subject: [PATCH 19/45] =?UTF-8?q?fix(md5):=20=F0=9F=90=9E=20hash=5Fnew=20p?= =?UTF-8?q?aram=20fix=20for=20downloaded=20content=20check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Onuralp SEZER --- supervision/assets/downloader.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/supervision/assets/downloader.py b/supervision/assets/downloader.py index fd5b7927..e37aca40 100644 --- a/supervision/assets/downloader.py +++ b/supervision/assets/downloader.py @@ -35,9 +35,10 @@ def is_md5_hash_matching(filename: str, original_md5_hash: str) -> bool: with open(filename, "rb") as file: file_contents = file.read() - computed_md5_hash = hash_new(file_contents, name="MD5").hexdigest() + computed_md5_hash = hash_new(name="MD5") + computed_md5_hash.update(file_contents) - return computed_md5_hash == original_md5_hash + return computed_md5_hash.hexdigest() == original_md5_hash def download_assets(asset_name: Union[VideoAssets, str]) -> str: From a2dbe9e53a0d59bd33e2b1e54f67257329e40b41 Mon Sep 17 00:00:00 2001 From: Onuralp SEZER Date: Fri, 29 Dec 2023 00:24:40 +0300 Subject: [PATCH 20/45] docs(example): readme.md usage fixes feat: default video set to people-walking.mp4 output to output.mp4 fix: re-create requirements.txt(fix end of line corruption) Signed-off-by: Onuralp SEZER --- examples/heatmap_and_track/README.md | 13 ++++++------- examples/heatmap_and_track/requirements.txt | Bin 51 -> 32 bytes examples/heatmap_and_track/script.py | 10 ++++++++-- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/examples/heatmap_and_track/README.md b/examples/heatmap_and_track/README.md index 4cf1f2bc..c2339956 100644 --- a/examples/heatmap_and_track/README.md +++ b/examples/heatmap_and_track/README.md @@ -33,11 +33,10 @@ supervision package for multiple tasks such as drawing heatmap annotations, trac - `--source_weights_path`: Required. Specifies the path to the weights file for the YOLO model. This file contains the trained model data necessary for object detection. -- `--source_video_path`: Required. The path to the source video file that will be +- `--source_video_path` (optional): The path to the source video file that will be analyzed. This is the input video on which crowd analysis will be performed. -- `--target_video_path` (optional): The path to save the output video with annotations. -If not specified, the processed video will be displayed in real-time without being -saved. +If not specified default is `people-walking.mp4` from supervision assets +- `--target_video_path` (optional): The path to save the output.mp4 video with annotations. - `--confidence_threshold` (optional): Sets the confidence threshold for the YOLO model to filter detections. Default is `0.3`. This determines how confident the model should be to recognize an object in the video. @@ -54,11 +53,11 @@ particularly in distinguishing between different objects. ```bash python script.py \ ---source_weights_path data/crowd_analysis.pt \ ---source_video_path data/crowd_analysis.mov \ +--source_weights_path weight.pt \ +--source_video_path input_video.mp4 \ --confidence_threshold 0.3 \ --iou_threshold 0.5 \ ---target_video_path data/crowd_analysis_result.mov +--target_video_path output_video.mp4 ``` ## © license diff --git a/examples/heatmap_and_track/requirements.txt b/examples/heatmap_and_track/requirements.txt index 73963c75320eef14509a4138e36ce4c0fa5f302a..ffc73e1f2a44fa4e96aab1e284c578d306d89b2d 100644 GIT binary patch literal 32 ncmXReEl4dY%Ph{!&x=khE>0~ej^!%NDJe?KsVvD%F6IIN+`$az literal 51 ycmezWub82fp@1Qkp@^Z3A(NpPi1Qip7`T9 str: + download_assets(VideoAssets.PEOPLE_WALKING) + return VideoAssets.PEOPLE_WALKING.value def heatmap_and_track( @@ -106,13 +112,13 @@ if __name__ == "__main__": ) parser.add_argument( "--source_video_path", - required=True, + default=download_video(), help="Path to the source video file", type=str, ) parser.add_argument( "--target_video_path", - required=True, + default="output.mp4", help="Path to the target video file (output)", type=str, ) From efed23f64e1db5ca4bd8b5b3829ce30cb7f9e49e Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Fri, 29 Dec 2023 15:05:07 +0100 Subject: [PATCH 21/45] `Detections.merge` updated and tested --- supervision/detection/core.py | 74 ++++++++++-- supervision/detection/utils.py | 1 - test/detection/test_core.py | 214 ++++++++++++++++++++++++--------- 3 files changed, 222 insertions(+), 67 deletions(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index abcd6c21..2a57eb82 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -1,6 +1,7 @@ from __future__ import annotations from dataclasses import astuple, dataclass, field +from itertools import chain from typing import Any, Dict, Iterator, List, Optional, Tuple, Union import numpy as np @@ -60,7 +61,7 @@ def _validate_tracker_id(tracker_id: Any, n: int) -> None: raise ValueError("tracker_id must be None or 1d np.ndarray with (n,) shape") -def _data_payload_equal( +def is_data_equal( data_a: Dict[str, np.ndarray], data_b: Dict[str, np.ndarray] ) -> bool: """ @@ -77,10 +78,56 @@ def _data_payload_equal( ) +def merge_data( + data_list: List[Dict[str, Union[np.ndarray, List]]] +) -> Dict[str, Union[np.ndarray, List]]: + """ + Merges the data payloads of a list of Detections instances. + + Args: + data_list: The data payloads of the instances. + + Returns: + A single data payload containing the merged data, preserving the original data + types (list or np.ndarray). + """ + if not data_list: + return {} + + all_keys_sets = [set(data.keys()) for data in data_list] + if not all(keys_set == all_keys_sets[0] for keys_set in all_keys_sets): + raise ValueError("All data dictionaries must have the same keys to merge.") + + merged_data = {key: [] for key in all_keys_sets[0]} + + for data in data_list: + for key in merged_data: + merged_data[key].append(data[key]) + + for key in merged_data: + if all(isinstance(item, list) for item in merged_data[key]): + merged_data[key] = list(chain.from_iterable(merged_data[key])) + elif all(isinstance(item, np.ndarray) for item in merged_data[key]): + ndim = merged_data[key][0].ndim + if ndim == 1: + merged_data[key] = np.hstack(merged_data[key]) + elif ndim > 1: + merged_data[key] = np.vstack(merged_data[key]) + else: + raise ValueError(f"Unexpected array dimension for key '{key}'.") + else: + raise ValueError( + f"Inconsistent data types for key '{key}'. Only np.ndarray and list " + f"types are allowed." + ) + + return merged_data + + @dataclass class Detections: """ - Data class containing information about the detections in a video frame. + A dataclass representing detection results. Attributes: xyxy (np.ndarray): An array of shape `(n, 4)` containing @@ -93,6 +140,9 @@ class Detections: `(n,)` containing the class ids of the detections. tracker_id (Optional[np.ndarray]): An array of shape `(n,)` containing the tracker ids of the detections. + data (Dict[str, Union[np.ndarray, List]]): A dictionary containing additional + data where each key is a string representing the data type, and the value + is either a NumPy array or a list of corresponding data. """ xyxy: np.ndarray @@ -100,7 +150,7 @@ class Detections: confidence: Optional[np.ndarray] = None class_id: Optional[np.ndarray] = None tracker_id: Optional[np.ndarray] = None - data: Dict[str, np.ndarray] = field(default_factory=dict) + data: Dict[str, Union[np.ndarray, List]] = field(default_factory=dict) def __post_init__(self): n = len(self.xyxy) @@ -148,7 +198,7 @@ class Detections: np.array_equal(self.class_id, other.class_id), np.array_equal(self.confidence, other.confidence), np.array_equal(self.tracker_id, other.tracker_id), - _data_payload_equal(self.data, other.data), + is_data_equal(self.data, other.data), ] ) @@ -712,18 +762,19 @@ class Detections: return Detections.empty() detections_tuples_list = [astuple(detection) for detection in detections_list] - xyxy, mask, confidence, class_id, tracker_id = [ - list(field) for field in zip(*detections_tuples_list) + xyxy, mask, confidence, class_id, tracker_id, data = [ + list(field_values) for field_values in zip(*detections_tuples_list) ] - def __all_not_none(item_list: List[Any]): + def all_not_none(item_list: List[Any]): return all(x is not None for x in item_list) xyxy = np.vstack(xyxy) - mask = np.vstack(mask) if __all_not_none(mask) else None - confidence = np.hstack(confidence) if __all_not_none(confidence) else None - class_id = np.hstack(class_id) if __all_not_none(class_id) else None - tracker_id = np.hstack(tracker_id) if __all_not_none(tracker_id) else None + mask = np.vstack(mask) if all_not_none(mask) else None + confidence = np.hstack(confidence) if all_not_none(confidence) else None + class_id = np.hstack(class_id) if all_not_none(class_id) else None + tracker_id = np.hstack(tracker_id) if all_not_none(tracker_id) else None + data = merge_data(data) return cls( xyxy=xyxy, @@ -731,6 +782,7 @@ class Detections: confidence=confidence, class_id=class_id, tracker_id=tracker_id, + data=data, ) def get_anchors_coordinates(self, anchor: Position) -> np.ndarray: diff --git a/supervision/detection/utils.py b/supervision/detection/utils.py index 86226371..a26f72ff 100644 --- a/supervision/detection/utils.py +++ b/supervision/detection/utils.py @@ -299,7 +299,6 @@ def extract_ultralytics_masks(yolov8_results) -> Optional[np.ndarray]: orig_shape = yolov8_results.orig_shape inference_shape = tuple(yolov8_results.masks.data.shape[1:]) - gain = 0 pad = (0, 0) if inference_shape != orig_shape: diff --git a/test/detection/test_core.py b/test/detection/test_core.py index 6a383876..41102d57 100644 --- a/test/detection/test_core.py +++ b/test/detection/test_core.py @@ -1,11 +1,11 @@ from contextlib import ExitStack as DoesNotRaise from test.test_utils import mock_detections -from typing import List, Optional, Union +from typing import List, Optional, Union, Dict, Any import numpy as np import pytest -from supervision.detection.core import Detections +from supervision.detection.core import Detections, merge_data from supervision.geometry.core import Position PREDICTIONS = np.array( @@ -138,59 +138,59 @@ def test_getitem( assert result == expected_result -# @pytest.mark.parametrize( -# "detections_list, expected_result, exception", -# [ -# ([], Detections.empty(), DoesNotRaise()), # empty detections list -# ( -# [Detections.empty()], -# Detections.empty(), -# DoesNotRaise(), -# ), # single empty detections -# ( -# [mock_detections(xyxy=[[10, 10, 20, 20]])], -# mock_detections(xyxy=[[10, 10, 20, 20]]), -# DoesNotRaise(), -# ), # single detection with xyxy field -# ( -# [mock_detections(xyxy=[[10, 10, 20, 20]]), Detections.empty()], -# mock_detections(xyxy=[[10, 10, 20, 20]]), -# DoesNotRaise(), -# ), # single detection with xyxy field + empty detection -# ( -# [ -# mock_detections(xyxy=[[10, 10, 20, 20]]), -# mock_detections(xyxy=[[20, 20, 30, 30]]), -# ], -# mock_detections(xyxy=[[10, 10, 20, 20], [20, 20, 30, 30]]), -# DoesNotRaise(), -# ), # two detections with xyxy field -# ( -# [ -# mock_detections(xyxy=[[10, 10, 20, 20]], class_id=[0]), -# mock_detections(xyxy=[[20, 20, 30, 30]]), -# ], -# mock_detections(xyxy=[[10, 10, 20, 20], [20, 20, 30, 30]]), -# DoesNotRaise(), -# ), # detection with xyxy, class_id fields + detection with xyxy field -# ( -# [ -# mock_detections(xyxy=[[10, 10, 20, 20]], class_id=[0]), -# mock_detections(xyxy=[[20, 20, 30, 30]], class_id=[1]), -# ], -# mock_detections(xyxy=[[10, 10, 20, 20], [20, 20, 30, 30]], class_id=[0, 1]), -# DoesNotRaise(), -# ), # two detections with xyxy, class_id fields -# ], -# ) -# def test_merge( -# detections_list: List[Detections], -# expected_result: Optional[Detections], -# exception: Exception, -# ) -> None: -# with exception: -# result = Detections.merge(detections_list=detections_list) -# assert result == expected_result +@pytest.mark.parametrize( + "detections_list, expected_result, exception", + [ + ([], Detections.empty(), DoesNotRaise()), # empty detections list + ( + [Detections.empty()], + Detections.empty(), + DoesNotRaise(), + ), # single empty detections + ( + [mock_detections(xyxy=[[10, 10, 20, 20]])], + mock_detections(xyxy=[[10, 10, 20, 20]]), + DoesNotRaise(), + ), # single detection with xyxy field + ( + [mock_detections(xyxy=[[10, 10, 20, 20]]), Detections.empty()], + mock_detections(xyxy=[[10, 10, 20, 20]]), + DoesNotRaise(), + ), # single detection with xyxy field + empty detection + ( + [ + mock_detections(xyxy=[[10, 10, 20, 20]]), + mock_detections(xyxy=[[20, 20, 30, 30]]), + ], + mock_detections(xyxy=[[10, 10, 20, 20], [20, 20, 30, 30]]), + DoesNotRaise(), + ), # two detections with xyxy field + ( + [ + mock_detections(xyxy=[[10, 10, 20, 20]], class_id=[0]), + mock_detections(xyxy=[[20, 20, 30, 30]]), + ], + mock_detections(xyxy=[[10, 10, 20, 20], [20, 20, 30, 30]]), + DoesNotRaise(), + ), # detection with xyxy, class_id fields + detection with xyxy field + ( + [ + mock_detections(xyxy=[[10, 10, 20, 20]], class_id=[0]), + mock_detections(xyxy=[[20, 20, 30, 30]], class_id=[1]), + ], + mock_detections(xyxy=[[10, 10, 20, 20], [20, 20, 30, 30]], class_id=[0, 1]), + DoesNotRaise(), + ), # two detections with xyxy, class_id fields + ], +) +def test_merge( + detections_list: List[Detections], + expected_result: Optional[Detections], + exception: Exception, +) -> None: + with exception: + result = Detections.merge(detections_list=detections_list) + assert result == expected_result @pytest.mark.parametrize( @@ -324,3 +324,107 @@ def test_equal( detections_a: Detections, detections_b: Detections, expected_result: bool ) -> None: assert (detections_a == detections_b) == expected_result + + +@pytest.mark.parametrize( + "data_list, expected_result, exception", + [ + ( + [], + {}, + DoesNotRaise(), + ), # empty data list + ( + [ + {} + ], + {}, + DoesNotRaise(), + ), # single empty data dict + ( + [ + {}, + {} + ], + {}, + DoesNotRaise(), + ), # two empty data dicts + ( + [ + {"test_1": [1, 2, 3]}, + {"test_1": [3, 2, 1]}, + ], + {"test_1": [1, 2, 3, 3, 2, 1]}, + DoesNotRaise(), + ), # two data dicts with the same field name and list values + ( + [ + {"test_1": [1, 2, 3]}, + {"test_1": [3, 2, 1]}, + {"test_1": [1, 2, 3]}, + ], + {"test_1": [1, 2, 3, 3, 2, 1, 1, 2, 3]}, + DoesNotRaise(), + ), # three data dicts with the same field name and list values + ( + [ + {"test_1": [1, 2, 3]}, + {"test_2": [3, 2, 1]}, + ], + None, + pytest.raises(ValueError), + ), # two data dicts with different field names + ( + [ + {"test_1": np.array([1, 2, 3])}, + {"test_1": np.array([3, 2, 1])}, + ], + {"test_1": np.array([1, 2, 3, 3, 2, 1])}, + DoesNotRaise(), + ), # two data dicts with the same field name and np.array values as 1D arrays + ( + [ + {"test_1": np.array([[1, 2, 3]])}, + {"test_1": np.array([[3, 2, 1]])}, + ], + {"test_1": np.array([[1, 2, 3], [3, 2, 1]])}, + DoesNotRaise(), + ), # two data dicts with the same field name and np.array values as 2D arrays + ( + [ + {"test_1": np.array([1, 2, 3]), "test_2": np.array(['a', 'b', 'c'])}, + {"test_1": np.array([3, 2, 1]), "test_2": np.array(['c', 'b', 'a'])}, + ], + { + "test_1": np.array([1, 2, 3, 3, 2, 1]), + "test_2": np.array(['a', 'b', 'c', 'c', 'b', 'a']) + }, + DoesNotRaise(), + ), # two data dicts with the same field names and np.array values + ( + [ + {"test_1": [1, 2, 3], "test_2": np.array(['a', 'b', 'c'])}, + {"test_1": [3, 2, 1], "test_2": np.array(['c', 'b', 'a'])}, + ], + { + "test_1": [1, 2, 3, 3, 2, 1], + "test_2": np.array(['a', 'b', 'c', 'c', 'b', 'a']) + }, + DoesNotRaise(), + ), # two data dicts with the same field names and mixed values + ] +) +def test_merge_data( + data_list: List[Dict[str, Any]], + expected_result: Optional[Dict[str, Any]], + exception: Exception +): + with exception: + result = merge_data(data_list=data_list) + for key in result: + if isinstance(result[key], np.ndarray): + assert np.array_equal(result[key], expected_result[ + key]), f"Mismatch in arrays for key {key}" + else: + assert result[key] == expected_result[ + key], f"Mismatch in non-array data for key {key}" From 27eb823809aff2cda60f9a854d2a0e2df7acaa6b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 29 Dec 2023 14:05:20 +0000 Subject: [PATCH 22/45] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto?= =?UTF-8?q?=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/core.py | 6 ++---- test/detection/test_core.py | 37 ++++++++++++++++------------------- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index 2a57eb82..704bbc73 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -61,9 +61,7 @@ def _validate_tracker_id(tracker_id: Any, n: int) -> None: raise ValueError("tracker_id must be None or 1d np.ndarray with (n,) shape") -def is_data_equal( - data_a: Dict[str, np.ndarray], data_b: Dict[str, np.ndarray] -) -> bool: +def is_data_equal(data_a: Dict[str, np.ndarray], data_b: Dict[str, np.ndarray]) -> bool: """ Compares the data payloads of two Detections instances. @@ -79,7 +77,7 @@ def is_data_equal( def merge_data( - data_list: List[Dict[str, Union[np.ndarray, List]]] + data_list: List[Dict[str, Union[np.ndarray, List]]], ) -> Dict[str, Union[np.ndarray, List]]: """ Merges the data payloads of a list of Detections instances. diff --git a/test/detection/test_core.py b/test/detection/test_core.py index 41102d57..b21423e9 100644 --- a/test/detection/test_core.py +++ b/test/detection/test_core.py @@ -1,6 +1,6 @@ from contextlib import ExitStack as DoesNotRaise from test.test_utils import mock_detections -from typing import List, Optional, Union, Dict, Any +from typing import Any, Dict, List, Optional, Union import numpy as np import pytest @@ -335,17 +335,12 @@ def test_equal( DoesNotRaise(), ), # empty data list ( - [ - {} - ], + [{}], {}, DoesNotRaise(), ), # single empty data dict ( - [ - {}, - {} - ], + [{}, {}], {}, DoesNotRaise(), ), # two empty data dicts @@ -392,39 +387,41 @@ def test_equal( ), # two data dicts with the same field name and np.array values as 2D arrays ( [ - {"test_1": np.array([1, 2, 3]), "test_2": np.array(['a', 'b', 'c'])}, - {"test_1": np.array([3, 2, 1]), "test_2": np.array(['c', 'b', 'a'])}, + {"test_1": np.array([1, 2, 3]), "test_2": np.array(["a", "b", "c"])}, + {"test_1": np.array([3, 2, 1]), "test_2": np.array(["c", "b", "a"])}, ], { "test_1": np.array([1, 2, 3, 3, 2, 1]), - "test_2": np.array(['a', 'b', 'c', 'c', 'b', 'a']) + "test_2": np.array(["a", "b", "c", "c", "b", "a"]), }, DoesNotRaise(), ), # two data dicts with the same field names and np.array values ( [ - {"test_1": [1, 2, 3], "test_2": np.array(['a', 'b', 'c'])}, - {"test_1": [3, 2, 1], "test_2": np.array(['c', 'b', 'a'])}, + {"test_1": [1, 2, 3], "test_2": np.array(["a", "b", "c"])}, + {"test_1": [3, 2, 1], "test_2": np.array(["c", "b", "a"])}, ], { "test_1": [1, 2, 3, 3, 2, 1], - "test_2": np.array(['a', 'b', 'c', 'c', 'b', 'a']) + "test_2": np.array(["a", "b", "c", "c", "b", "a"]), }, DoesNotRaise(), ), # two data dicts with the same field names and mixed values - ] + ], ) def test_merge_data( data_list: List[Dict[str, Any]], expected_result: Optional[Dict[str, Any]], - exception: Exception + exception: Exception, ): with exception: result = merge_data(data_list=data_list) for key in result: if isinstance(result[key], np.ndarray): - assert np.array_equal(result[key], expected_result[ - key]), f"Mismatch in arrays for key {key}" + assert np.array_equal( + result[key], expected_result[key] + ), f"Mismatch in arrays for key {key}" else: - assert result[key] == expected_result[ - key], f"Mismatch in non-array data for key {key}" + assert ( + result[key] == expected_result[key] + ), f"Mismatch in non-array data for key {key}" From 701b0c0b97d757ae21625e2fd753c71b3d781a92 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Fri, 29 Dec 2023 15:15:38 +0100 Subject: [PATCH 23/45] fix `test_detections_from_xml_obj` --- supervision/dataset/formats/pascal_voc.py | 11 ++- test/dataset/formats/test_pascal_voc.py | 103 ++++++++++++---------- 2 files changed, 59 insertions(+), 55 deletions(-) diff --git a/supervision/dataset/formats/pascal_voc.py b/supervision/dataset/formats/pascal_voc.py index 74f6be70..32ba7a74 100644 --- a/supervision/dataset/formats/pascal_voc.py +++ b/supervision/dataset/formats/pascal_voc.py @@ -268,12 +268,11 @@ def detections_from_xml_obj( [extended_classes.index(class_name) for class_name in class_names] ) - if with_masks: - annotation = Detections( - xyxy=xyxy, mask=np.array(masks).astype(bool), class_id=class_id - ) - else: - annotation = Detections(xyxy=xyxy, class_id=class_id) + annotation = Detections( + xyxy=xyxy.astype(np.float32), + mask=np.array(masks).astype(bool) if with_masks else None, + class_id=class_id) + return annotation, extended_classes diff --git a/test/dataset/formats/test_pascal_voc.py b/test/dataset/formats/test_pascal_voc.py index 238967dc..fa58f257 100644 --- a/test/dataset/formats/test_pascal_voc.py +++ b/test/dataset/formats/test_pascal_voc.py @@ -7,8 +7,9 @@ import pytest from supervision.dataset.formats.pascal_voc import ( object_to_pascal_voc, - parse_polygon_points, + parse_polygon_points, detections_from_xml_obj, ) +from test.test_utils import mock_detections def are_xml_elements_equal(elem1, elem2): @@ -112,51 +113,55 @@ N_CLASS_N_BBOX = """test1 NO_DETECTIONS = """""" -# @pytest.mark.parametrize( -# "xml_string, classes, resolution_wh, force_masks, expected_result, exception", -# [ -# ( -# ONE_CLASS_ONE_BBOX, -# ["test"], -# (100, 100), -# False, -# mock_detections(np.array([[0, 0, 10, 10]]), None, [0]), -# DoesNotRaise(), -# ), -# ( -# ONE_CLASS_N_BBOX, -# ["test"], -# (100, 100), -# False, -# mock_detections(np.array([[0, 0, 10, 10], [10, 10, 20, 20]]), None, [0, 0]), -# DoesNotRaise(), -# ), -# ( -# N_CLASS_N_BBOX, -# ["test", "test2"], -# (100, 100), -# False, -# mock_detections( -# np.array([[0, 0, 10, 10], [20, 30, 30, 40], [10, 10, 20, 20]]), -# None, -# [0, 0, 1], -# ), -# DoesNotRaise(), -# ), -# ( -# NO_DETECTIONS, -# [], -# (100, 100), -# False, -# mock_detections(np.empty((0, 4)), None, []), -# DoesNotRaise(), -# ), -# ], -# ) -# def test_detections_from_xml_obj( -# xml_string, classes, resolution_wh, force_masks, expected_result, exception -# ): -# with exception: -# root = ET.fromstring(xml_string) -# result, _ = detections_from_xml_obj(root, classes, resolution_wh, force_masks) -# assert result == expected_result +@pytest.mark.parametrize( + "xml_string, classes, resolution_wh, force_masks, expected_result, exception", + [ + ( + ONE_CLASS_ONE_BBOX, + ["test"], + (100, 100), + False, + mock_detections(xyxy=[[0, 0, 10, 10]], class_id=[0]), + DoesNotRaise(), + ), + ( + ONE_CLASS_N_BBOX, + ["test"], + (100, 100), + False, + mock_detections( + xyxy=np.array([[0, 0, 10, 10], [10, 10, 20, 20]]), + class_id=[0, 0] + ), + DoesNotRaise(), + ), + ( + N_CLASS_N_BBOX, + ["test", "test2"], + (100, 100), + False, + mock_detections( + xyxy=np.array([[0, 0, 10, 10], [20, 30, 30, 40], [10, 10, 20, 20]]), + class_id=[0, 0, 1], + ), + DoesNotRaise(), + ), + ( + NO_DETECTIONS, + [], + (100, 100), + False, + mock_detections(xyxy=np.empty((0, 4)), class_id=[]), + DoesNotRaise(), + ), + ], +) +def test_detections_from_xml_obj( + xml_string, classes, resolution_wh, force_masks, expected_result, exception +): + with exception: + root = ET.fromstring(xml_string) + result, _ = detections_from_xml_obj(root, classes, resolution_wh, force_masks) + print(result) + print(expected_result) + assert result == expected_result From 14761cb20a596e275252699ae5112dab37f13454 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Fri, 29 Dec 2023 15:15:57 +0100 Subject: [PATCH 24/45] fix `test_detections_from_xml_obj` --- test/dataset/formats/test_pascal_voc.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/dataset/formats/test_pascal_voc.py b/test/dataset/formats/test_pascal_voc.py index fa58f257..4aee7d74 100644 --- a/test/dataset/formats/test_pascal_voc.py +++ b/test/dataset/formats/test_pascal_voc.py @@ -162,6 +162,4 @@ def test_detections_from_xml_obj( with exception: root = ET.fromstring(xml_string) result, _ = detections_from_xml_obj(root, classes, resolution_wh, force_masks) - print(result) - print(expected_result) assert result == expected_result From 63ccc98d0b683adff58c5f70509dbf8f2f8afbae Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 29 Dec 2023 14:15:59 +0000 Subject: [PATCH 25/45] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto?= =?UTF-8?q?=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/dataset/formats/pascal_voc.py | 3 ++- test/dataset/formats/test_pascal_voc.py | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/supervision/dataset/formats/pascal_voc.py b/supervision/dataset/formats/pascal_voc.py index 32ba7a74..ba77ddf8 100644 --- a/supervision/dataset/formats/pascal_voc.py +++ b/supervision/dataset/formats/pascal_voc.py @@ -271,7 +271,8 @@ def detections_from_xml_obj( annotation = Detections( xyxy=xyxy.astype(np.float32), mask=np.array(masks).astype(bool) if with_masks else None, - class_id=class_id) + class_id=class_id, + ) return annotation, extended_classes diff --git a/test/dataset/formats/test_pascal_voc.py b/test/dataset/formats/test_pascal_voc.py index fa58f257..23aa65a4 100644 --- a/test/dataset/formats/test_pascal_voc.py +++ b/test/dataset/formats/test_pascal_voc.py @@ -1,4 +1,5 @@ from contextlib import ExitStack as DoesNotRaise +from test.test_utils import mock_detections from typing import List, Optional import defusedxml.ElementTree as ET @@ -6,10 +7,10 @@ import numpy as np import pytest from supervision.dataset.formats.pascal_voc import ( + detections_from_xml_obj, object_to_pascal_voc, - parse_polygon_points, detections_from_xml_obj, + parse_polygon_points, ) -from test.test_utils import mock_detections def are_xml_elements_equal(elem1, elem2): @@ -130,8 +131,7 @@ NO_DETECTIONS = """""" (100, 100), False, mock_detections( - xyxy=np.array([[0, 0, 10, 10], [10, 10, 20, 20]]), - class_id=[0, 0] + xyxy=np.array([[0, 0, 10, 10], [10, 10, 20, 20]]), class_id=[0, 0] ), DoesNotRaise(), ), From 9be71da8886fc05783cdf93f8d688e3df8c70989 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Fri, 29 Dec 2023 16:15:36 +0100 Subject: [PATCH 26/45] handle one more edge case in `Detections.merge` logic --- supervision/detection/core.py | 12 +++++++++- test/detection/test_core.py | 45 +++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index 704bbc73..b972afaa 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -87,7 +87,11 @@ def merge_data( Returns: A single data payload containing the merged data, preserving the original data - types (list or np.ndarray). + types (list or np.ndarray). + + Raises: + ValueError: If data values within a single object have different lengths or if + dictionaries have different keys. """ if not data_list: return {} @@ -96,6 +100,12 @@ def merge_data( if not all(keys_set == all_keys_sets[0] for keys_set in all_keys_sets): raise ValueError("All data dictionaries must have the same keys to merge.") + for data in data_list: + lengths = [len(value) for value in data.values()] + if len(set(lengths)) > 1: + raise ValueError( + "All data values within a single object must have equal length.") + merged_data = {key: [] for key in all_keys_sets[0]} for data in data_list: diff --git a/test/detection/test_core.py b/test/detection/test_core.py index b21423e9..3b9a53f1 100644 --- a/test/detection/test_core.py +++ b/test/detection/test_core.py @@ -344,6 +344,35 @@ def test_equal( {}, DoesNotRaise(), ), # two empty data dicts + ( + [ + {"test_1": []}, + ], + {"test_1": []}, + DoesNotRaise(), + ), # single data dict with a single field name and empty list values + ( + [ + {"test_1": np.array([])}, + ], + {"test_1": np.array([])}, + DoesNotRaise(), + ), # single data dict with a single field name and empty np.array values + ( + [ + {"test_1": [1, 2, 3]}, + ], + {"test_1": [1, 2, 3]}, + DoesNotRaise(), + ), # single data dict with a single field name and list values + ( + [ + {"test_1": []}, + {"test_1": [3, 2, 1]}, + ], + {"test_1": [3, 2, 1]}, + DoesNotRaise(), + ), # two data dicts with the same field name and empty and list values ( [ {"test_1": [1, 2, 3]}, @@ -407,6 +436,22 @@ def test_equal( }, DoesNotRaise(), ), # two data dicts with the same field names and mixed values + ( + [ + {"test_1": np.array([1, 2, 3])}, + {"test_1": np.array([[3, 2, 1]])}, + ], + None, + pytest.raises(ValueError), + ), # two data dicts with the same field name and 1D and 2D arrays values + ( + [ + {"test_1": np.array([1, 2, 3]), "test_2": np.array(["a", "b"])}, + {"test_1": np.array([3, 2, 1]), "test_2": np.array(["c", "b", "a"])}, + ], + None, + pytest.raises(ValueError), + ), # two data dicts with the same field name and different length arrays values ], ) def test_merge_data( From db245bbc34f6049f7193c0d0a7ee72464706b85d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 29 Dec 2023 15:15:48 +0000 Subject: [PATCH 27/45] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto?= =?UTF-8?q?=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/core.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index b972afaa..214b5f42 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -104,7 +104,8 @@ def merge_data( lengths = [len(value) for value in data.values()] if len(set(lengths)) > 1: raise ValueError( - "All data values within a single object must have equal length.") + "All data values within a single object must have equal length." + ) merged_data = {key: [] for key in all_keys_sets[0]} From 95e2ad6d5225de927fa44d760549108d59735a6d Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Fri, 29 Dec 2023 16:36:54 +0100 Subject: [PATCH 28/45] refactor of `core.py`; moving utils to `utils.py` --- supervision/detection/core.py | 131 +------------- .../detection/tools/inference_slicer.py | 3 +- supervision/detection/utils.py | 127 +++++++++++++- test/detection/test_core.py | 164 +----------------- test/detection/test_utils.py | 150 +++++++++++++++- 5 files changed, 292 insertions(+), 283 deletions(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index b972afaa..c4995e30 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -1,7 +1,6 @@ from __future__ import annotations from dataclasses import astuple, dataclass, field -from itertools import chain from typing import Any, Dict, Iterator, List, Optional, Tuple, Union import numpy as np @@ -11,127 +10,12 @@ from supervision.detection.utils import ( extract_ultralytics_masks, non_max_suppression, process_roboflow_result, - xywh_to_xyxy, + xywh_to_xyxy, validate_xyxy, validate_mask, validate_class_id, validate_confidence, + validate_tracker_id, validate_data, is_data_equal, merge_data, ) from supervision.geometry.core import Position -def _validate_xyxy(xyxy: Any, n: int) -> None: - is_valid = isinstance(xyxy, np.ndarray) and xyxy.shape == (n, 4) - if not is_valid: - raise ValueError("xyxy must be 2d np.ndarray with (n, 4) shape") - - -def _validate_mask(mask: Any, n: int) -> None: - is_valid = mask is None or ( - isinstance(mask, np.ndarray) and len(mask.shape) == 3 and mask.shape[0] == n - ) - if not is_valid: - raise ValueError("mask must be 3d np.ndarray with (n, H, W) shape") - - -def validate_inference_callback(callback) -> None: - tmp_img = np.zeros((256, 256, 3), dtype=np.uint8) - res = callback(tmp_img) - if not isinstance(res, Detections): - raise ValueError("Callback function must return sv.Detection type") - - -def _validate_class_id(class_id: Any, n: int) -> None: - is_valid = class_id is None or ( - isinstance(class_id, np.ndarray) and class_id.shape == (n,) - ) - if not is_valid: - raise ValueError("class_id must be None or 1d np.ndarray with (n,) shape") - - -def _validate_confidence(confidence: Any, n: int) -> None: - is_valid = confidence is None or ( - isinstance(confidence, np.ndarray) and confidence.shape == (n,) - ) - if not is_valid: - raise ValueError("confidence must be None or 1d np.ndarray with (n,) shape") - - -def _validate_tracker_id(tracker_id: Any, n: int) -> None: - is_valid = tracker_id is None or ( - isinstance(tracker_id, np.ndarray) and tracker_id.shape == (n,) - ) - if not is_valid: - raise ValueError("tracker_id must be None or 1d np.ndarray with (n,) shape") - - -def is_data_equal(data_a: Dict[str, np.ndarray], data_b: Dict[str, np.ndarray]) -> bool: - """ - Compares the data payloads of two Detections instances. - - Args: - data_a, data_b: The data payloads of the instances. - - Returns: - True if the data payloads are equal, False otherwise. - """ - return set(data_a.keys()) == set(data_b.keys()) and all( - np.array_equal(data_a[key], data_b[key]) for key in data_a - ) - - -def merge_data( - data_list: List[Dict[str, Union[np.ndarray, List]]], -) -> Dict[str, Union[np.ndarray, List]]: - """ - Merges the data payloads of a list of Detections instances. - - Args: - data_list: The data payloads of the instances. - - Returns: - A single data payload containing the merged data, preserving the original data - types (list or np.ndarray). - - Raises: - ValueError: If data values within a single object have different lengths or if - dictionaries have different keys. - """ - if not data_list: - return {} - - all_keys_sets = [set(data.keys()) for data in data_list] - if not all(keys_set == all_keys_sets[0] for keys_set in all_keys_sets): - raise ValueError("All data dictionaries must have the same keys to merge.") - - for data in data_list: - lengths = [len(value) for value in data.values()] - if len(set(lengths)) > 1: - raise ValueError( - "All data values within a single object must have equal length.") - - merged_data = {key: [] for key in all_keys_sets[0]} - - for data in data_list: - for key in merged_data: - merged_data[key].append(data[key]) - - for key in merged_data: - if all(isinstance(item, list) for item in merged_data[key]): - merged_data[key] = list(chain.from_iterable(merged_data[key])) - elif all(isinstance(item, np.ndarray) for item in merged_data[key]): - ndim = merged_data[key][0].ndim - if ndim == 1: - merged_data[key] = np.hstack(merged_data[key]) - elif ndim > 1: - merged_data[key] = np.vstack(merged_data[key]) - else: - raise ValueError(f"Unexpected array dimension for key '{key}'.") - else: - raise ValueError( - f"Inconsistent data types for key '{key}'. Only np.ndarray and list " - f"types are allowed." - ) - - return merged_data - - @dataclass class Detections: """ @@ -162,11 +46,12 @@ class Detections: def __post_init__(self): n = len(self.xyxy) - _validate_xyxy(xyxy=self.xyxy, n=n) - _validate_mask(mask=self.mask, n=n) - _validate_class_id(class_id=self.class_id, n=n) - _validate_confidence(confidence=self.confidence, n=n) - _validate_tracker_id(tracker_id=self.tracker_id, n=n) + validate_xyxy(xyxy=self.xyxy, n=n) + validate_mask(mask=self.mask, n=n) + validate_class_id(class_id=self.class_id, n=n) + validate_confidence(confidence=self.confidence, n=n) + validate_tracker_id(tracker_id=self.tracker_id, n=n) + validate_data(data=self.data, n=n) def __len__(self): """ diff --git a/supervision/detection/tools/inference_slicer.py b/supervision/detection/tools/inference_slicer.py index 5f6fb391..2c0c48a6 100644 --- a/supervision/detection/tools/inference_slicer.py +++ b/supervision/detection/tools/inference_slicer.py @@ -3,7 +3,7 @@ from typing import Callable, Optional, Tuple import numpy as np -from supervision.detection.core import Detections, validate_inference_callback +from supervision.detection.core import Detections from supervision.detection.utils import move_boxes from supervision.utils.image import crop_image @@ -60,7 +60,6 @@ class InferenceSlicer: self.iou_threshold = iou_threshold self.callback = callback self.thread_workers = thread_workers - validate_inference_callback(callback=callback) def __call__(self, image: np.ndarray) -> Detections: """ diff --git a/supervision/detection/utils.py b/supervision/detection/utils.py index a26f72ff..0499044b 100644 --- a/supervision/detection/utils.py +++ b/supervision/detection/utils.py @@ -1,4 +1,5 @@ -from typing import List, Optional, Tuple +from itertools import chain +from typing import List, Optional, Tuple, Dict, Union, Any import cv2 import numpy as np @@ -469,3 +470,127 @@ def calculate_masks_centroids(masks: np.ndarray) -> np.ndarray: centroid_y = sum_over_mask(vertical_indices, aggregation_axis) / total_pixels return np.column_stack((centroid_x, centroid_y)).astype(int) + + +def validate_xyxy(xyxy: Any, n: int) -> None: + is_valid = isinstance(xyxy, np.ndarray) and xyxy.shape == (n, 4) + if not is_valid: + raise ValueError("xyxy must be 2d np.ndarray with (n, 4) shape") + + +def validate_mask(mask: Any, n: int) -> None: + is_valid = mask is None or ( + isinstance(mask, np.ndarray) and len(mask.shape) == 3 and mask.shape[0] == n + ) + if not is_valid: + raise ValueError("mask must be 3d np.ndarray with (n, H, W) shape") + + +def validate_class_id(class_id: Any, n: int) -> None: + is_valid = class_id is None or ( + isinstance(class_id, np.ndarray) and class_id.shape == (n,) + ) + if not is_valid: + raise ValueError("class_id must be None or 1d np.ndarray with (n,) shape") + + +def validate_confidence(confidence: Any, n: int) -> None: + is_valid = confidence is None or ( + isinstance(confidence, np.ndarray) and confidence.shape == (n,) + ) + if not is_valid: + raise ValueError("confidence must be None or 1d np.ndarray with (n,) shape") + + +def validate_tracker_id(tracker_id: Any, n: int) -> None: + is_valid = tracker_id is None or ( + isinstance(tracker_id, np.ndarray) and tracker_id.shape == (n,) + ) + if not is_valid: + raise ValueError("tracker_id must be None or 1d np.ndarray with (n,) shape") + + +def validate_data(data: Dict[str, Union[np.ndarray, List]], n: int) -> None: + for key, value in data.items(): + if isinstance(value, list): + if len(value) != n: + raise ValueError(f"Length of list for key '{key}' must be {n}") + elif isinstance(value, np.ndarray): + if value.ndim == 1 and value.shape[0] != n: + raise ValueError(f"Shape of np.ndarray for key '{key}' must be ({n},)") + elif value.ndim > 1 and value.shape[0] != n: + raise ValueError( + f"First dimension of np.ndarray for key '{key}' must have size {n}") + else: + raise ValueError(f"Value for key '{key}' must be a list or np.ndarray") + + +def is_data_equal(data_a: Dict[str, np.ndarray], data_b: Dict[str, np.ndarray]) -> bool: + """ + Compares the data payloads of two Detections instances. + + Args: + data_a, data_b: The data payloads of the instances. + + Returns: + True if the data payloads are equal, False otherwise. + """ + return set(data_a.keys()) == set(data_b.keys()) and all( + np.array_equal(data_a[key], data_b[key]) for key in data_a + ) + + +def merge_data( + data_list: List[Dict[str, Union[np.ndarray, List]]], +) -> Dict[str, Union[np.ndarray, List]]: + """ + Merges the data payloads of a list of Detections instances. + + Args: + data_list: The data payloads of the instances. + + Returns: + A single data payload containing the merged data, preserving the original data + types (list or np.ndarray). + + Raises: + ValueError: If data values within a single object have different lengths or if + dictionaries have different keys. + """ + if not data_list: + return {} + + all_keys_sets = [set(data.keys()) for data in data_list] + if not all(keys_set == all_keys_sets[0] for keys_set in all_keys_sets): + raise ValueError("All data dictionaries must have the same keys to merge.") + + for data in data_list: + lengths = [len(value) for value in data.values()] + if len(set(lengths)) > 1: + raise ValueError( + "All data values within a single object must have equal length.") + + merged_data = {key: [] for key in all_keys_sets[0]} + + for data in data_list: + for key in merged_data: + merged_data[key].append(data[key]) + + for key in merged_data: + if all(isinstance(item, list) for item in merged_data[key]): + merged_data[key] = list(chain.from_iterable(merged_data[key])) + elif all(isinstance(item, np.ndarray) for item in merged_data[key]): + ndim = merged_data[key][0].ndim + if ndim == 1: + merged_data[key] = np.hstack(merged_data[key]) + elif ndim > 1: + merged_data[key] = np.vstack(merged_data[key]) + else: + raise ValueError(f"Unexpected array dimension for key '{key}'.") + else: + raise ValueError( + f"Inconsistent data types for key '{key}'. Only np.ndarray and list " + f"types are allowed." + ) + + return merged_data diff --git a/test/detection/test_core.py b/test/detection/test_core.py index 3b9a53f1..e71cdbb7 100644 --- a/test/detection/test_core.py +++ b/test/detection/test_core.py @@ -1,11 +1,11 @@ from contextlib import ExitStack as DoesNotRaise from test.test_utils import mock_detections -from typing import Any, Dict, List, Optional, Union +from typing import List, Optional, Union import numpy as np import pytest -from supervision.detection.core import Detections, merge_data +from supervision.detection.core import Detections from supervision.geometry.core import Position PREDICTIONS = np.array( @@ -299,23 +299,23 @@ def test_get_anchor_coordinates( False, ), # detection with xyxy field + detection with xyxy, confidence fields ( - mock_detections(xyxy=[[10, 10, 20, 20]], data={"test": [1, 2, 3]}), - mock_detections(xyxy=[[10, 10, 20, 20]], data={"test": [1, 2, 3]}), + mock_detections(xyxy=[[10, 10, 20, 20]], data={"test": [1]}), + mock_detections(xyxy=[[10, 10, 20, 20]], data={"test": [1]}), True, ), # detections with xyxy, data fields ( - mock_detections(xyxy=[[10, 10, 20, 20]], data={"test": [1, 2, 3]}), + mock_detections(xyxy=[[10, 10, 20, 20]], data={"test": [1]}), mock_detections(xyxy=[[10, 10, 20, 20]]), False, ), # detection with xyxy field + detection with xyxy, data fields ( - mock_detections(xyxy=[[10, 10, 20, 20]], data={"test_1": [1, 2, 3]}), - mock_detections(xyxy=[[10, 10, 20, 20]], data={"test_2": [1, 2, 3]}), + mock_detections(xyxy=[[10, 10, 20, 20]], data={"test_1": [1]}), + mock_detections(xyxy=[[10, 10, 20, 20]], data={"test_2": [1]}), False, ), # detections with xyxy, and different data field names ( - mock_detections(xyxy=[[10, 10, 20, 20]], data={"test_1": [1, 2, 3]}), - mock_detections(xyxy=[[10, 10, 20, 20]], data={"test_1": [3, 2, 1]}), + mock_detections(xyxy=[[10, 10, 20, 20]], data={"test_1": [1]}), + mock_detections(xyxy=[[10, 10, 20, 20]], data={"test_1": [3]}), False, ), # detections with xyxy, and different data field values ], @@ -324,149 +324,3 @@ def test_equal( detections_a: Detections, detections_b: Detections, expected_result: bool ) -> None: assert (detections_a == detections_b) == expected_result - - -@pytest.mark.parametrize( - "data_list, expected_result, exception", - [ - ( - [], - {}, - DoesNotRaise(), - ), # empty data list - ( - [{}], - {}, - DoesNotRaise(), - ), # single empty data dict - ( - [{}, {}], - {}, - DoesNotRaise(), - ), # two empty data dicts - ( - [ - {"test_1": []}, - ], - {"test_1": []}, - DoesNotRaise(), - ), # single data dict with a single field name and empty list values - ( - [ - {"test_1": np.array([])}, - ], - {"test_1": np.array([])}, - DoesNotRaise(), - ), # single data dict with a single field name and empty np.array values - ( - [ - {"test_1": [1, 2, 3]}, - ], - {"test_1": [1, 2, 3]}, - DoesNotRaise(), - ), # single data dict with a single field name and list values - ( - [ - {"test_1": []}, - {"test_1": [3, 2, 1]}, - ], - {"test_1": [3, 2, 1]}, - DoesNotRaise(), - ), # two data dicts with the same field name and empty and list values - ( - [ - {"test_1": [1, 2, 3]}, - {"test_1": [3, 2, 1]}, - ], - {"test_1": [1, 2, 3, 3, 2, 1]}, - DoesNotRaise(), - ), # two data dicts with the same field name and list values - ( - [ - {"test_1": [1, 2, 3]}, - {"test_1": [3, 2, 1]}, - {"test_1": [1, 2, 3]}, - ], - {"test_1": [1, 2, 3, 3, 2, 1, 1, 2, 3]}, - DoesNotRaise(), - ), # three data dicts with the same field name and list values - ( - [ - {"test_1": [1, 2, 3]}, - {"test_2": [3, 2, 1]}, - ], - None, - pytest.raises(ValueError), - ), # two data dicts with different field names - ( - [ - {"test_1": np.array([1, 2, 3])}, - {"test_1": np.array([3, 2, 1])}, - ], - {"test_1": np.array([1, 2, 3, 3, 2, 1])}, - DoesNotRaise(), - ), # two data dicts with the same field name and np.array values as 1D arrays - ( - [ - {"test_1": np.array([[1, 2, 3]])}, - {"test_1": np.array([[3, 2, 1]])}, - ], - {"test_1": np.array([[1, 2, 3], [3, 2, 1]])}, - DoesNotRaise(), - ), # two data dicts with the same field name and np.array values as 2D arrays - ( - [ - {"test_1": np.array([1, 2, 3]), "test_2": np.array(["a", "b", "c"])}, - {"test_1": np.array([3, 2, 1]), "test_2": np.array(["c", "b", "a"])}, - ], - { - "test_1": np.array([1, 2, 3, 3, 2, 1]), - "test_2": np.array(["a", "b", "c", "c", "b", "a"]), - }, - DoesNotRaise(), - ), # two data dicts with the same field names and np.array values - ( - [ - {"test_1": [1, 2, 3], "test_2": np.array(["a", "b", "c"])}, - {"test_1": [3, 2, 1], "test_2": np.array(["c", "b", "a"])}, - ], - { - "test_1": [1, 2, 3, 3, 2, 1], - "test_2": np.array(["a", "b", "c", "c", "b", "a"]), - }, - DoesNotRaise(), - ), # two data dicts with the same field names and mixed values - ( - [ - {"test_1": np.array([1, 2, 3])}, - {"test_1": np.array([[3, 2, 1]])}, - ], - None, - pytest.raises(ValueError), - ), # two data dicts with the same field name and 1D and 2D arrays values - ( - [ - {"test_1": np.array([1, 2, 3]), "test_2": np.array(["a", "b"])}, - {"test_1": np.array([3, 2, 1]), "test_2": np.array(["c", "b", "a"])}, - ], - None, - pytest.raises(ValueError), - ), # two data dicts with the same field name and different length arrays values - ], -) -def test_merge_data( - data_list: List[Dict[str, Any]], - expected_result: Optional[Dict[str, Any]], - exception: Exception, -): - with exception: - result = merge_data(data_list=data_list) - for key in result: - if isinstance(result[key], np.ndarray): - assert np.array_equal( - result[key], expected_result[key] - ), f"Mismatch in arrays for key {key}" - else: - assert ( - result[key] == expected_result[key] - ), f"Mismatch in non-array data for key {key}" diff --git a/test/detection/test_utils.py b/test/detection/test_utils.py index 83b71b85..139a02cf 100644 --- a/test/detection/test_utils.py +++ b/test/detection/test_utils.py @@ -1,5 +1,5 @@ from contextlib import ExitStack as DoesNotRaise -from typing import List, Optional, Tuple +from typing import List, Optional, Tuple, Dict, Any import numpy as np import pytest @@ -11,7 +11,7 @@ from supervision.detection.utils import ( move_boxes, non_max_suppression, process_roboflow_result, - scale_boxes, + scale_boxes, merge_data, ) TEST_MASK = np.zeros((1, 1000, 1000), dtype=bool) @@ -640,3 +640,149 @@ def test_calculate_masks_centroids( with exception: result = calculate_masks_centroids(masks=masks) assert np.array_equal(result, expected_result) + + +@pytest.mark.parametrize( + "data_list, expected_result, exception", + [ + ( + [], + {}, + DoesNotRaise(), + ), # empty data list + ( + [{}], + {}, + DoesNotRaise(), + ), # single empty data dict + ( + [{}, {}], + {}, + DoesNotRaise(), + ), # two empty data dicts + ( + [ + {"test_1": []}, + ], + {"test_1": []}, + DoesNotRaise(), + ), # single data dict with a single field name and empty list values + ( + [ + {"test_1": np.array([])}, + ], + {"test_1": np.array([])}, + DoesNotRaise(), + ), # single data dict with a single field name and empty np.array values + ( + [ + {"test_1": [1, 2, 3]}, + ], + {"test_1": [1, 2, 3]}, + DoesNotRaise(), + ), # single data dict with a single field name and list values + ( + [ + {"test_1": []}, + {"test_1": [3, 2, 1]}, + ], + {"test_1": [3, 2, 1]}, + DoesNotRaise(), + ), # two data dicts with the same field name and empty and list values + ( + [ + {"test_1": [1, 2, 3]}, + {"test_1": [3, 2, 1]}, + ], + {"test_1": [1, 2, 3, 3, 2, 1]}, + DoesNotRaise(), + ), # two data dicts with the same field name and list values + ( + [ + {"test_1": [1, 2, 3]}, + {"test_1": [3, 2, 1]}, + {"test_1": [1, 2, 3]}, + ], + {"test_1": [1, 2, 3, 3, 2, 1, 1, 2, 3]}, + DoesNotRaise(), + ), # three data dicts with the same field name and list values + ( + [ + {"test_1": [1, 2, 3]}, + {"test_2": [3, 2, 1]}, + ], + None, + pytest.raises(ValueError), + ), # two data dicts with different field names + ( + [ + {"test_1": np.array([1, 2, 3])}, + {"test_1": np.array([3, 2, 1])}, + ], + {"test_1": np.array([1, 2, 3, 3, 2, 1])}, + DoesNotRaise(), + ), # two data dicts with the same field name and np.array values as 1D arrays + ( + [ + {"test_1": np.array([[1, 2, 3]])}, + {"test_1": np.array([[3, 2, 1]])}, + ], + {"test_1": np.array([[1, 2, 3], [3, 2, 1]])}, + DoesNotRaise(), + ), # two data dicts with the same field name and np.array values as 2D arrays + ( + [ + {"test_1": np.array([1, 2, 3]), "test_2": np.array(["a", "b", "c"])}, + {"test_1": np.array([3, 2, 1]), "test_2": np.array(["c", "b", "a"])}, + ], + { + "test_1": np.array([1, 2, 3, 3, 2, 1]), + "test_2": np.array(["a", "b", "c", "c", "b", "a"]), + }, + DoesNotRaise(), + ), # two data dicts with the same field names and np.array values + ( + [ + {"test_1": [1, 2, 3], "test_2": np.array(["a", "b", "c"])}, + {"test_1": [3, 2, 1], "test_2": np.array(["c", "b", "a"])}, + ], + { + "test_1": [1, 2, 3, 3, 2, 1], + "test_2": np.array(["a", "b", "c", "c", "b", "a"]), + }, + DoesNotRaise(), + ), # two data dicts with the same field names and mixed values + ( + [ + {"test_1": np.array([1, 2, 3])}, + {"test_1": np.array([[3, 2, 1]])}, + ], + None, + pytest.raises(ValueError), + ), # two data dicts with the same field name and 1D and 2D arrays values + ( + [ + {"test_1": np.array([1, 2, 3]), "test_2": np.array(["a", "b"])}, + {"test_1": np.array([3, 2, 1]), "test_2": np.array(["c", "b", "a"])}, + ], + None, + pytest.raises(ValueError), + ), # two data dicts with the same field name and different length arrays values + ], +) +def test_merge_data( + data_list: List[Dict[str, Any]], + expected_result: Optional[Dict[str, Any]], + exception: Exception, +): + with exception: + result = merge_data(data_list=data_list) + for key in result: + if isinstance(result[key], np.ndarray): + assert np.array_equal( + result[key], expected_result[key] + ), f"Mismatch in arrays for key {key}" + else: + assert ( + result[key] == expected_result[key] + ), f"Mismatch in non-array data for key {key}" From ea21a75135d24c6d9f0f171f637bc712d6aeb9e8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 29 Dec 2023 15:37:48 +0000 Subject: [PATCH 29/45] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto?= =?UTF-8?q?=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/core.py | 11 +++++++++-- supervision/detection/utils.py | 8 +++++--- test/detection/test_utils.py | 5 +++-- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index c4995e30..ea1a7e31 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -8,10 +8,17 @@ import numpy as np from supervision.detection.utils import ( calculate_masks_centroids, extract_ultralytics_masks, + is_data_equal, + merge_data, non_max_suppression, process_roboflow_result, - xywh_to_xyxy, validate_xyxy, validate_mask, validate_class_id, validate_confidence, - validate_tracker_id, validate_data, is_data_equal, merge_data, + validate_class_id, + validate_confidence, + validate_data, + validate_mask, + validate_tracker_id, + validate_xyxy, + xywh_to_xyxy, ) from supervision.geometry.core import Position diff --git a/supervision/detection/utils.py b/supervision/detection/utils.py index 0499044b..5cccdb3f 100644 --- a/supervision/detection/utils.py +++ b/supervision/detection/utils.py @@ -1,5 +1,5 @@ from itertools import chain -from typing import List, Optional, Tuple, Dict, Union, Any +from typing import Any, Dict, List, Optional, Tuple, Union import cv2 import numpy as np @@ -520,7 +520,8 @@ def validate_data(data: Dict[str, Union[np.ndarray, List]], n: int) -> None: raise ValueError(f"Shape of np.ndarray for key '{key}' must be ({n},)") elif value.ndim > 1 and value.shape[0] != n: raise ValueError( - f"First dimension of np.ndarray for key '{key}' must have size {n}") + f"First dimension of np.ndarray for key '{key}' must have size {n}" + ) else: raise ValueError(f"Value for key '{key}' must be a list or np.ndarray") @@ -568,7 +569,8 @@ def merge_data( lengths = [len(value) for value in data.values()] if len(set(lengths)) > 1: raise ValueError( - "All data values within a single object must have equal length.") + "All data values within a single object must have equal length." + ) merged_data = {key: [] for key in all_keys_sets[0]} diff --git a/test/detection/test_utils.py b/test/detection/test_utils.py index 139a02cf..7cd87fb7 100644 --- a/test/detection/test_utils.py +++ b/test/detection/test_utils.py @@ -1,5 +1,5 @@ from contextlib import ExitStack as DoesNotRaise -from typing import List, Optional, Tuple, Dict, Any +from typing import Any, Dict, List, Optional, Tuple import numpy as np import pytest @@ -8,10 +8,11 @@ from supervision.detection.utils import ( calculate_masks_centroids, clip_boxes, filter_polygons_by_area, + merge_data, move_boxes, non_max_suppression, process_roboflow_result, - scale_boxes, merge_data, + scale_boxes, ) TEST_MASK = np.zeros((1, 1000, 1000), dtype=bool) From 50f3de34832586787818089987ee5c0196d1badf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 Dec 2023 18:40:50 +0300 Subject: [PATCH 30/45] :arrow_up: Bump mkdocs-material from 9.5.2 to 9.5.3 (#691) Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.2 to 9.5.3. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.2...9.5.3) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/poetry.lock b/poetry.lock index 4be42bd2..4867adc2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1857,13 +1857,13 @@ mkdocs = ">=1.1" [[package]] name = "mkdocs-material" -version = "9.5.2" +version = "9.5.3" description = "Documentation that simply works" optional = false python-versions = ">=3.8" files = [ - {file = "mkdocs_material-9.5.2-py3-none-any.whl", hash = "sha256:6ed0fbf4682491766f0ec1acc955db6901c2fd424c7ab343964ef51b819741f5"}, - {file = "mkdocs_material-9.5.2.tar.gz", hash = "sha256:ca8b9cd2b3be53e858e5a1a45ac9668bd78d95d77a30288bb5ebc1a31db6184c"}, + {file = "mkdocs_material-9.5.3-py3-none-any.whl", hash = "sha256:76c93a8525cceb0b395b9cedab3428bf518cf6439adef2b940f1c1574b775d89"}, + {file = "mkdocs_material-9.5.3.tar.gz", hash = "sha256:5899219f422f0a6de784232d9d40374416302ffae3c160cacc72969fcc1ee372"}, ] [package.dependencies] @@ -1871,7 +1871,7 @@ 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 = ">=1.5.3,<1.6.0" mkdocs-material-extensions = ">=1.3,<2.0" paginate = ">=0.5,<1.0" pygments = ">=2.16,<3.0" From 099d36011d4b37539c46c3f2deb92ee98d0eea2c Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Fri, 29 Dec 2023 16:53:41 +0100 Subject: [PATCH 31/45] better `Detections.merge` docs + more `Detections.merge` tests --- supervision/detection/core.py | 27 ++++++++++++++++++++++++--- test/detection/test_core.py | 10 ++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index ea1a7e31..8269571a 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -650,12 +650,33 @@ class Detections: Example: ```python - >>> from supervision import Detections + import numpy as np + import supervision as sv - >>> detections_1 = Detections(...) - >>> detections_2 = Detections(...) + >>> detections_1 = sv.Detections( + ... xyxy=np.array([[15, 15, 100, 100], [200, 200, 300, 300]]), + ... class_id=np.array([1, 2]), + ... data={'feature_vector': np.array([0.1, 0.2)])} + ... ) + + >>> detections_2 = sv.Detections( + ... xyxy=np.array([[30, 30, 120, 120]]), + ... class_id=np.array([1]), + ... data={'feature_vector': [np.array([0.3])]} + ... ) >>> merged_detections = Detections.merge([detections_1, detections_2]) + + >>> merged_detections.xyxy + array([[ 15, 15, 100, 100], + [200, 200, 300, 300], + [ 30, 30, 120, 120]]) + + >>> merged_detections.class_id + array([1, 2, 1]) + + >>> merged_detections.data['feature_vector'] + array([0.1, 0.2, 0.3]) ``` """ if len(detections_list) == 0: diff --git a/test/detection/test_core.py b/test/detection/test_core.py index e71cdbb7..40ab22a6 100644 --- a/test/detection/test_core.py +++ b/test/detection/test_core.py @@ -181,6 +181,16 @@ def test_getitem( mock_detections(xyxy=[[10, 10, 20, 20], [20, 20, 30, 30]], class_id=[0, 1]), DoesNotRaise(), ), # two detections with xyxy, class_id fields + ( + [ + mock_detections(xyxy=[[10, 10, 20, 20]], data={"test": [1]}), + mock_detections(xyxy=[[20, 20, 30, 30]], data={"test": [2]}), + ], + mock_detections( + xyxy=[[10, 10, 20, 20], [20, 20, 30, 30]], data={"test": [1, 2]} + ), + DoesNotRaise(), + ), # two detections with xyxy, data fields ], ) def test_merge( From 5984d3374cd46cded2e7c8455bf9749076fe2bf2 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Fri, 29 Dec 2023 18:24:24 +0100 Subject: [PATCH 32/45] better `Detections.merge` validation --- supervision/detection/core.py | 63 +++++++++++++++++++--------------- supervision/detection/utils.py | 53 +++++++++++++++++++++++----- test/detection/test_core.py | 15 +++++--- 3 files changed, 92 insertions(+), 39 deletions(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index 8269571a..1ce5599f 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -1,7 +1,7 @@ from __future__ import annotations -from dataclasses import astuple, dataclass, field -from typing import Any, Dict, Iterator, List, Optional, Tuple, Union +from dataclasses import dataclass, field +from typing import Dict, Iterator, List, Optional, Tuple, Union import numpy as np @@ -12,13 +12,8 @@ from supervision.detection.utils import ( merge_data, non_max_suppression, process_roboflow_result, - validate_class_id, - validate_confidence, - validate_data, - validate_mask, - validate_tracker_id, - validate_xyxy, xywh_to_xyxy, + validate_detections_fields, ) from supervision.geometry.core import Position @@ -52,13 +47,14 @@ class Detections: data: Dict[str, Union[np.ndarray, List]] = field(default_factory=dict) def __post_init__(self): - n = len(self.xyxy) - validate_xyxy(xyxy=self.xyxy, n=n) - validate_mask(mask=self.mask, n=n) - validate_class_id(class_id=self.class_id, n=n) - validate_confidence(confidence=self.confidence, n=n) - validate_tracker_id(tracker_id=self.tracker_id, n=n) - validate_data(data=self.data, n=n) + validate_detections_fields( + xyxy=self.xyxy, + mask=self.mask, + confidence=self.confidence, + class_id=self.class_id, + tracker_id=self.tracker_id, + data=self.data + ) def __len__(self): """ @@ -682,20 +678,33 @@ class Detections: if len(detections_list) == 0: return Detections.empty() - detections_tuples_list = [astuple(detection) for detection in detections_list] - xyxy, mask, confidence, class_id, tracker_id, data = [ - list(field_values) for field_values in zip(*detections_tuples_list) - ] + for detections in detections_list: + validate_detections_fields( + xyxy=detections.xyxy, + mask=detections.mask, + confidence=detections.confidence, + class_id=detections.class_id, + tracker_id=detections.tracker_id, + data=detections.data + ) - def all_not_none(item_list: List[Any]): - return all(x is not None for x in item_list) + xyxy = np.vstack([d.xyxy for d in detections_list]) - xyxy = np.vstack(xyxy) - mask = np.vstack(mask) if all_not_none(mask) else None - confidence = np.hstack(confidence) if all_not_none(confidence) else None - class_id = np.hstack(class_id) if all_not_none(class_id) else None - tracker_id = np.hstack(tracker_id) if all_not_none(tracker_id) else None - data = merge_data(data) + def stack_or_none(name: str): + if all(d.__getattribute__(name) is None for d in detections_list): + return None + if any(d.__getattribute__(name) is None for d in detections_list): + raise ValueError(f"All or none of the '{name}' fields must be None") + return np.vstack([d.__getattribute__(name) for d in detections_list]) \ + if name == 'mask' else np.hstack( + [d.__getattribute__(name) for d in detections_list]) + + mask = stack_or_none('mask') + confidence = stack_or_none('confidence') + class_id = stack_or_none('class_id') + tracker_id = stack_or_none('tracker_id') + + data = merge_data([d.data for d in detections_list]) return cls( xyxy=xyxy, diff --git a/supervision/detection/utils.py b/supervision/detection/utils.py index 5cccdb3f..81f5983f 100644 --- a/supervision/detection/utils.py +++ b/supervision/detection/utils.py @@ -472,45 +472,65 @@ def calculate_masks_centroids(masks: np.ndarray) -> np.ndarray: return np.column_stack((centroid_x, centroid_y)).astype(int) -def validate_xyxy(xyxy: Any, n: int) -> None: - is_valid = isinstance(xyxy, np.ndarray) and xyxy.shape == (n, 4) +def validate_xyxy(xyxy: Any) -> None: + expected_shape = "(_, 4)" + actual_shape = str(getattr(xyxy, 'shape', None)) + is_valid = isinstance(xyxy, np.ndarray) and xyxy.ndim == 2 and xyxy.shape[1] == 4 if not is_valid: - raise ValueError("xyxy must be 2d np.ndarray with (n, 4) shape") + raise ValueError( + f"xyxy must be a 2D np.ndarray with shape {expected_shape}, but got shape " + f"{actual_shape}") def validate_mask(mask: Any, n: int) -> None: + expected_shape = f"({n}, H, W)" + actual_shape = str(getattr(mask, 'shape', None)) is_valid = mask is None or ( isinstance(mask, np.ndarray) and len(mask.shape) == 3 and mask.shape[0] == n ) if not is_valid: - raise ValueError("mask must be 3d np.ndarray with (n, H, W) shape") + raise ValueError( + f"mask must be a 3D np.ndarray with shape {expected_shape}, but got shape " + f"{actual_shape}") def validate_class_id(class_id: Any, n: int) -> None: + expected_shape = f"({n},)" + actual_shape = str(getattr(class_id, 'shape', None)) is_valid = class_id is None or ( isinstance(class_id, np.ndarray) and class_id.shape == (n,) ) if not is_valid: - raise ValueError("class_id must be None or 1d np.ndarray with (n,) shape") + raise ValueError( + f"class_id must be a 1D np.ndarray with shape {expected_shape}, but got " + f"shape {actual_shape}") def validate_confidence(confidence: Any, n: int) -> None: + expected_shape = f"({n},)" + actual_shape = str(getattr(confidence, 'shape', None)) is_valid = confidence is None or ( isinstance(confidence, np.ndarray) and confidence.shape == (n,) ) if not is_valid: - raise ValueError("confidence must be None or 1d np.ndarray with (n,) shape") + raise ValueError( + f"confidence must be a 1D np.ndarray with shape {expected_shape}, but got " + f"shape {actual_shape}") def validate_tracker_id(tracker_id: Any, n: int) -> None: + expected_shape = f"({n},)" + actual_shape = str(getattr(tracker_id, 'shape', None)) is_valid = tracker_id is None or ( isinstance(tracker_id, np.ndarray) and tracker_id.shape == (n,) ) if not is_valid: - raise ValueError("tracker_id must be None or 1d np.ndarray with (n,) shape") + raise ValueError( + f"tracker_id must be a 1D np.ndarray with shape {expected_shape}, but got " + f"shape {actual_shape}") -def validate_data(data: Dict[str, Union[np.ndarray, List]], n: int) -> None: +def validate_data(data: Dict[str, Any], n: int) -> None: for key, value in data.items(): if isinstance(value, list): if len(value) != n: @@ -526,6 +546,23 @@ def validate_data(data: Dict[str, Union[np.ndarray, List]], n: int) -> None: raise ValueError(f"Value for key '{key}' must be a list or np.ndarray") +def validate_detections_fields( + xyxy: Any, + mask: Any, + class_id: Any, + confidence: Any, + tracker_id: Any, + data: Dict[str, Any] +) -> None: + validate_xyxy(xyxy) + n = len(xyxy) + validate_mask(mask, n) + validate_class_id(class_id, n) + validate_confidence(confidence, n) + validate_tracker_id(tracker_id, n) + validate_data(data, n) + + def is_data_equal(data_a: Dict[str, np.ndarray], data_b: Dict[str, np.ndarray]) -> bool: """ Compares the data payloads of two Detections instances. diff --git a/test/detection/test_core.py b/test/detection/test_core.py index 40ab22a6..84cf8a98 100644 --- a/test/detection/test_core.py +++ b/test/detection/test_core.py @@ -143,17 +143,24 @@ def test_getitem( [ ([], Detections.empty(), DoesNotRaise()), # empty detections list ( - [Detections.empty()], + [ + Detections.empty() + ], Detections.empty(), DoesNotRaise(), ), # single empty detections ( - [mock_detections(xyxy=[[10, 10, 20, 20]])], + [ + mock_detections(xyxy=[[10, 10, 20, 20]]) + ], mock_detections(xyxy=[[10, 10, 20, 20]]), DoesNotRaise(), ), # single detection with xyxy field ( - [mock_detections(xyxy=[[10, 10, 20, 20]]), Detections.empty()], + [ + mock_detections(xyxy=[[10, 10, 20, 20]]), + mock_detections(xyxy=np.empty((0, 4), dtype=np.float32)), + ], mock_detections(xyxy=[[10, 10, 20, 20]]), DoesNotRaise(), ), # single detection with xyxy field + empty detection @@ -171,7 +178,7 @@ def test_getitem( mock_detections(xyxy=[[20, 20, 30, 30]]), ], mock_detections(xyxy=[[10, 10, 20, 20], [20, 20, 30, 30]]), - DoesNotRaise(), + pytest.raises(ValueError), ), # detection with xyxy, class_id fields + detection with xyxy field ( [ From e75e52792559a78358feedd6b9ca44b8d6f280b1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 29 Dec 2023 17:24:36 +0000 Subject: [PATCH 33/45] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto?= =?UTF-8?q?=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/core.py | 22 ++++++++++++---------- supervision/detection/utils.py | 27 ++++++++++++++++----------- test/detection/test_core.py | 8 ++------ 3 files changed, 30 insertions(+), 27 deletions(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index 1ce5599f..d66772f8 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -12,8 +12,8 @@ from supervision.detection.utils import ( merge_data, non_max_suppression, process_roboflow_result, - xywh_to_xyxy, validate_detections_fields, + xywh_to_xyxy, ) from supervision.geometry.core import Position @@ -53,7 +53,7 @@ class Detections: confidence=self.confidence, class_id=self.class_id, tracker_id=self.tracker_id, - data=self.data + data=self.data, ) def __len__(self): @@ -685,7 +685,7 @@ class Detections: confidence=detections.confidence, class_id=detections.class_id, tracker_id=detections.tracker_id, - data=detections.data + data=detections.data, ) xyxy = np.vstack([d.xyxy for d in detections_list]) @@ -695,14 +695,16 @@ class Detections: return None if any(d.__getattribute__(name) is None for d in detections_list): raise ValueError(f"All or none of the '{name}' fields must be None") - return np.vstack([d.__getattribute__(name) for d in detections_list]) \ - if name == 'mask' else np.hstack( - [d.__getattribute__(name) for d in detections_list]) + return ( + np.vstack([d.__getattribute__(name) for d in detections_list]) + if name == "mask" + else np.hstack([d.__getattribute__(name) for d in detections_list]) + ) - mask = stack_or_none('mask') - confidence = stack_or_none('confidence') - class_id = stack_or_none('class_id') - tracker_id = stack_or_none('tracker_id') + mask = stack_or_none("mask") + confidence = stack_or_none("confidence") + class_id = stack_or_none("class_id") + tracker_id = stack_or_none("tracker_id") data = merge_data([d.data for d in detections_list]) diff --git a/supervision/detection/utils.py b/supervision/detection/utils.py index 81f5983f..8ff34f4d 100644 --- a/supervision/detection/utils.py +++ b/supervision/detection/utils.py @@ -474,60 +474,65 @@ def calculate_masks_centroids(masks: np.ndarray) -> np.ndarray: def validate_xyxy(xyxy: Any) -> None: expected_shape = "(_, 4)" - actual_shape = str(getattr(xyxy, 'shape', None)) + actual_shape = str(getattr(xyxy, "shape", None)) is_valid = isinstance(xyxy, np.ndarray) and xyxy.ndim == 2 and xyxy.shape[1] == 4 if not is_valid: raise ValueError( f"xyxy must be a 2D np.ndarray with shape {expected_shape}, but got shape " - f"{actual_shape}") + f"{actual_shape}" + ) def validate_mask(mask: Any, n: int) -> None: expected_shape = f"({n}, H, W)" - actual_shape = str(getattr(mask, 'shape', None)) + actual_shape = str(getattr(mask, "shape", None)) is_valid = mask is None or ( isinstance(mask, np.ndarray) and len(mask.shape) == 3 and mask.shape[0] == n ) if not is_valid: raise ValueError( f"mask must be a 3D np.ndarray with shape {expected_shape}, but got shape " - f"{actual_shape}") + f"{actual_shape}" + ) def validate_class_id(class_id: Any, n: int) -> None: expected_shape = f"({n},)" - actual_shape = str(getattr(class_id, 'shape', None)) + actual_shape = str(getattr(class_id, "shape", None)) is_valid = class_id is None or ( isinstance(class_id, np.ndarray) and class_id.shape == (n,) ) if not is_valid: raise ValueError( f"class_id must be a 1D np.ndarray with shape {expected_shape}, but got " - f"shape {actual_shape}") + f"shape {actual_shape}" + ) def validate_confidence(confidence: Any, n: int) -> None: expected_shape = f"({n},)" - actual_shape = str(getattr(confidence, 'shape', None)) + actual_shape = str(getattr(confidence, "shape", None)) is_valid = confidence is None or ( isinstance(confidence, np.ndarray) and confidence.shape == (n,) ) if not is_valid: raise ValueError( f"confidence must be a 1D np.ndarray with shape {expected_shape}, but got " - f"shape {actual_shape}") + f"shape {actual_shape}" + ) def validate_tracker_id(tracker_id: Any, n: int) -> None: expected_shape = f"({n},)" - actual_shape = str(getattr(tracker_id, 'shape', None)) + actual_shape = str(getattr(tracker_id, "shape", None)) is_valid = tracker_id is None or ( isinstance(tracker_id, np.ndarray) and tracker_id.shape == (n,) ) if not is_valid: raise ValueError( f"tracker_id must be a 1D np.ndarray with shape {expected_shape}, but got " - f"shape {actual_shape}") + f"shape {actual_shape}" + ) def validate_data(data: Dict[str, Any], n: int) -> None: @@ -552,7 +557,7 @@ def validate_detections_fields( class_id: Any, confidence: Any, tracker_id: Any, - data: Dict[str, Any] + data: Dict[str, Any], ) -> None: validate_xyxy(xyxy) n = len(xyxy) diff --git a/test/detection/test_core.py b/test/detection/test_core.py index 84cf8a98..f3b739e8 100644 --- a/test/detection/test_core.py +++ b/test/detection/test_core.py @@ -143,16 +143,12 @@ def test_getitem( [ ([], Detections.empty(), DoesNotRaise()), # empty detections list ( - [ - Detections.empty() - ], + [Detections.empty()], Detections.empty(), DoesNotRaise(), ), # single empty detections ( - [ - mock_detections(xyxy=[[10, 10, 20, 20]]) - ], + [mock_detections(xyxy=[[10, 10, 20, 20]])], mock_detections(xyxy=[[10, 10, 20, 20]]), DoesNotRaise(), ), # single detection with xyxy field From d3cbad4aa1418232adc385d7a34d1e97fe29ec9e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 01:20:51 +0000 Subject: [PATCH 34/45] :arrow_up: Bump pytest from 7.4.3 to 7.4.4 Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.4.3 to 7.4.4. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/7.4.3...7.4.4) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 4867adc2..71e6beda 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2676,13 +2676,13 @@ tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} [[package]] name = "pytest" -version = "7.4.3" +version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, - {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, + {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, + {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, ] [package.dependencies] From ced9f35b6ee36fdd2b4441e48a6b45d99168e332 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 2 Jan 2024 15:05:20 +0100 Subject: [PATCH 35/45] initial `Detections.__getitem__` implementation --- supervision/detection/core.py | 3 +- supervision/detection/utils.py | 33 +++++++++ test/detection/test_utils.py | 131 +++++++++++++++++++++++++++++++++ 3 files changed, 166 insertions(+), 1 deletion(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index d66772f8..70eeac22 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -13,7 +13,7 @@ from supervision.detection.utils import ( non_max_suppression, process_roboflow_result, validate_detections_fields, - xywh_to_xyxy, + xywh_to_xyxy, get_data_item, ) from supervision.geometry.core import Position @@ -821,6 +821,7 @@ class Detections: confidence=self.confidence[index] if self.confidence is not None else None, class_id=self.class_id[index] if self.class_id is not None else None, tracker_id=self.tracker_id[index] if self.tracker_id is not None else None, + data=get_data_item(self.data, index), ) @property diff --git a/supervision/detection/utils.py b/supervision/detection/utils.py index 8ff34f4d..bc47d9cc 100644 --- a/supervision/detection/utils.py +++ b/supervision/detection/utils.py @@ -638,3 +638,36 @@ def merge_data( ) return merged_data + + +def get_data_item( + data: Dict[str, Union[np.ndarray, List]], + index: Union[int, slice, List[int], np.ndarray] +) -> Dict[str, Union[np.ndarray, List]]: + """ + Retrieve a subset of the data dictionary based on the given index. + + Args: + data: The data dictionary of the Detections object. + index: The index or indices specifying the subset to retrieve. + + Returns: + A subset of the data dictionary corresponding to the specified index. + """ + subset_data = {} + for key, value in data.items(): + if isinstance(value, np.ndarray): + subset_data[key] = value[index] + elif isinstance(value, list): + if isinstance(index, slice): + subset_data[key] = value[index] + elif isinstance(index, (list, np.ndarray)): + subset_data[key] = [value[i] for i in index] + elif isinstance(index, int): + subset_data[key] = [value[index]] + else: + raise TypeError(f"Unsupported index type: {type(index)}") + else: + raise TypeError(f"Unsupported data type for key '{key}': {type(value)}") + + return subset_data diff --git a/test/detection/test_utils.py b/test/detection/test_utils.py index 7cd87fb7..675680d7 100644 --- a/test/detection/test_utils.py +++ b/test/detection/test_utils.py @@ -13,6 +13,7 @@ from supervision.detection.utils import ( non_max_suppression, process_roboflow_result, scale_boxes, + get_data_item, ) TEST_MASK = np.zeros((1, 1000, 1000), dtype=bool) @@ -787,3 +788,133 @@ def test_merge_data( assert ( result[key] == expected_result[key] ), f"Mismatch in non-array data for key {key}" + + +@pytest.mark.parametrize( + "data, index, expected_result, exception", + [ + ( + {}, + 0, + {}, + DoesNotRaise() + ), # empty data dict + ( + { + "test_1": [1, 2, 3], + }, + 0, + { + "test_1": [1], + }, + DoesNotRaise() + ), # single data dict with a single field name and list values + ( + { + "test_1": np.array([1, 2, 3]), + }, + 0, + { + "test_1": np.array([1]), + }, + DoesNotRaise() + ), # single data dict with a single field name and np.array values as 1D arrays + ( + { + "test_1": [1, 2, 3], + }, + slice(0, 2), + { + "test_1": [1, 2], + }, + DoesNotRaise() + ), # single data dict with a single field name and list values + ( + { + "test_1": np.array([1, 2, 3]), + }, + slice(0, 2), + { + "test_1": np.array([1, 2]), + }, + DoesNotRaise() + ), # single data dict with a single field name and np.array values as 1D arrays + ( + { + "test_1": [1, 2, 3], + }, + -1, + { + "test_1": [3], + }, + DoesNotRaise() + ), # single data dict with a single field name and list values + ( + { + "test_1": np.array([1, 2, 3]), + }, + -1, + { + "test_1": np.array([3]), + }, + DoesNotRaise() + ), # single data dict with a single field name and np.array values as 1D arrays + ( + { + "test_1": [1, 2, 3], + }, + [0, 2], + { + "test_1": [1, 3], + }, + DoesNotRaise() + ), # single data dict with a single field name and list values + ( + { + "test_1": np.array([1, 2, 3]), + }, + [0, 2], + { + "test_1": np.array([1, 3]), + }, + DoesNotRaise() + ), # single data dict with a single field name and np.array values as 1D arrays + ( + { + "test_1": [1, 2, 3], + }, + np.array([0, 2]), + { + "test_1": [1, 3], + }, + DoesNotRaise() + ), # single data dict with a single field name and list values + ( + { + "test_1": np.array([1, 2, 3]), + }, + np.array([0, 2]), + { + "test_1": np.array([1, 3]), + }, + DoesNotRaise() + ), + ] +) +def test_get_data_item( + data: Dict[str, Any], + index: Any, + expected_result: Optional[Dict[str, Any]], + exception: Exception, +): + with exception: + result = get_data_item(data=data, index=index) + for key in result: + if isinstance(result[key], np.ndarray): + assert np.array_equal( + result[key], expected_result[key] + ), f"Mismatch in arrays for key {key}" + else: + assert ( + result[key] == expected_result[key] + ), f"Mismatch in non-array data for key {key}" From 51694819e3ac85883e9575bece6af978548db793 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 2 Jan 2024 14:05:34 +0000 Subject: [PATCH 36/45] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto?= =?UTF-8?q?=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/core.py | 3 ++- supervision/detection/utils.py | 2 +- test/detection/test_utils.py | 33 ++++++++++++++------------------- 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index 70eeac22..8d319302 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -8,12 +8,13 @@ import numpy as np from supervision.detection.utils import ( calculate_masks_centroids, extract_ultralytics_masks, + get_data_item, is_data_equal, merge_data, non_max_suppression, process_roboflow_result, validate_detections_fields, - xywh_to_xyxy, get_data_item, + xywh_to_xyxy, ) from supervision.geometry.core import Position diff --git a/supervision/detection/utils.py b/supervision/detection/utils.py index bc47d9cc..2df89a84 100644 --- a/supervision/detection/utils.py +++ b/supervision/detection/utils.py @@ -642,7 +642,7 @@ def merge_data( def get_data_item( data: Dict[str, Union[np.ndarray, List]], - index: Union[int, slice, List[int], np.ndarray] + index: Union[int, slice, List[int], np.ndarray], ) -> Dict[str, Union[np.ndarray, List]]: """ Retrieve a subset of the data dictionary based on the given index. diff --git a/test/detection/test_utils.py b/test/detection/test_utils.py index 675680d7..a24119ce 100644 --- a/test/detection/test_utils.py +++ b/test/detection/test_utils.py @@ -8,12 +8,12 @@ from supervision.detection.utils import ( calculate_masks_centroids, clip_boxes, filter_polygons_by_area, + get_data_item, merge_data, move_boxes, non_max_suppression, process_roboflow_result, scale_boxes, - get_data_item, ) TEST_MASK = np.zeros((1, 1000, 1000), dtype=bool) @@ -793,12 +793,7 @@ def test_merge_data( @pytest.mark.parametrize( "data, index, expected_result, exception", [ - ( - {}, - 0, - {}, - DoesNotRaise() - ), # empty data dict + ({}, 0, {}, DoesNotRaise()), # empty data dict ( { "test_1": [1, 2, 3], @@ -807,7 +802,7 @@ def test_merge_data( { "test_1": [1], }, - DoesNotRaise() + DoesNotRaise(), ), # single data dict with a single field name and list values ( { @@ -817,7 +812,7 @@ def test_merge_data( { "test_1": np.array([1]), }, - DoesNotRaise() + DoesNotRaise(), ), # single data dict with a single field name and np.array values as 1D arrays ( { @@ -827,7 +822,7 @@ def test_merge_data( { "test_1": [1, 2], }, - DoesNotRaise() + DoesNotRaise(), ), # single data dict with a single field name and list values ( { @@ -837,7 +832,7 @@ def test_merge_data( { "test_1": np.array([1, 2]), }, - DoesNotRaise() + DoesNotRaise(), ), # single data dict with a single field name and np.array values as 1D arrays ( { @@ -847,7 +842,7 @@ def test_merge_data( { "test_1": [3], }, - DoesNotRaise() + DoesNotRaise(), ), # single data dict with a single field name and list values ( { @@ -857,7 +852,7 @@ def test_merge_data( { "test_1": np.array([3]), }, - DoesNotRaise() + DoesNotRaise(), ), # single data dict with a single field name and np.array values as 1D arrays ( { @@ -867,7 +862,7 @@ def test_merge_data( { "test_1": [1, 3], }, - DoesNotRaise() + DoesNotRaise(), ), # single data dict with a single field name and list values ( { @@ -877,7 +872,7 @@ def test_merge_data( { "test_1": np.array([1, 3]), }, - DoesNotRaise() + DoesNotRaise(), ), # single data dict with a single field name and np.array values as 1D arrays ( { @@ -887,7 +882,7 @@ def test_merge_data( { "test_1": [1, 3], }, - DoesNotRaise() + DoesNotRaise(), ), # single data dict with a single field name and list values ( { @@ -897,9 +892,9 @@ def test_merge_data( { "test_1": np.array([1, 3]), }, - DoesNotRaise() + DoesNotRaise(), ), - ] + ], ) def test_get_data_item( data: Dict[str, Any], @@ -916,5 +911,5 @@ def test_get_data_item( ), f"Mismatch in arrays for key {key}" else: assert ( - result[key] == expected_result[key] + result[key] == expected_result[key] ), f"Mismatch in non-array data for key {key}" From a10b9840afa6ee1cd6b960b260d57bb22c8a2026 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 2 Jan 2024 15:58:44 +0100 Subject: [PATCH 37/45] initial `Detections.__iter__` implementation --- supervision/detection/core.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index 70eeac22..8ff5340c 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -71,11 +71,12 @@ class Detections: Optional[float], Optional[int], Optional[int], + Dict[str, Union[np.ndarray, List]], ] ]: """ Iterates over the Detections object and yield a tuple of - `(xyxy, mask, confidence, class_id, tracker_id)` for each detection. + `(xyxy, mask, confidence, class_id, tracker_id, data)` for each detection. """ for i in range(len(self.xyxy)): yield ( @@ -84,6 +85,7 @@ class Detections: self.confidence[i] if self.confidence is not None else None, self.class_id[i] if self.class_id is not None else None, self.tracker_id[i] if self.tracker_id is not None else None, + get_data_item(self.data, i), ) def __eq__(self, other: Detections): From 0f6396669b1e2f5d1861dc8aab5d90e943ded504 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 2 Jan 2024 17:37:26 +0100 Subject: [PATCH 38/45] initial `Detections.__setitem__` implementation + `Detections.__getitem__` changes --- supervision/detection/core.py | 41 +++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index 5216bb84..0385bddf 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -787,35 +787,41 @@ class Detections: raise ValueError(f"{anchor} is not supported.") def __getitem__( - self, index: Union[int, slice, List[int], np.ndarray] - ) -> Detections: + self, index: Union[int, slice, List[int], np.ndarray, str] + ) -> Union[Detections, List, np.ndarray, None]: """ - Get a subset of the Detections object. + Get a subset of the Detections object or access an item from its data field. + + When provided with an integer, slice, list of integers, or a numpy array, this + method returns a new Detections object that represents a subset of the original + detections. When provided with a string, it accesses the corresponding item in + the data dictionary. Args: - index (Union[int, slice, List[int], np.ndarray]): - The index or indices of the subset of the Detections + index (Union[int, slice, List[int], np.ndarray, str]): The index, indices, + or key to access a subset of the Detections or an item from the data. Returns: - (Detections): A subset of the Detections object. + Union[Detections, Any]: A subset of the Detections object or an item from + the data field. Example: - ```python + ``` >>> import supervision as sv >>> detections = sv.Detections(...) >>> first_detection = detections[0] - >>> first_10_detections = detections[0:10] - >>> some_detections = detections[[0, 2, 4]] - >>> class_0_detections = detections[detections.class_id == 0] - >>> high_confidence_detections = detections[detections.confidence > 0.5] + + >>> feature_vector = detections['feature_vector'] ``` """ + if isinstance(index, str): + return self.data.get(index) if isinstance(index, int): index = [index] return Detections( @@ -827,6 +833,19 @@ class Detections: data=get_data_item(self.data, index), ) + def __setitem__(self, key: str, value: Union[np.ndarray, List]): + """ + Set a value in the data dictionary of the Detections object. + + Args: + key (str): The key in the data dictionary to set. + value (Union[np.ndarray, List]): The value to set for the key. + """ + if not isinstance(value, (np.ndarray, list)): + raise TypeError("Value must be a np.ndarray or a list") + + self.data[key] = value + @property def area(self) -> np.ndarray: """ From e3d92a01c4eff23ffc2f3358d8ae144c61a6105b Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 2 Jan 2024 17:58:08 +0100 Subject: [PATCH 39/45] ready for colab tests --- supervision/detection/core.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index 0385bddf..201b4e9a 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -38,6 +38,14 @@ class Detections: data (Dict[str, Union[np.ndarray, List]]): A dictionary containing additional data where each key is a string representing the data type, and the value is either a NumPy array or a list of corresponding data. + + !!! warning + + The `data` field in the `sv.Detections` class is currently in an experimental + phase. Please be aware that its API and functionality are subject to change in + future updates as we continue to refine and improve its capabilities. + We encourage users to experiment with this feature and provide feedback, but + also to be prepared for potential modifications in upcoming releases. """ xyxy: np.ndarray @@ -806,7 +814,7 @@ class Detections: the data field. Example: - ``` + ```python >>> import supervision as sv >>> detections = sv.Detections(...) @@ -840,10 +848,33 @@ class Detections: Args: key (str): The key in the data dictionary to set. value (Union[np.ndarray, List]): The value to set for the key. + + Example: + ```python + >>> import cv2 + >>> from ultralytics import YOLO + >>> import supervision as sv + + >>> model = YOLO('yolov8s.pt') + + >>> image = cv2.imread(SOURCE_IMAGE_PATH) + + >>> result = model(image)[0] + >>> detections = sv.Detections.from_ultralytics(result) + + >>> detections['names'] = [ + ... model.model.names[class_id] + ... for class_id + ... in detections.class_id + ... ] + ``` """ if not isinstance(value, (np.ndarray, list)): raise TypeError("Value must be a np.ndarray or a list") + if isinstance(value, list): + value = np.array(value) + self.data[key] = value @property From 706ff0fd460cb187cfba2f57f4e0d6017548c4c7 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 2 Jan 2024 21:00:17 +0100 Subject: [PATCH 40/45] initial update of `process_roboflow_result` function --- supervision/detection/utils.py | 18 ++++++++++++--- test/detection/test_utils.py | 40 +++++++++++++++++++++++++++++++--- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/supervision/detection/utils.py b/supervision/detection/utils.py index 2df89a84..3b9d2cb8 100644 --- a/supervision/detection/utils.py +++ b/supervision/detection/utils.py @@ -331,13 +331,21 @@ def extract_ultralytics_masks(yolov8_results) -> Optional[np.ndarray]: def process_roboflow_result( roboflow_result: dict, -) -> Tuple[np.ndarray, np.ndarray, np.ndarray, Optional[np.ndarray], np.ndarray]: +) -> Tuple[ + np.ndarray, + np.ndarray, + np.ndarray, + Optional[np.ndarray], + np.ndarray, + Dict[str, List[np.ndarray]] +]: if not roboflow_result["predictions"]: - return np.empty((0, 4)), np.empty(0), np.empty(0), None, None + return np.empty((0, 4)), np.empty(0), np.empty(0), None, None, {"class_name": np.empty(0)} xyxy = [] confidence = [] class_id = [] + class_name = [] masks = [] tracker_ids = [] @@ -357,6 +365,7 @@ def process_roboflow_result( if "points" not in prediction: xyxy.append([x_min, y_min, x_max, y_max]) class_id.append(prediction["class_id"]) + class_name.append(prediction["class"]) confidence.append(prediction["confidence"]) if "tracker_id" in prediction: tracker_ids.append(prediction["tracker_id"]) @@ -367,6 +376,7 @@ def process_roboflow_result( mask = polygon_to_mask(polygon, resolution_wh=(image_width, image_height)) xyxy.append([x_min, y_min, x_max, y_max]) class_id.append(prediction["class_id"]) + class_name.append(prediction["class"]) confidence.append(prediction["confidence"]) masks.append(mask) if "tracker_id" in prediction: @@ -375,10 +385,12 @@ def process_roboflow_result( xyxy = np.array(xyxy) if len(xyxy) > 0 else np.empty((0, 4)) confidence = np.array(confidence) if len(confidence) > 0 else np.empty(0) class_id = np.array(class_id).astype(int) if len(class_id) > 0 else np.empty(0) + class_name = np.array(class_name) if len(class_name) > 0 else np.empty(0) masks = np.array(masks, dtype=bool) if len(masks) > 0 else None tracker_id = np.array(tracker_ids).astype(int) if len(tracker_ids) > 0 else None + data = {"class_name": class_name} - return xyxy, confidence, class_id, masks, tracker_id + return xyxy, confidence, class_id, masks, tracker_id, data def move_boxes(xyxy: np.ndarray, offset: np.ndarray) -> np.ndarray: diff --git a/test/detection/test_utils.py b/test/detection/test_utils.py index a24119ce..423bde54 100644 --- a/test/detection/test_utils.py +++ b/test/detection/test_utils.py @@ -268,7 +268,14 @@ def test_filter_polygons_by_area( [ ( {"predictions": [], "image": {"width": 1000, "height": 1000}}, - (np.empty((0, 4)), np.empty(0), np.empty(0), None, None), + ( + np.empty((0, 4)), + np.empty(0), + np.empty(0), + None, + None, + {"class_name": np.empty(0)} + ), DoesNotRaise(), ), # empty result ( @@ -292,6 +299,7 @@ def test_filter_polygons_by_area( np.array([0]), None, None, + {"class_name": np.array(["person"])} ), DoesNotRaise(), ), # single correct object detection result @@ -327,6 +335,7 @@ def test_filter_polygons_by_area( np.array([0, 7]), None, np.array([1, 2]), + {"class_name": np.array(["person", "truck"])} ), DoesNotRaise(), ), # two correct object detection result @@ -347,7 +356,14 @@ def test_filter_polygons_by_area( ], "image": {"width": 1000, "height": 1000}, }, - (np.empty((0, 4)), np.empty(0), np.empty(0), None, None), + ( + np.empty((0, 4)), + np.empty(0), + np.empty(0), + None, + None, + {"class_name": np.empty(0)} + ), DoesNotRaise(), ), # single incorrect instance segmentation result with no points ( @@ -366,7 +382,14 @@ def test_filter_polygons_by_area( ], "image": {"width": 1000, "height": 1000}, }, - (np.empty((0, 4)), np.empty(0), np.empty(0), None, None), + ( + np.empty((0, 4)), + np.empty(0), + np.empty(0), + None, + None, + {"class_name": np.empty(0)} + ), DoesNotRaise(), ), # single incorrect instance segmentation result with no enough points ( @@ -396,6 +419,7 @@ def test_filter_polygons_by_area( np.array([0]), TEST_MASK, None, + {"class_name": np.array(["person"])} ), DoesNotRaise(), ), # single incorrect instance segmentation result with no enough points @@ -436,6 +460,7 @@ def test_filter_polygons_by_area( np.array([0]), TEST_MASK, None, + {"class_name": np.array(["person"])} ), DoesNotRaise(), ), # two instance segmentation results - one correct, one incorrect @@ -459,6 +484,15 @@ def test_process_roboflow_result( assert (result[4] is None and expected_result[4] is None) or ( np.array_equal(result[4], expected_result[4]) ) + for key in result[5]: + if isinstance(result[5][key], np.ndarray): + assert np.array_equal( + result[5][key], expected_result[5][key] + ), f"Mismatch in arrays for key {key}" + else: + assert ( + result[5][key] == expected_result[5][key] + ), f"Mismatch in non-array data for key {key}" @pytest.mark.parametrize( From d35f1888f9152d4d86541d079f1f1452052d6b76 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 2 Jan 2024 20:02:03 +0000 Subject: [PATCH 41/45] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto?= =?UTF-8?q?=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/detection/utils.py | 11 +++++++++-- test/detection/test_utils.py | 14 +++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/supervision/detection/utils.py b/supervision/detection/utils.py index 3b9d2cb8..7bc84e4a 100644 --- a/supervision/detection/utils.py +++ b/supervision/detection/utils.py @@ -337,10 +337,17 @@ def process_roboflow_result( np.ndarray, Optional[np.ndarray], np.ndarray, - Dict[str, List[np.ndarray]] + Dict[str, List[np.ndarray]], ]: if not roboflow_result["predictions"]: - return np.empty((0, 4)), np.empty(0), np.empty(0), None, None, {"class_name": np.empty(0)} + return ( + np.empty((0, 4)), + np.empty(0), + np.empty(0), + None, + None, + {"class_name": np.empty(0)}, + ) xyxy = [] confidence = [] diff --git a/test/detection/test_utils.py b/test/detection/test_utils.py index 423bde54..e40a1620 100644 --- a/test/detection/test_utils.py +++ b/test/detection/test_utils.py @@ -274,7 +274,7 @@ def test_filter_polygons_by_area( np.empty(0), None, None, - {"class_name": np.empty(0)} + {"class_name": np.empty(0)}, ), DoesNotRaise(), ), # empty result @@ -299,7 +299,7 @@ def test_filter_polygons_by_area( np.array([0]), None, None, - {"class_name": np.array(["person"])} + {"class_name": np.array(["person"])}, ), DoesNotRaise(), ), # single correct object detection result @@ -335,7 +335,7 @@ def test_filter_polygons_by_area( np.array([0, 7]), None, np.array([1, 2]), - {"class_name": np.array(["person", "truck"])} + {"class_name": np.array(["person", "truck"])}, ), DoesNotRaise(), ), # two correct object detection result @@ -362,7 +362,7 @@ def test_filter_polygons_by_area( np.empty(0), None, None, - {"class_name": np.empty(0)} + {"class_name": np.empty(0)}, ), DoesNotRaise(), ), # single incorrect instance segmentation result with no points @@ -388,7 +388,7 @@ def test_filter_polygons_by_area( np.empty(0), None, None, - {"class_name": np.empty(0)} + {"class_name": np.empty(0)}, ), DoesNotRaise(), ), # single incorrect instance segmentation result with no enough points @@ -419,7 +419,7 @@ def test_filter_polygons_by_area( np.array([0]), TEST_MASK, None, - {"class_name": np.array(["person"])} + {"class_name": np.array(["person"])}, ), DoesNotRaise(), ), # single incorrect instance segmentation result with no enough points @@ -460,7 +460,7 @@ def test_filter_polygons_by_area( np.array([0]), TEST_MASK, None, - {"class_name": np.array(["person"])} + {"class_name": np.array(["person"])}, ), DoesNotRaise(), ), # two instance segmentation results - one correct, one incorrect From 0d107043700877a8734a17c4b89b47db076e1e69 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 3 Jan 2024 06:50:18 +0100 Subject: [PATCH 42/45] improve `from_roboflow` docstring --- supervision/detection/core.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/supervision/detection/core.py b/supervision/detection/core.py index 201b4e9a..0396a386 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -397,7 +397,14 @@ class Detections: def from_roboflow(cls, roboflow_result: dict) -> Detections: """ Create a Detections object from the [Roboflow](https://roboflow.com/) - API inference result. + API inference result. This method extracts bounding boxes, class IDs, + confidences, and class names from the Roboflow API result and encapsulates + them into a Detections object. + + !!! note + + Class names can be accessed using the key 'class_name' in the returned + object's data attribute. Args: roboflow_result (dict): The result from the @@ -427,9 +434,10 @@ class Detections: ... } >>> detections = sv.Detections.from_roboflow(roboflow_result) + >>> detections['class_name'] ``` """ - xyxy, confidence, class_id, masks, trackers = process_roboflow_result( + xyxy, confidence, class_id, masks, trackers, data = process_roboflow_result( roboflow_result=roboflow_result ) @@ -442,6 +450,7 @@ class Detections: class_id=class_id, mask=masks, tracker_id=trackers, + data=data, ) @classmethod From f4d2f2a900b2c3f3a40ff74b492f5f2c4fb64e83 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 3 Jan 2024 07:22:48 +0100 Subject: [PATCH 43/45] use `CLASS_NAME_DATA_FIELD` data constant over 'class_name' string --- supervision/config.py | 1 + supervision/detection/utils.py | 8 +++++--- test/detection/test_utils.py | 15 ++++++++------- 3 files changed, 14 insertions(+), 10 deletions(-) create mode 100644 supervision/config.py diff --git a/supervision/config.py b/supervision/config.py new file mode 100644 index 00000000..aee864d0 --- /dev/null +++ b/supervision/config.py @@ -0,0 +1 @@ +CLASS_NAME_DATA_FIELD = 'class_name' diff --git a/supervision/detection/utils.py b/supervision/detection/utils.py index 7bc84e4a..f7af7797 100644 --- a/supervision/detection/utils.py +++ b/supervision/detection/utils.py @@ -4,6 +4,8 @@ from typing import Any, Dict, List, Optional, Tuple, Union import cv2 import numpy as np +from supervision.config import CLASS_NAME_DATA_FIELD + MIN_POLYGON_POINT_COUNT = 3 @@ -336,7 +338,7 @@ def process_roboflow_result( np.ndarray, np.ndarray, Optional[np.ndarray], - np.ndarray, + Optional[np.ndarray], Dict[str, List[np.ndarray]], ]: if not roboflow_result["predictions"]: @@ -346,7 +348,7 @@ def process_roboflow_result( np.empty(0), None, None, - {"class_name": np.empty(0)}, + {CLASS_NAME_DATA_FIELD: np.empty(0)}, ) xyxy = [] @@ -395,7 +397,7 @@ def process_roboflow_result( class_name = np.array(class_name) if len(class_name) > 0 else np.empty(0) masks = np.array(masks, dtype=bool) if len(masks) > 0 else None tracker_id = np.array(tracker_ids).astype(int) if len(tracker_ids) > 0 else None - data = {"class_name": class_name} + data = {CLASS_NAME_DATA_FIELD: class_name} return xyxy, confidence, class_id, masks, tracker_id, data diff --git a/test/detection/test_utils.py b/test/detection/test_utils.py index e40a1620..576b25f8 100644 --- a/test/detection/test_utils.py +++ b/test/detection/test_utils.py @@ -4,6 +4,7 @@ from typing import Any, Dict, List, Optional, Tuple import numpy as np import pytest +from supervision.config import CLASS_NAME_DATA_FIELD from supervision.detection.utils import ( calculate_masks_centroids, clip_boxes, @@ -274,7 +275,7 @@ def test_filter_polygons_by_area( np.empty(0), None, None, - {"class_name": np.empty(0)}, + {CLASS_NAME_DATA_FIELD: np.empty(0)}, ), DoesNotRaise(), ), # empty result @@ -299,7 +300,7 @@ def test_filter_polygons_by_area( np.array([0]), None, None, - {"class_name": np.array(["person"])}, + {CLASS_NAME_DATA_FIELD: np.array(["person"])}, ), DoesNotRaise(), ), # single correct object detection result @@ -335,7 +336,7 @@ def test_filter_polygons_by_area( np.array([0, 7]), None, np.array([1, 2]), - {"class_name": np.array(["person", "truck"])}, + {CLASS_NAME_DATA_FIELD: np.array(["person", "truck"])}, ), DoesNotRaise(), ), # two correct object detection result @@ -362,7 +363,7 @@ def test_filter_polygons_by_area( np.empty(0), None, None, - {"class_name": np.empty(0)}, + {CLASS_NAME_DATA_FIELD: np.empty(0)}, ), DoesNotRaise(), ), # single incorrect instance segmentation result with no points @@ -388,7 +389,7 @@ def test_filter_polygons_by_area( np.empty(0), None, None, - {"class_name": np.empty(0)}, + {CLASS_NAME_DATA_FIELD: np.empty(0)}, ), DoesNotRaise(), ), # single incorrect instance segmentation result with no enough points @@ -419,7 +420,7 @@ def test_filter_polygons_by_area( np.array([0]), TEST_MASK, None, - {"class_name": np.array(["person"])}, + {CLASS_NAME_DATA_FIELD: np.array(["person"])}, ), DoesNotRaise(), ), # single incorrect instance segmentation result with no enough points @@ -460,7 +461,7 @@ def test_filter_polygons_by_area( np.array([0]), TEST_MASK, None, - {"class_name": np.array(["person"])}, + {CLASS_NAME_DATA_FIELD: np.array(["person"])}, ), DoesNotRaise(), ), # two instance segmentation results - one correct, one incorrect From 80d1df75094781ce6e7a53434b381c465d6980ca Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 3 Jan 2024 06:23:01 +0000 Subject: [PATCH 44/45] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto?= =?UTF-8?q?=20format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supervision/config.py b/supervision/config.py index aee864d0..d1a83b55 100644 --- a/supervision/config.py +++ b/supervision/config.py @@ -1 +1 @@ -CLASS_NAME_DATA_FIELD = 'class_name' +CLASS_NAME_DATA_FIELD = "class_name" From 11cd92e4f3533be90ea7f48461ad12c76e21e30b Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 3 Jan 2024 08:19:14 +0100 Subject: [PATCH 45/45] update `sv.LabelAnnotator` to respect optional `'class_name'` `sv.Detections.data` field --- supervision/annotators/core.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/supervision/annotators/core.py b/supervision/annotators/core.py index fc3cf7f2..01a0920c 100644 --- a/supervision/annotators/core.py +++ b/supervision/annotators/core.py @@ -6,6 +6,7 @@ import numpy as np from supervision.annotators.base import BaseAnnotator from supervision.annotators.utils import ColorLookup, Trace, resolve_color +from supervision.config import CLASS_NAME_DATA_FIELD from supervision.detection.core import Detections from supervision.detection.utils import clip_boxes, mask_to_polygons from supervision.draw.color import Color, ColorPalette @@ -890,6 +891,16 @@ class LabelAnnotator: anchors_coordinates = detections.get_anchors_coordinates( anchor=self.text_anchor ).astype(int) + if labels is not None and len(labels) != len(detections): + raise ValueError( + f"The number of labels provided ({len(labels)}) does not match the " + f"number of detections ({len(detections)}). Each detection should have " + f"a corresponding label. This discrepancy can occur if the labels and " + f"detections are not aligned or if an incorrect number of labels has " + f"been provided. Please ensure that the labels array has the same " + f"length as the Detections object." + ) + for detection_idx, center_coordinates in enumerate(anchors_coordinates): color = resolve_color( color=self.color, @@ -899,11 +910,16 @@ class LabelAnnotator: if custom_color_lookup is None else custom_color_lookup, ) - text = ( - f"{detections.class_id[detection_idx]}" - if (labels is None or len(detections) != len(labels)) - else labels[detection_idx] - ) + + if labels is not None: + text = labels[detection_idx] + elif detections[CLASS_NAME_DATA_FIELD] is not None: + text = detections[CLASS_NAME_DATA_FIELD][detection_idx] + elif detections.class_id is not None: + text = str(detections.class_id[detection_idx]) + else: + text = str(detection_idx) + text_w, text_h = cv2.getTextSize( text=text, fontFace=font,