diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d010db4d..6b46c50c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,3 @@ -default_language_version: - python: python3.8 ci: autofix_prs: true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b7dc93bd..62838aec 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -24,7 +24,7 @@ Before you contribute a new feature, consider submitting an Issue to discuss the ## How to Contribute Changes -First, fork this repository to your own GitHub account. Create a new branch that describes your changes (i.e. `line-counter-docs`). Push your changes to the branch on your fork and then submit a pull request to this repository. +First, fork this repository to your own GitHub account. Create a new branch that describes your changes (i.e. `line-counter-docs`). Push your changes to the branch on your fork and then submit a pull request to `develop` branch of this repository. When creating new functions, please ensure you have the following: @@ -32,6 +32,7 @@ When creating new functions, please ensure you have the following: 2. Unit tests for the function. 3. Examples in the documentation for the function. 4. Created an entry in our docs to autogenerate the documentation for the function. +5. Please share google colab with minimal code to test new feature or reproduce PR whenever it is possible. Please ensure that google colab can be accessed without any issue. All pull requests will be reviewed by the maintainers of the project. We will provide feedback and ask for changes if necessary. diff --git a/poetry.lock b/poetry.lock index fa38d3ab..ae9d1338 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2156,54 +2156,83 @@ files = [ [[package]] name = "pillow" -version = "8.4.0" +version = "9.5.0" description = "Python Imaging Library (Fork)" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "Pillow-8.4.0-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:81f8d5c81e483a9442d72d182e1fb6dcb9723f289a57e8030811bac9ea3fef8d"}, - {file = "Pillow-8.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3f97cfb1e5a392d75dd8b9fd274d205404729923840ca94ca45a0af57e13dbe6"}, - {file = "Pillow-8.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb9fc393f3c61f9054e1ed26e6fe912c7321af2f41ff49d3f83d05bacf22cc78"}, - {file = "Pillow-8.4.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d82cdb63100ef5eedb8391732375e6d05993b765f72cb34311fab92103314649"}, - {file = "Pillow-8.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62cc1afda735a8d109007164714e73771b499768b9bb5afcbbee9d0ff374b43f"}, - {file = "Pillow-8.4.0-cp310-cp310-win32.whl", hash = "sha256:e3dacecfbeec9a33e932f00c6cd7996e62f53ad46fbe677577394aaa90ee419a"}, - {file = "Pillow-8.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:620582db2a85b2df5f8a82ddeb52116560d7e5e6b055095f04ad828d1b0baa39"}, - {file = "Pillow-8.4.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:1bc723b434fbc4ab50bb68e11e93ce5fb69866ad621e3c2c9bdb0cd70e345f55"}, - {file = "Pillow-8.4.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72cbcfd54df6caf85cc35264c77ede902452d6df41166010262374155947460c"}, - {file = "Pillow-8.4.0-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70ad9e5c6cb9b8487280a02c0ad8a51581dcbbe8484ce058477692a27c151c0a"}, - {file = "Pillow-8.4.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25a49dc2e2f74e65efaa32b153527fc5ac98508d502fa46e74fa4fd678ed6645"}, - {file = "Pillow-8.4.0-cp36-cp36m-win32.whl", hash = "sha256:93ce9e955cc95959df98505e4608ad98281fff037350d8c2671c9aa86bcf10a9"}, - {file = "Pillow-8.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:2e4440b8f00f504ee4b53fe30f4e381aae30b0568193be305256b1462216feff"}, - {file = "Pillow-8.4.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:8c803ac3c28bbc53763e6825746f05cc407b20e4a69d0122e526a582e3b5e153"}, - {file = "Pillow-8.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8a17b5d948f4ceeceb66384727dde11b240736fddeda54ca740b9b8b1556b29"}, - {file = "Pillow-8.4.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1394a6ad5abc838c5cd8a92c5a07535648cdf6d09e8e2d6df916dfa9ea86ead8"}, - {file = "Pillow-8.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:792e5c12376594bfcb986ebf3855aa4b7c225754e9a9521298e460e92fb4a488"}, - {file = "Pillow-8.4.0-cp37-cp37m-win32.whl", hash = "sha256:d99ec152570e4196772e7a8e4ba5320d2d27bf22fdf11743dd882936ed64305b"}, - {file = "Pillow-8.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:7b7017b61bbcdd7f6363aeceb881e23c46583739cb69a3ab39cb384f6ec82e5b"}, - {file = "Pillow-8.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:d89363f02658e253dbd171f7c3716a5d340a24ee82d38aab9183f7fdf0cdca49"}, - {file = "Pillow-8.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0a0956fdc5defc34462bb1c765ee88d933239f9a94bc37d132004775241a7585"}, - {file = "Pillow-8.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b7bb9de00197fb4261825c15551adf7605cf14a80badf1761d61e59da347779"}, - {file = "Pillow-8.4.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72b9e656e340447f827885b8d7a15fc8c4e68d410dc2297ef6787eec0f0ea409"}, - {file = "Pillow-8.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5a4532a12314149d8b4e4ad8ff09dde7427731fcfa5917ff16d0291f13609df"}, - {file = "Pillow-8.4.0-cp38-cp38-win32.whl", hash = "sha256:82aafa8d5eb68c8463b6e9baeb4f19043bb31fefc03eb7b216b51e6a9981ae09"}, - {file = "Pillow-8.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:066f3999cb3b070a95c3652712cffa1a748cd02d60ad7b4e485c3748a04d9d76"}, - {file = "Pillow-8.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:5503c86916d27c2e101b7f71c2ae2cddba01a2cf55b8395b0255fd33fa4d1f1a"}, - {file = "Pillow-8.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4acc0985ddf39d1bc969a9220b51d94ed51695d455c228d8ac29fcdb25810e6e"}, - {file = "Pillow-8.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b052a619a8bfcf26bd8b3f48f45283f9e977890263e4571f2393ed8898d331b"}, - {file = "Pillow-8.4.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:493cb4e415f44cd601fcec11c99836f707bb714ab03f5ed46ac25713baf0ff20"}, - {file = "Pillow-8.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8831cb7332eda5dc89b21a7bce7ef6ad305548820595033a4b03cf3091235ed"}, - {file = "Pillow-8.4.0-cp39-cp39-win32.whl", hash = "sha256:5e9ac5f66616b87d4da618a20ab0a38324dbe88d8a39b55be8964eb520021e02"}, - {file = "Pillow-8.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:3eb1ce5f65908556c2d8685a8f0a6e989d887ec4057326f6c22b24e8a172c66b"}, - {file = "Pillow-8.4.0-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ddc4d832a0f0b4c52fff973a0d44b6c99839a9d016fe4e6a1cb8f3eea96479c2"}, - {file = "Pillow-8.4.0-pp36-pypy36_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a3e5ddc44c14042f0844b8cf7d2cd455f6cc80fd7f5eefbe657292cf601d9ad"}, - {file = "Pillow-8.4.0-pp36-pypy36_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c70e94281588ef053ae8998039610dbd71bc509e4acbc77ab59d7d2937b10698"}, - {file = "Pillow-8.4.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:3862b7256046fcd950618ed22d1d60b842e3a40a48236a5498746f21189afbbc"}, - {file = "Pillow-8.4.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4901622493f88b1a29bd30ec1a2f683782e57c3c16a2dbc7f2595ba01f639df"}, - {file = "Pillow-8.4.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84c471a734240653a0ec91dec0996696eea227eafe72a33bd06c92697728046b"}, - {file = "Pillow-8.4.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:244cf3b97802c34c41905d22810846802a3329ddcb93ccc432870243211c79fc"}, - {file = "Pillow-8.4.0.tar.gz", hash = "sha256:b8e2f83c56e141920c39464b852de3719dfbfb6e3c99a2d8da0edf4fb33176ed"}, + {file = "Pillow-9.5.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:ace6ca218308447b9077c14ea4ef381ba0b67ee78d64046b3f19cf4e1139ad16"}, + {file = "Pillow-9.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3d403753c9d5adc04d4694d35cf0391f0f3d57c8e0030aac09d7678fa8030aa"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ba1b81ee69573fe7124881762bb4cd2e4b6ed9dd28c9c60a632902fe8db8b38"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe7e1c262d3392afcf5071df9afa574544f28eac825284596ac6db56e6d11062"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f36397bf3f7d7c6a3abdea815ecf6fd14e7fcd4418ab24bae01008d8d8ca15e"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:252a03f1bdddce077eff2354c3861bf437c892fb1832f75ce813ee94347aa9b5"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:85ec677246533e27770b0de5cf0f9d6e4ec0c212a1f89dfc941b64b21226009d"}, + {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b416f03d37d27290cb93597335a2f85ed446731200705b22bb927405320de903"}, + {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1781a624c229cb35a2ac31cc4a77e28cafc8900733a864870c49bfeedacd106a"}, + {file = "Pillow-9.5.0-cp310-cp310-win32.whl", hash = "sha256:8507eda3cd0608a1f94f58c64817e83ec12fa93a9436938b191b80d9e4c0fc44"}, + {file = "Pillow-9.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:d3c6b54e304c60c4181da1c9dadf83e4a54fd266a99c70ba646a9baa626819eb"}, + {file = "Pillow-9.5.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:7ec6f6ce99dab90b52da21cf0dc519e21095e332ff3b399a357c187b1a5eee32"}, + {file = "Pillow-9.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:560737e70cb9c6255d6dcba3de6578a9e2ec4b573659943a5e7e4af13f298f5c"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96e88745a55b88a7c64fa49bceff363a1a27d9a64e04019c2281049444a571e3"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d9c206c29b46cfd343ea7cdfe1232443072bbb270d6a46f59c259460db76779a"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfcc2c53c06f2ccb8976fb5c71d448bdd0a07d26d8e07e321c103416444c7ad1"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:a0f9bb6c80e6efcde93ffc51256d5cfb2155ff8f78292f074f60f9e70b942d99"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8d935f924bbab8f0a9a28404422da8af4904e36d5c33fc6f677e4c4485515625"}, + {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fed1e1cf6a42577953abbe8e6cf2fe2f566daebde7c34724ec8803c4c0cda579"}, + {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c1170d6b195555644f0616fd6ed929dfcf6333b8675fcca044ae5ab110ded296"}, + {file = "Pillow-9.5.0-cp311-cp311-win32.whl", hash = "sha256:54f7102ad31a3de5666827526e248c3530b3a33539dbda27c6843d19d72644ec"}, + {file = "Pillow-9.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfa4561277f677ecf651e2b22dc43e8f5368b74a25a8f7d1d4a3a243e573f2d4"}, + {file = "Pillow-9.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:965e4a05ef364e7b973dd17fc765f42233415974d773e82144c9bbaaaea5d089"}, + {file = "Pillow-9.5.0-cp312-cp312-win32.whl", hash = "sha256:22baf0c3cf0c7f26e82d6e1adf118027afb325e703922c8dfc1d5d0156bb2eeb"}, + {file = "Pillow-9.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:432b975c009cf649420615388561c0ce7cc31ce9b2e374db659ee4f7d57a1f8b"}, + {file = "Pillow-9.5.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:5d4ebf8e1db4441a55c509c4baa7a0587a0210f7cd25fcfe74dbbce7a4bd1906"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:375f6e5ee9620a271acb6820b3d1e94ffa8e741c0601db4c0c4d3cb0a9c224bf"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99eb6cafb6ba90e436684e08dad8be1637efb71c4f2180ee6b8f940739406e78"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfaaf10b6172697b9bceb9a3bd7b951819d1ca339a5ef294d1f1ac6d7f63270"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:763782b2e03e45e2c77d7779875f4432e25121ef002a41829d8868700d119392"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:35f6e77122a0c0762268216315bf239cf52b88865bba522999dc38f1c52b9b47"}, + {file = "Pillow-9.5.0-cp37-cp37m-win32.whl", hash = "sha256:aca1c196f407ec7cf04dcbb15d19a43c507a81f7ffc45b690899d6a76ac9fda7"}, + {file = "Pillow-9.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322724c0032af6692456cd6ed554bb85f8149214d97398bb80613b04e33769f6"}, + {file = "Pillow-9.5.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:a0aa9417994d91301056f3d0038af1199eb7adc86e646a36b9e050b06f526597"}, + {file = "Pillow-9.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8286396b351785801a976b1e85ea88e937712ee2c3ac653710a4a57a8da5d9c"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c830a02caeb789633863b466b9de10c015bded434deb3ec87c768e53752ad22a"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fbd359831c1657d69bb81f0db962905ee05e5e9451913b18b831febfe0519082"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8fc330c3370a81bbf3f88557097d1ea26cd8b019d6433aa59f71195f5ddebbf"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:7002d0797a3e4193c7cdee3198d7c14f92c0836d6b4a3f3046a64bd1ce8df2bf"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:229e2c79c00e85989a34b5981a2b67aa079fd08c903f0aaead522a1d68d79e51"}, + {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9adf58f5d64e474bed00d69bcd86ec4bcaa4123bfa70a65ce72e424bfb88ed96"}, + {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:662da1f3f89a302cc22faa9f14a262c2e3951f9dbc9617609a47521c69dd9f8f"}, + {file = "Pillow-9.5.0-cp38-cp38-win32.whl", hash = "sha256:6608ff3bf781eee0cd14d0901a2b9cc3d3834516532e3bd673a0a204dc8615fc"}, + {file = "Pillow-9.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:e49eb4e95ff6fd7c0c402508894b1ef0e01b99a44320ba7d8ecbabefddcc5569"}, + {file = "Pillow-9.5.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:482877592e927fd263028c105b36272398e3e1be3269efda09f6ba21fd83ec66"}, + {file = "Pillow-9.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3ded42b9ad70e5f1754fb7c2e2d6465a9c842e41d178f262e08b8c85ed8a1d8e"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c446d2245ba29820d405315083d55299a796695d747efceb5717a8b450324115"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aca1152d93dcc27dc55395604dcfc55bed5f25ef4c98716a928bacba90d33a3"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:608488bdcbdb4ba7837461442b90ea6f3079397ddc968c31265c1e056964f1ef"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:60037a8db8750e474af7ffc9faa9b5859e6c6d0a50e55c45576bf28be7419705"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:07999f5834bdc404c442146942a2ecadd1cb6292f5229f4ed3b31e0a108746b1"}, + {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a127ae76092974abfbfa38ca2d12cbeddcdeac0fb71f9627cc1135bedaf9d51a"}, + {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:489f8389261e5ed43ac8ff7b453162af39c3e8abd730af8363587ba64bb2e865"}, + {file = "Pillow-9.5.0-cp39-cp39-win32.whl", hash = "sha256:9b1af95c3a967bf1da94f253e56b6286b50af23392a886720f563c547e48e964"}, + {file = "Pillow-9.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:77165c4a5e7d5a284f10a6efaa39a0ae8ba839da344f20b111d62cc932fa4e5d"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:833b86a98e0ede388fa29363159c9b1a294b0905b5128baf01db683672f230f5"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aaf305d6d40bd9632198c766fb64f0c1a83ca5b667f16c1e79e1661ab5060140"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0852ddb76d85f127c135b6dd1f0bb88dbb9ee990d2cd9aa9e28526c93e794fba"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:91ec6fe47b5eb5a9968c79ad9ed78c342b1f97a091677ba0e012701add857829"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cb841572862f629b99725ebaec3287fc6d275be9b14443ea746c1dd325053cbd"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c380b27d041209b849ed246b111b7c166ba36d7933ec6e41175fd15ab9eb1572"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c9af5a3b406a50e313467e3565fc99929717f780164fe6fbb7704edba0cebbe"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5671583eab84af046a397d6d0ba25343c00cd50bce03787948e0fff01d4fd9b1"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:84a6f19ce086c1bf894644b43cd129702f781ba5751ca8572f08aa40ef0ab7b7"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1e7723bd90ef94eda669a3c2c19d549874dd5badaeefabefd26053304abe5799"}, + {file = "Pillow-9.5.0.tar.gz", hash = "sha256:bf548479d336726d7a0eceb6e767e179fbde37833ae42794602631a070d630f1"}, ] +[package.extras] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] + [[package]] name = "pkginfo" version = "1.9.6" @@ -3443,4 +3472,4 @@ desktop = ["opencv-python"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<3.12.0" -content-hash = "be27f05c8857580f327c9d6b89216524c9b8cacf662a24b1d5d4147e4a194f81" +content-hash = "4917c08576fa8226c0593bac637f8442171d16cb50912a0cfe225959dcaa4e5e" diff --git a/pyproject.toml b/pyproject.toml index 20df1bbf..ba6a7b30 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,6 +5,7 @@ description = "A set of easy-to-use utils that will come in handy in any Compute authors = ["Piotr Skalski "] maintainers = ["Piotr Skalski "] readme = "README.md" +license = "MIT" packages = [{include = "supervision"}] homepage = "https://github.com/roboflow/supervision" repository = "https://github.com/roboflow/supervision" @@ -14,7 +15,7 @@ keywords = ["machine-learning", "deep-learning", "vision", "ML", "DL", "AI", "YO classifiers=[ 'Intended Audience :: Developers', 'Intended Audience :: Science/Research', - 'License :: OSI Approved :: BSD License', + 'License :: OSI Approved :: MIT License', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', @@ -37,7 +38,7 @@ python = ">=3.8,<3.12.0" numpy = "^1.20.0" matplotlib = "^3.7.1" pyyaml = "^6.0" -pillow = "^8.4.0" +pillow = "^9.4.0" opencv-python = { version = "^4.8.0.74", optional = true } opencv-python-headless = "^4.8.0.74" diff --git a/supervision/dataset/core.py b/supervision/dataset/core.py index 68e258b8..cdc2550e 100644 --- a/supervision/dataset/core.py +++ b/supervision/dataset/core.py @@ -31,7 +31,6 @@ from supervision.dataset.utils import ( train_test_split, ) from supervision.detection.core import Detections -from supervision.utils.file import list_files_with_extensions @dataclass @@ -212,16 +211,20 @@ class DetectionDataset(BaseDataset): @classmethod def from_pascal_voc( - cls, images_directory_path: str, annotations_directory_path: str + cls, + images_directory_path: str, + annotations_directory_path: str, + force_masks: bool = False, ) -> DetectionDataset: """ Creates a Dataset instance from PASCAL VOC formatted data. Args: - images_directory_path (str): The path to the - directory containing the images. - annotations_directory_path (str): The path to the directory + images_directory_path (str): Path to the directory containing the images. + annotations_directory_path (str): Path to the directory containing the PASCAL VOC XML annotations. + force_masks (bool, optional): If True, forces masks to + be loaded for all annotations, regardless of whether they are present. Returns: DetectionDataset: A DetectionDataset instance containing @@ -240,7 +243,7 @@ class DetectionDataset(BaseDataset): >>> project = rf.workspace(WORKSPACE_ID).project(PROJECT_ID) >>> dataset = project.version(PROJECT_VERSION).download("voc") - >>> ds = sv.DetectionDataset.from_yolo( + >>> ds = sv.DetectionDataset.from_pascal_voc( ... images_directory_path=f"{dataset.location}/train/images", ... annotations_directory_path=f"{dataset.location}/train/labels" ... ) @@ -249,34 +252,13 @@ class DetectionDataset(BaseDataset): ['dog', 'person'] ``` """ - image_paths = list_files_with_extensions( - directory=images_directory_path, extensions=["jpg", "jpeg", "png"] - ) - annotation_paths = list_files_with_extensions( - directory=annotations_directory_path, extensions=["xml"] + + classes, images, annotations = load_pascal_voc_annotations( + images_directory_path=images_directory_path, + annotations_directory_path=annotations_directory_path, + force_masks=force_masks, ) - raw_annotations: List[Tuple[str, Detections, List[str]]] = [ - load_pascal_voc_annotations(annotation_path=str(annotation_path)) - for annotation_path in annotation_paths - ] - - classes = [] - for annotation in raw_annotations: - classes.extend(annotation[2]) - classes = list(set(classes)) - - for annotation in raw_annotations: - class_id = [classes.index(class_name) for class_name in annotation[2]] - annotation[1].class_id = np.array(class_id) - - images = { - image_path.name: cv2.imread(str(image_path)) for image_path in image_paths - } - - annotations = { - image_name: detections for image_name, detections, _ in raw_annotations - } return DetectionDataset(classes=classes, images=images, annotations=annotations) @classmethod diff --git a/supervision/dataset/formats/pascal_voc.py b/supervision/dataset/formats/pascal_voc.py index 8753821c..3d1f1578 100644 --- a/supervision/dataset/formats/pascal_voc.py +++ b/supervision/dataset/formats/pascal_voc.py @@ -1,12 +1,16 @@ -from typing import List, Optional, Tuple +import os +from pathlib import Path +from typing import Dict, List, Optional, Tuple from xml.dom.minidom import parseString from xml.etree.ElementTree import Element, SubElement, parse, tostring +import cv2 import numpy as np from supervision.dataset.utils import approximate_mask_with_polygons from supervision.detection.core import Detections -from supervision.detection.utils import polygon_to_xyxy +from supervision.detection.utils import polygon_to_mask, polygon_to_xyxy +from supervision.utils.file import list_files_with_extensions def object_to_pascal_voc( @@ -126,27 +130,100 @@ def detections_to_pascal_voc( def load_pascal_voc_annotations( - annotation_path: str, -) -> Tuple[str, Detections, List[str]]: + images_directory_path: str, + annotations_directory_path: str, + force_masks: bool = False, +) -> Tuple[List[str], Dict[str, np.ndarray], Dict[str, Detections]]: """ Loads PASCAL VOC XML annotations and returns the image name, a Detections instance, and a list of class names. Args: - annotation_path (str): The path to the PASCAL VOC XML annotations file. + images_directory_path (str): The path to the directory containing the images. + annotations_directory_path (str): The path to the directory containing the + PASCAL VOC annotation files. + force_masks (bool, optional): If True, forces masks to be loaded for all + annotations, regardless of whether they are present. Returns: - Tuple[str, Detections, List[str]]: A tuple containing the image name, - a Detections instance, and a list of class - names of objects in the detections. + Tuple[List[str], Dict[str, np.ndarray], Dict[str, Detections]]: A tuple + containing a list of class names, + a dictionary with image names as keys and + images as values, and a dictionary with image names as + keys and corresponding Detections instances as values. """ - tree = parse(annotation_path) - root = tree.getroot() - image_name = root.find("filename").text + image_paths = list_files_with_extensions( + directory=images_directory_path, extensions=["jpg", "jpeg", "png"] + ) + classes = [] + images = {} + annotations = {} + + for image_path in image_paths: + image_name = Path(image_path).stem + image = cv2.imread(str(image_path)) + + annotation_path = os.path.join(annotations_directory_path, f"{image_name}.xml") + if not os.path.exists(annotation_path): + images[image_path.name] = image + annotations[image_path.name] = Detections.empty() + continue + + tree = parse(annotation_path) + root = tree.getroot() + + resolution_wh = (image.shape[1], image.shape[0]) + annotation, classes = detections_from_xml_obj( + root, classes, resolution_wh, force_masks + ) + + images[image_path.name] = image + annotations[image_path.name] = annotation + + return classes, images, annotations + + +def detections_from_xml_obj( + root: Element, classes: List[str], resolution_wh, force_masks: bool = False +) -> Tuple[Detections, List[str]]: + """ + Converts an XML object in Pascal VOC format to a Detections object. + Expected XML format: + + ... + + dog + + 48 + 240 + 195 + 371 + + + 48 + 240 + 195 + 240 + 195 + 371 + 48 + 371 + + + + + Returns: + Tuple[Detections, List[str]]: A tuple containing a Detections object and an + updated list of class names, extended with the class names + from the XML object. + """ xyxy = [] class_names = [] + masks = [] + with_masks = False + extended_classes = classes[:] for obj in root.findall("object"): class_name = obj.find("name").text class_names.append(class_name) @@ -159,7 +236,40 @@ def load_pascal_voc_annotations( xyxy.append([x1, y1, x2, y2]) - xyxy = np.array(xyxy) - detections = Detections(xyxy=xyxy) + with_masks = obj.find("polygon") is not None + with_masks = force_masks if force_masks else with_masks - return image_name, detections, class_names + for polygon in obj.findall("polygon"): + polygon_points = parse_polygon_points(polygon) + + mask_from_polygon = polygon_to_mask( + polygon=np.array(polygon_points), + resolution_wh=resolution_wh, + ) + masks.append(mask_from_polygon) + + xyxy = np.array(xyxy) if len(xyxy) > 0 else np.empty((0, 4)) + for k in set(class_names): + if k not in extended_classes: + extended_classes.append(k) + class_id = np.array( + [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) + return annotation, extended_classes + + +def parse_polygon_points(polygon: Element) -> List[List[int]]: + polygon_points = [] + coords = polygon.findall(".//*") + for i in range(0, len(coords), 2): + x = int(coords[i].text) + y = int(coords[i + 1].text) + polygon_points.append([x, y]) + return polygon_points diff --git a/supervision/detection/core.py b/supervision/detection/core.py index 69692e02..d65f2a86 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -6,12 +6,13 @@ from typing import Any, Iterator, List, Optional, Tuple, Union import numpy as np from supervision.detection.utils import ( - extract_yolov8_masks, + extract_ultralytics_masks, non_max_suppression, process_roboflow_result, xywh_to_xyxy, ) from supervision.geometry.core import Position +from supervision.utils.internal import deprecated def _validate_xyxy(xyxy: Any, n: int) -> None: @@ -177,6 +178,10 @@ class Detections: ) @classmethod + @deprecated( + "This method is deprecated and removed in 0.15.0 release. Use " + "sv.Detections.from_ultralytics() instead." + ) def from_yolov8(cls, yolov8_results) -> Detections: """ Creates a Detections instance from a @@ -205,7 +210,44 @@ class Detections: xyxy=yolov8_results.boxes.xyxy.cpu().numpy(), confidence=yolov8_results.boxes.conf.cpu().numpy(), class_id=yolov8_results.boxes.cls.cpu().numpy().astype(int), - mask=extract_yolov8_masks(yolov8_results), + mask=extract_ultralytics_masks(yolov8_results), + ) + + @classmethod + def from_ultralytics(cls, ultralytics_results) -> Detections: + """ + Creates a Detections instance from a + [YOLOv8](https://github.com/ultralytics/ultralytics) inference result. + + Args: + yolov8_results (ultralytics.yolo.engine.results.Results): The output + results instance from YOLOv8 + + Returns: + Detections: A new Detections object. + + Example: + ```python + >>> import cv2 + >>> from ultralytics import YOLO, FastSAM, SAM, RTDETR + >>> import supervision as sv + + >>> image = cv2.imread(SOURCE_IMAGE_PATH) + >>> model = YOLO('yolov8s.pt') + >>> model = SAM('sam_b.pt') + >>> model = SAM('mobile_sam.pt') + >>> model = FastSAM('FastSAM-s.pt') + >>> model = RTDETR('rtdetr-l.pt') + + >>> result = model(image)[0] + >>> detections = sv.Detections.from_ultralytics(result) + ``` + """ + return cls( + xyxy=ultralytics_results.boxes.xyxy.cpu().numpy(), + confidence=ultralytics_results.boxes.conf.cpu().numpy(), + class_id=ultralytics_results.boxes.cls.cpu().numpy().astype(int), + mask=extract_ultralytics_masks(ultralytics_results), ) @classmethod @@ -414,6 +456,44 @@ class Detections: return Detections(xyxy=xywh_to_xyxy(boxes_xywh=xywh), mask=mask) + @classmethod + def from_paddledet(cls, paddledet_result): + """ + Creates a Detections instance from + [PaddleDetection](https://github.com/PaddlePaddle/PaddleDetection) + inference result. + + Args: + paddledet_result (List[dict]): The output Results instance from SAM + + Returns: + Detections: A new Detections object. + + Example: + ```python + >>> import supervision as sv + >>> import paddle + >>> from ppdet.engine import Trainer + >>> from ppdet.core.workspace import load_config + + >>> weights = (...) + >>> config = (...) + + >>> cfg = load_config(config) + >>> trainer = Trainer(cfg, mode='test') + >>> trainer.load_weights(weights) + + >>> paddledet_result = trainer.predict([images])[0] + + >>> detections = sv.Detections.from_paddledet(paddledet_result) + ``` + """ + return cls( + xyxy=paddledet_result["bbox"][:, 2:6], + confidence=paddledet_result["bbox"][:, 1], + class_id=paddledet_result["bbox"][:, 0].astype(int), + ) + @classmethod def empty(cls) -> Detections: """ diff --git a/supervision/detection/utils.py b/supervision/detection/utils.py index 5fa5ef0f..db631755 100644 --- a/supervision/detection/utils.py +++ b/supervision/detection/utils.py @@ -19,7 +19,7 @@ def polygon_to_mask(polygon: np.ndarray, resolution_wh: Tuple[int, int]) -> np.n `1`'s and the rest is filled with `0`'s. """ width, height = resolution_wh - mask = np.zeros((height, width), dtype=np.uint8) + mask = np.zeros((height, width)) cv2.fillPoly(mask, [polygon], color=1) return mask @@ -293,7 +293,7 @@ def approximate_polygon( return np.squeeze(approximated_points, axis=1) -def extract_yolov8_masks(yolov8_results) -> Optional[np.ndarray]: +def extract_ultralytics_masks(yolov8_results) -> Optional[np.ndarray]: if not yolov8_results.masks: return None @@ -321,7 +321,10 @@ def extract_yolov8_masks(yolov8_results) -> Optional[np.ndarray]: for i in range(masks.shape[0]): mask = masks[i] mask = mask[top:bottom, left:right] - mask = cv2.resize(mask, (orig_shape[1], orig_shape[0])) + + if mask.shape != orig_shape: + mask = cv2.resize(mask, (orig_shape[1], orig_shape[0])) + mask_maps.append(mask) return np.asarray(mask_maps, dtype=bool) diff --git a/test/dataset/formats/test_pascal_voc.py b/test/dataset/formats/test_pascal_voc.py new file mode 100644 index 00000000..248f6b9c --- /dev/null +++ b/test/dataset/formats/test_pascal_voc.py @@ -0,0 +1,160 @@ +import xml.etree.ElementTree as ET +from contextlib import ExitStack as DoesNotRaise +from test.utils import mock_detections +from typing import List, Optional + +import numpy as np +import pytest + +from supervision.dataset.formats.pascal_voc import ( + detections_from_xml_obj, + object_to_pascal_voc, + parse_polygon_points, +) + + +def are_xml_elements_equal(elem1, elem2): + if ( + elem1.tag != elem2.tag + or elem1.attrib != elem2.attrib + or elem1.text != elem2.text + or len(elem1) != len(elem2) + ): + return False + + for child1, child2 in zip(elem1, elem2): + if not are_xml_elements_equal(child1, child2): + return False + + return True + + +@pytest.mark.parametrize( + "xyxy, name, polygon, expected_result, exception", + [ + ( + [0, 0, 10, 10], + "test", + None, + ET.fromstring( + """test00 + 1010""" + ), + DoesNotRaise(), + ), + ( + [0, 0, 10, 10], + "test", + [[0, 0], [10, 0], [10, 10], [0, 10]], + ET.fromstring( + """test00 + 10100010 + 01010010 + """ + ), + DoesNotRaise(), + ), + ], +) +def test_object_to_pascal_voc( + xyxy: np.ndarray, + name: str, + polygon: Optional[np.ndarray], + expected_result, + exception: Exception, +): + with exception: + result = object_to_pascal_voc(xyxy=xyxy, name=name, polygon=polygon) + assert are_xml_elements_equal(result, expected_result) + + +@pytest.mark.parametrize( + "polygon_element, expected_result, exception", + [ + ( + ET.fromstring( + """0010010 + 10010""" + ), + [[0, 0], [10, 0], [10, 10], [0, 10]], + DoesNotRaise(), + ) + ], +) +def test_parse_polygon_points( + polygon_element, + expected_result: List[list], + exception, +): + with exception: + result = parse_polygon_points(polygon_element) + assert result == expected_result + + +ONE_CLASS_N_BBOX = """test001010test1010 +2020 """ + +ONE_CLASS_ONE_BBOX = """test001010""" + +N_CLASS_N_BBOX = """test001010 +test2030 +3040test210102020""" + +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