diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d5256ea..9668f4aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,12 @@ name: CI on: push: - branches: [ main ] + branches: [ main , 'v*', 'dev*'] pull_request: - branches: [ main ] + branches: [ main , 'v*' ] + +permissions: + contents: read jobs: build: @@ -13,9 +16,12 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.8", "3.9"] + python-version: ["3.9", "3.10", "3.11"] steps: + - name: Install openGL + run: sudo apt install freeglut3-dev + - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} @@ -23,7 +29,18 @@ jobs: with: python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Install run: | python -m pip install --upgrade pip pip install -e . + + - name: Test with pytest + run: | + pytest + diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..aa07f0d8 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,44 @@ +name: Linting Checks + +on: + push: + branches: [ main , 'v*', 'dev_*'] + pull_request: + branches: [ main , 'v*' ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.9"] + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + + - name: Install + run: | + python -m pip install --upgrade pip + pip install -e . + + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --statistics --exit-zero diff --git a/LICENSE b/LICENSE index 1f905d6f..f83bea7e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ MIT License Copyright (c) 2020 Joseph Auckley, Matthew O'Kelly, Aman Sinha, Hongrui Zheng +Copyright (c) 2023 Hongrui Zheng, Renukanandan Tumu, Luigi Berducci, Ahmad Amine Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Pipfile b/Pipfile new file mode 100644 index 00000000..b85b85fa --- /dev/null +++ b/Pipfile @@ -0,0 +1,32 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +gymnasium = "*" +numpy = "<1.25" +pillow = ">=9.0.1" +scipy = ">=1.7.3" +pyglet = "<1.5" +pyyaml = ">=5.3.1" +yamldataclassconfig = "*" +requests = "*" +pyopengl = "*" +numba = "*" +shapely = "*" +opencv-python = "*" +f110-gym = {file = "."} + +[dev-packages] +flake8 = "*" +black = "*" +ipykernel = "*" +pytest = "*" +isort = "*" +autoflake = "*" +matplotlib = "*" + +[requires] +python_version = "3.10" +python_full_version = "3.10.11" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 00000000..5950f0cd --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,1380 @@ +{ + "_meta": { + "hash": { + "sha256": "8ba9fc82f4b3c300687f74e53209cf8bf21995882ac9cde2b005fd30b579e7ec" + }, + "pipfile-spec": 6, + "requires": { + "python_full_version": "3.10.11", + "python_version": "3.10" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", + "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9" + ], + "markers": "python_version >= '3.6'", + "version": "==2023.7.22" + }, + "charset-normalizer": { + "hashes": [ + "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96", + "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c", + "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710", + "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706", + "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020", + "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252", + "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad", + "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329", + "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a", + "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f", + "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6", + "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4", + "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a", + "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46", + "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2", + "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23", + "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace", + "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd", + "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982", + "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10", + "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2", + "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea", + "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09", + "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5", + "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149", + "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489", + "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9", + "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80", + "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592", + "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3", + "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6", + "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed", + "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c", + "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200", + "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a", + "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e", + "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d", + "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6", + "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623", + "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669", + "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3", + "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa", + "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9", + "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2", + "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f", + "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1", + "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4", + "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a", + "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8", + "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3", + "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029", + "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f", + "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959", + "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22", + "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7", + "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952", + "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346", + "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e", + "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d", + "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299", + "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd", + "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a", + "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3", + "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037", + "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94", + "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c", + "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858", + "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a", + "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449", + "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c", + "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918", + "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1", + "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c", + "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac", + "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.2.0" + }, + "cloudpickle": { + "hashes": [ + "sha256:61f594d1f4c295fa5cd9014ceb3a1fc4a70b0de1164b94fbc2d854ccba056f9f", + "sha256:d89684b8de9e34a2a43b3460fbca07d09d6e25ce858df4d5a44240403b6178f5" + ], + "markers": "python_version >= '3.6'", + "version": "==2.2.1" + }, + "dataclasses-json": { + "hashes": [ + "sha256:5ec6fed642adb1dbdb4182badb01e0861badfd8fda82e3b67f44b2d1e9d10d21", + "sha256:d82896a94c992ffaf689cd1fafc180164e2abdd415b8f94a7f78586af5886236" + ], + "markers": "python_version < '3.13' and python_version >= '3.7'", + "version": "==0.5.14" + }, + "f110-gym": { + "file": "." + }, + "farama-notifications": { + "hashes": [ + "sha256:13fceff2d14314cf80703c8266462ebf3733c7d165336eee998fc58e545efd18", + "sha256:14de931035a41961f7c056361dc7f980762a143d05791ef5794a751a2caf05ae" + ], + "version": "==0.0.4" + }, + "future": { + "hashes": [ + "sha256:34a17436ed1e96697a86f9de3d15a3b0be01d8bc8de9c1dffd59fb8234ed5307" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.18.3" + }, + "gymnasium": { + "hashes": [ + "sha256:1a532752efcb7590478b1cc7aa04f608eb7a2fdad5570cd217b66b6a35274bb1", + "sha256:61c3384b5575985bb7f85e43213bcb40f36fcdff388cae6bc229304c71f2843e" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==0.29.1" + }, + "idna": { + "hashes": [ + "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", + "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" + ], + "markers": "python_version >= '3.5'", + "version": "==3.4" + }, + "llvmlite": { + "hashes": [ + "sha256:09f83ea7a54509c285f905d968184bba00fc31ebf12f2b6b1494d677bb7dde9b", + "sha256:0c23edd196bd797dc3a7860799054ea3488d2824ecabc03f9135110c2e39fcbc", + "sha256:3673c53cb21c65d2ff3704962b5958e967c6fc0bd0cff772998face199e8d87b", + "sha256:39a0b4d0088c01a469a5860d2e2d7a9b4e6a93c0f07eb26e71a9a872a8cadf8d", + "sha256:467b43836b388eaedc5a106d76761e388dbc4674b2f2237bc477c6895b15a634", + "sha256:4a7525db121f2e699809b539b5308228854ccab6693ecb01b52c44a2f5647e20", + "sha256:5b3076dc4e9c107d16dc15ecb7f2faf94f7736cd2d5e9f4dc06287fd672452c1", + "sha256:5cdb0d45df602099d833d50bd9e81353a5e036242d3c003c5b294fc61d1986b4", + "sha256:7b37297f3cbd68d14a97223a30620589d98ad1890e5040c9e5fc181063f4ed49", + "sha256:84747289775d0874e506f907a4513db889471607db19b04de97d144047fec885", + "sha256:84ce9b1c7a59936382ffde7871978cddcda14098e5a76d961e204523e5c372fb", + "sha256:9329b930d699699846623054121ed105fd0823ed2180906d3b3235d361645490", + "sha256:96707ebad8b051bbb4fc40c65ef93b7eeee16643bd4d579a14d11578e4b7a647", + "sha256:a36d9f244b6680cb90bbca66b146dabb2972f4180c64415c96f7c8a2d8b60a36", + "sha256:a66a5bd580951751b4268f4c3bddcef92682814d6bc72f3cd3bb67f335dd7097", + "sha256:bba2747cf5b4954e945c287fe310b3fcc484e2a9d1b0c273e99eb17d103bb0e6", + "sha256:bbd5e82cc990e5a3e343a3bf855c26fdfe3bfae55225f00efd01c05bbda79918", + "sha256:cda71de10a1f48416309e408ea83dab5bf36058f83e13b86a2961defed265568", + "sha256:e2dbbb8424037ca287983b115a29adf37d806baf7e1bf4a67bd2cffb74e085ed", + "sha256:e35766e42acef0fe7d1c43169a8ffc327a47808fae6a067b049fe0e9bbf84dd5", + "sha256:e44f854dc11559795bcdeaf12303759e56213d42dabbf91a5897aa2d8b033810", + "sha256:e74e7bec3235a1e1c9ad97d897a620c5007d0ed80c32c84c1d787e7daa17e4ec", + "sha256:f643d15aacd0b0b0dc8b74b693822ba3f9a53fa63bc6a178c2dba7cc88f42144", + "sha256:ff8f31111bb99d135ff296757dc81ab36c2dee54ed4bd429158a96da9807c316" + ], + "markers": "python_version >= '3.8'", + "version": "==0.40.1" + }, + "marshmallow": { + "hashes": [ + "sha256:5d2371bbe42000f2b3fb5eaa065224df7d8f8597bc19a1bbfa5bfe7fba8da889", + "sha256:684939db93e80ad3561392f47be0230743131560a41c5110684c16e21ade0a5c" + ], + "markers": "python_version >= '3.8'", + "version": "==3.20.1" + }, + "mypy-extensions": { + "hashes": [ + "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", + "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" + ], + "markers": "python_version >= '3.5'", + "version": "==1.0.0" + }, + "numba": { + "hashes": [ + "sha256:33c0500170d213e66d90558ad6aca57d3e03e97bb11da82e6d87ab793648cb17", + "sha256:3cf78d74ad9d289fbc1e5b1c9f2680fca7a788311eb620581893ab347ec37a7e", + "sha256:3d6483c27520d16cf5d122868b79cad79e48056ecb721b52d70c126bed65431e", + "sha256:4838edef2df5f056cb8974670f3d66562e751040c448eb0b67c7e2fec1726649", + "sha256:4c078f84b5529a7fdb8413bb33d5100f11ec7b44aa705857d9eb4e54a54ff505", + "sha256:53e9fab973d9e82c9f8449f75994a898daaaf821d84f06fbb0b9de2293dd9306", + "sha256:5a82bf37444039c732485c072fda21a361790ed990f88db57fd6941cd5e5d307", + "sha256:60ec56386076e9eed106a87c96626d5686fbb16293b9834f0849cf78c9491779", + "sha256:643cb09a9ba9e1bd8b060e910aeca455e9442361e80fce97690795ff9840e681", + "sha256:6c057ccedca95df23802b6ccad86bb318be624af45b5a38bb8412882be57a681", + "sha256:8e00ca63c5d0ad2beeb78d77f087b3a88c45ea9b97e7622ab2ec411a868420ee", + "sha256:93df62304ada9b351818ba19b1cfbddaf72cd89348e81474326ca0b23bf0bae1", + "sha256:9587ba1bf5f3035575e45562ada17737535c6d612df751e811d702693a72d95e", + "sha256:9a1b2b69448e510d672ff9a6b18d2db9355241d93c6a77677baa14bec67dc2a0", + "sha256:9b17fbe4a69dcd9a7cd49916b6463cd9a82af5f84911feeb40793b8bce00dfa7", + "sha256:9bcc36478773ce838f38afd9a4dfafc328d4ffb1915381353d657da7f6473282", + "sha256:a32ee263649aa3c3587b833d6311305379529570e6c20deb0c6f4fb5bc7020db", + "sha256:a3eac19529956185677acb7f01864919761bfffbb9ae04bbbe5e84bbc06cfc2b", + "sha256:ae50c8c90c2ce8057f9618b589223e13faa8cbc037d8f15b4aad95a2c33a0582", + "sha256:c0602e4f896e6a6d844517c3ab434bc978e7698a22a733cc8124465898c28fa8", + "sha256:db8268eb5093cae2288942a8cbd69c9352f6fe6e0bfa0a9a27679436f92e4248", + "sha256:e447c4634d1cc99ab50d4faa68f680f1d88b06a2a05acf134aa6fcc0342adeca", + "sha256:f47dd214adc5dcd040fe9ad2adbd2192133c9075d2189ce1b3d5f9d72863ef05", + "sha256:ff66d5b022af6c7d81ddbefa87768e78ed4f834ab2da6ca2fd0d60a9e69b94f5" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==0.57.1" + }, + "numpy": { + "hashes": [ + "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f", + "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61", + "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7", + "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400", + "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef", + "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2", + "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d", + "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc", + "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835", + "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706", + "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5", + "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4", + "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6", + "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463", + "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a", + "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f", + "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e", + "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e", + "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694", + "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8", + "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64", + "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d", + "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc", + "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254", + "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2", + "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1", + "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810", + "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==1.24.4" + }, + "opencv-python": { + "hashes": [ + "sha256:48eb3121d809a873086d6677565e3ac963e6946110d13cd115533fa70e2aa2eb", + "sha256:56d84c43ce800938b9b1ec74b33942b2edbcef3f70c2754eb9bfe5dff1ee3ace", + "sha256:67bce4b9aad307c98a9a07c6afb7de3a4e823c1f4991d6d8e88e229e7dfeee59", + "sha256:93871871b1c9d6b125cddd45b0638a2fa01ee9fd37f5e428823f750e404f2f15", + "sha256:9bcb4944211acf13742dbfd9d3a11dc4e36353ffa1746f2c7dcd6a01c32d1376", + "sha256:b2349dc9f97ed6c9ba163d0a7a24bcef9695a3e216cd143e92f1b9659c5d9a49", + "sha256:ba32cfa75a806abd68249699d34420737d27b5678553387fc5768747a6492147" + ], + "index": "pypi", + "markers": "python_version >= '3.6'", + "version": "==4.8.0.76" + }, + "packaging": { + "hashes": [ + "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61", + "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f" + ], + "markers": "python_version >= '3.7'", + "version": "==23.1" + }, + "pillow": { + "hashes": [ + "sha256:00e65f5e822decd501e374b0650146063fbb30a7264b4d2744bdd7b913e0cab5", + "sha256:040586f7d37b34547153fa383f7f9aed68b738992380ac911447bb78f2abe530", + "sha256:0b6eb5502f45a60a3f411c63187db83a3d3107887ad0d036c13ce836f8a36f1d", + "sha256:1ce91b6ec08d866b14413d3f0bbdea7e24dfdc8e59f562bb77bc3fe60b6144ca", + "sha256:1f62406a884ae75fb2f818694469519fb685cc7eaff05d3451a9ebe55c646891", + "sha256:22c10cc517668d44b211717fd9775799ccec4124b9a7f7b3635fc5386e584992", + "sha256:3400aae60685b06bb96f99a21e1ada7bc7a413d5f49bce739828ecd9391bb8f7", + "sha256:349930d6e9c685c089284b013478d6f76e3a534e36ddfa912cde493f235372f3", + "sha256:368ab3dfb5f49e312231b6f27b8820c823652b7cd29cfbd34090565a015e99ba", + "sha256:38250a349b6b390ee6047a62c086d3817ac69022c127f8a5dc058c31ccef17f3", + "sha256:3a684105f7c32488f7153905a4e3015a3b6c7182e106fe3c37fbb5ef3e6994c3", + "sha256:3a82c40d706d9aa9734289740ce26460a11aeec2d9c79b7af87bb35f0073c12f", + "sha256:3b08d4cc24f471b2c8ca24ec060abf4bebc6b144cb89cba638c720546b1cf538", + "sha256:3ed64f9ca2f0a95411e88a4efbd7a29e5ce2cea36072c53dd9d26d9c76f753b3", + "sha256:3f07ea8d2f827d7d2a49ecf1639ec02d75ffd1b88dcc5b3a61bbb37a8759ad8d", + "sha256:520f2a520dc040512699f20fa1c363eed506e94248d71f85412b625026f6142c", + "sha256:5c6e3df6bdd396749bafd45314871b3d0af81ff935b2d188385e970052091017", + "sha256:608bfdee0d57cf297d32bcbb3c728dc1da0907519d1784962c5f0c68bb93e5a3", + "sha256:685ac03cc4ed5ebc15ad5c23bc555d68a87777586d970c2c3e216619a5476223", + "sha256:76de421f9c326da8f43d690110f0e79fe3ad1e54be811545d7d91898b4c8493e", + "sha256:76edb0a1fa2b4745fb0c99fb9fb98f8b180a1bbceb8be49b087e0b21867e77d3", + "sha256:7be600823e4c8631b74e4a0d38384c73f680e6105a7d3c6824fcf226c178c7e6", + "sha256:81ff539a12457809666fef6624684c008e00ff6bf455b4b89fd00a140eecd640", + "sha256:88af2003543cc40c80f6fca01411892ec52b11021b3dc22ec3bc9d5afd1c5334", + "sha256:8c11160913e3dd06c8ffdb5f233a4f254cb449f4dfc0f8f4549eda9e542c93d1", + "sha256:8f8182b523b2289f7c415f589118228d30ac8c355baa2f3194ced084dac2dbba", + "sha256:9211e7ad69d7c9401cfc0e23d49b69ca65ddd898976d660a2fa5904e3d7a9baa", + "sha256:92be919bbc9f7d09f7ae343c38f5bb21c973d2576c1d45600fce4b74bafa7ac0", + "sha256:9c82b5b3e043c7af0d95792d0d20ccf68f61a1fec6b3530e718b688422727396", + "sha256:9f7c16705f44e0504a3a2a14197c1f0b32a95731d251777dcb060aa83022cb2d", + "sha256:9fb218c8a12e51d7ead2a7c9e101a04982237d4855716af2e9499306728fb485", + "sha256:a74ba0c356aaa3bb8e3eb79606a87669e7ec6444be352870623025d75a14a2bf", + "sha256:b4f69b3700201b80bb82c3a97d5e9254084f6dd5fb5b16fc1a7b974260f89f43", + "sha256:bc2ec7c7b5d66b8ec9ce9f720dbb5fa4bace0f545acd34870eff4a369b44bf37", + "sha256:c189af0545965fa8d3b9613cfdb0cd37f9d71349e0f7750e1fd704648d475ed2", + "sha256:c1fbe7621c167ecaa38ad29643d77a9ce7311583761abf7836e1510c580bf3dd", + "sha256:c7cf14a27b0d6adfaebb3ae4153f1e516df54e47e42dcc073d7b3d76111a8d86", + "sha256:c9f72a021fbb792ce98306ffb0c348b3c9cb967dce0f12a49aa4c3d3fdefa967", + "sha256:cd25d2a9d2b36fcb318882481367956d2cf91329f6892fe5d385c346c0649629", + "sha256:ce543ed15570eedbb85df19b0a1a7314a9c8141a36ce089c0a894adbfccb4568", + "sha256:ce7b031a6fc11365970e6a5686d7ba8c63e4c1cf1ea143811acbb524295eabed", + "sha256:d35e3c8d9b1268cbf5d3670285feb3528f6680420eafe35cccc686b73c1e330f", + "sha256:d50b6aec14bc737742ca96e85d6d0a5f9bfbded018264b3b70ff9d8c33485551", + "sha256:d5d0dae4cfd56969d23d94dc8e89fb6a217be461c69090768227beb8ed28c0a3", + "sha256:d5db32e2a6ccbb3d34d87c87b432959e0db29755727afb37290e10f6e8e62614", + "sha256:d72e2ecc68a942e8cf9739619b7f408cc7b272b279b56b2c83c6123fcfa5cdff", + "sha256:d737a602fbd82afd892ca746392401b634e278cb65d55c4b7a8f48e9ef8d008d", + "sha256:d80cf684b541685fccdd84c485b31ce73fc5c9b5d7523bf1394ce134a60c6883", + "sha256:db24668940f82321e746773a4bc617bfac06ec831e5c88b643f91f122a785684", + "sha256:dbc02381779d412145331789b40cc7b11fdf449e5d94f6bc0b080db0a56ea3f0", + "sha256:dffe31a7f47b603318c609f378ebcd57f1554a3a6a8effbc59c3c69f804296de", + "sha256:edf4392b77bdc81f36e92d3a07a5cd072f90253197f4a52a55a8cec48a12483b", + "sha256:efe8c0681042536e0d06c11f48cebe759707c9e9abf880ee213541c5b46c5bf3", + "sha256:f31f9fdbfecb042d046f9d91270a0ba28368a723302786c0009ee9b9f1f60199", + "sha256:f88a0b92277de8e3ca715a0d79d68dc82807457dae3ab8699c758f07c20b3c51", + "sha256:faaf07ea35355b01a35cb442dd950d8f1bb5b040a7787791a535de13db15ed90" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==10.0.0" + }, + "pyglet": { + "hashes": [ + "sha256:8a8317fbb2bae145bd80f6d92d66b6dbbc9d13f1cbbed682ff55793a63003a46", + "sha256:e4cc8dc2f09d8487f7b3e2d93bd1961528afe989d058177b26a46d3508fd2c33" + ], + "index": "pypi", + "version": "==1.4.11" + }, + "pyopengl": { + "hashes": [ + "sha256:a6ab19cf290df6101aaf7470843a9c46207789855746399d0af92521a0a92b7a", + "sha256:eef31a3888e6984fd4d8e6c9961b184c9813ca82604d37fe3da80eb000a76c86" + ], + "index": "pypi", + "version": "==3.1.7" + }, + "pyyaml": { + "hashes": [ + "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", + "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", + "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206", + "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27", + "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595", + "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62", + "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98", + "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696", + "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", + "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867", + "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47", + "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486", + "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6", + "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3", + "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", + "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", + "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c", + "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735", + "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", + "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba", + "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8", + "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5", + "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd", + "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3", + "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0", + "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", + "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c", + "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c", + "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", + "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", + "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", + "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859", + "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", + "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", + "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", + "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa", + "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c", + "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585", + "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", + "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f" + ], + "index": "pypi", + "markers": "python_version >= '3.6'", + "version": "==6.0.1" + }, + "requests": { + "hashes": [ + "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", + "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==2.31.0" + }, + "scipy": { + "hashes": [ + "sha256:0f3261f14b767b316d7137c66cc4f33a80ea05841b9c87ad83a726205b901423", + "sha256:10eb6af2f751aa3424762948e5352f707b0dece77288206f227864ddf675aca0", + "sha256:1342ca385c673208f32472830c10110a9dcd053cf0c4b7d4cd7026d0335a6c1d", + "sha256:214cdf04bbae7a54784f8431f976704ed607c4bc69ba0d5d5d6a9df84374df76", + "sha256:2b997a5369e2d30c97995dcb29d638701f8000d04df01b8e947f206e5d0ac788", + "sha256:2c91cf049ffb5575917f2a01da1da082fd24ed48120d08a6e7297dfcac771dcd", + "sha256:3aeb87661de987f8ec56fa6950863994cd427209158255a389fc5aea51fa7055", + "sha256:4447ad057d7597476f9862ecbd9285bbf13ba9d73ce25acfa4e4b11c6801b4c9", + "sha256:542a757e2a6ec409e71df3d8fd20127afbbacb1c07990cb23c5870c13953d899", + "sha256:8d9886f44ef8c9e776cb7527fb01455bf4f4a46c455c4682edc2c2cc8cd78562", + "sha256:90d3b1364e751d8214e325c371f0ee0dd38419268bf4888b2ae1040a6b266b2a", + "sha256:95763fbda1206bec41157582bea482f50eb3702c85fffcf6d24394b071c0e87a", + "sha256:ac74b1512d38718fb6a491c439aa7b3605b96b1ed3be6599c17d49d6c60fca18", + "sha256:afdb0d983f6135d50770dd979df50bf1c7f58b5b33e0eb8cf5c73c70600eae1d", + "sha256:b0620240ef445b5ddde52460e6bc3483b7c9c750275369379e5f609a1050911c", + "sha256:b133f237bd8ba73bad51bc12eb4f2d84cbec999753bf25ba58235e9fc2096d80", + "sha256:b29318a5e39bd200ca4381d80b065cdf3076c7d7281c5e36569e99273867f61d", + "sha256:b8425fa963a32936c9773ee3ce44a765d8ff67eed5f4ac81dc1e4a819a238ee9", + "sha256:d2b813bfbe8dec6a75164523de650bad41f4405d35b0fa24c2c28ae07fcefb20", + "sha256:d690e1ca993c8f7ede6d22e5637541217fc6a4d3f78b3672a6fe454dbb7eb9a7", + "sha256:e367904a0fec76433bf3fbf3e85bf60dae8e9e585ffd21898ab1085a29a04d16", + "sha256:ea932570b1c2a30edafca922345854ff2cd20d43cd9123b6dacfdecebfc1a80b", + "sha256:f28f1f6cfeb48339c192efc6275749b2a25a7e49c4d8369a28b6591da02fbc9a", + "sha256:f73102f769ee06041a3aa26b5841359b1a93cc364ce45609657751795e8f4a4a", + "sha256:fa4909c6c20c3d91480533cddbc0e7c6d849e7d9ded692918c76ce5964997898" + ], + "index": "pypi", + "markers": "python_version < '3.13' and python_version >= '3.9'", + "version": "==1.11.2" + }, + "shapely": { + "hashes": [ + "sha256:01224899ff692a62929ef1a3f5fe389043e262698a708ab7569f43a99a48ae82", + "sha256:05c51a29336e604c084fb43ae5dbbfa2c0ef9bd6fedeae0a0d02c7b57a56ba46", + "sha256:09d6c7763b1bee0d0a2b84bb32a4c25c6359ad1ac582a62d8b211e89de986154", + "sha256:193a398d81c97a62fc3634a1a33798a58fd1dcf4aead254d080b273efbb7e3ff", + "sha256:1a34a23d6266ca162499e4a22b79159dc0052f4973d16f16f990baa4d29e58b6", + "sha256:2569a4b91caeef54dd5ae9091ae6f63526d8ca0b376b5bb9fd1a3195d047d7d4", + "sha256:33403b8896e1d98aaa3a52110d828b18985d740cc9f34f198922018b1e0f8afe", + "sha256:3ad81f292fffbd568ae71828e6c387da7eb5384a79db9b4fde14dd9fdeffca9a", + "sha256:3cb256ae0c01b17f7bc68ee2ffdd45aebf42af8992484ea55c29a6151abe4386", + "sha256:45b4833235b90bc87ee26c6537438fa77559d994d2d3be5190dd2e54d31b2820", + "sha256:4641325e065fd3e07d55677849c9ddfd0cf3ee98f96475126942e746d55b17c8", + "sha256:502e0a607f1dcc6dee0125aeee886379be5242c854500ea5fd2e7ac076b9ce6d", + "sha256:66a6b1a3e72ece97fc85536a281476f9b7794de2e646ca8a4517e2e3c1446893", + "sha256:70a18fc7d6418e5aea76ac55dce33f98e75bd413c6eb39cfed6a1ba36469d7d4", + "sha256:7d3bbeefd8a6a1a1017265d2d36f8ff2d79d0162d8c141aa0d37a87063525656", + "sha256:83a8ec0ee0192b6e3feee9f6a499d1377e9c295af74d7f81ecba5a42a6b195b7", + "sha256:865bc3d7cc0ea63189d11a0b1120d1307ed7a64720a8bfa5be2fde5fc6d0d33f", + "sha256:90cfa4144ff189a3c3de62e2f3669283c98fb760cfa2e82ff70df40f11cadb39", + "sha256:91575d97fd67391b85686573d758896ed2fc7476321c9d2e2b0c398b628b961c", + "sha256:9a6ac34c16f4d5d3c174c76c9d7614ec8fe735f8f82b6cc97a46b54f386a86bf", + "sha256:a529218e72a3dbdc83676198e610485fdfa31178f4be5b519a8ae12ea688db14", + "sha256:a70a614791ff65f5e283feed747e1cc3d9e6c6ba91556e640636bbb0a1e32a71", + "sha256:ac1dfc397475d1de485e76de0c3c91cc9d79bd39012a84bb0f5e8a199fc17bef", + "sha256:b06d031bc64149e340448fea25eee01360a58936c89985cf584134171e05863f", + "sha256:b4f0711cc83734c6fad94fc8d4ec30f3d52c1787b17d9dca261dc841d4731c64", + "sha256:b50c401b64883e61556a90b89948297f1714dbac29243d17ed9284a47e6dd731", + "sha256:b519cf3726ddb6c67f6a951d1bb1d29691111eaa67ea19ddca4d454fbe35949c", + "sha256:bca57b683e3d94d0919e2f31e4d70fdfbb7059650ef1b431d9f4e045690edcd5", + "sha256:c43755d2c46b75a7b74ac6226d2cc9fa2a76c3263c5ae70c195c6fb4e7b08e79", + "sha256:c7eed1fb3008a8a4a56425334b7eb82651a51f9e9a9c2f72844a2fb394f38a6c", + "sha256:c8b0d834b11be97d5ab2b4dceada20ae8e07bcccbc0f55d71df6729965f406ad", + "sha256:ce88ec79df55430e37178a191ad8df45cae90b0f6972d46d867bf6ebbb58cc4d", + "sha256:d173d24e85e51510e658fb108513d5bc11e3fd2820db6b1bd0522266ddd11f51", + "sha256:d8f55f355be7821dade839df785a49dc9f16d1af363134d07eb11e9207e0b189", + "sha256:da71de5bf552d83dcc21b78cc0020e86f8d0feea43e202110973987ffa781c21", + "sha256:e55698e0ed95a70fe9ff9a23c763acfe0bf335b02df12142f74e4543095e9a9b", + "sha256:f32a748703e7bf6e92dfa3d2936b2fbfe76f8ce5f756e24f49ef72d17d26ad02", + "sha256:f470a130d6ddb05b810fc1776d918659407f8d025b7f56d2742a596b6dffa6c7" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==2.0.1" + }, + "typing-extensions": { + "hashes": [ + "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36", + "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2" + ], + "markers": "python_version >= '3.7'", + "version": "==4.7.1" + }, + "typing-inspect": { + "hashes": [ + "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", + "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78" + ], + "version": "==0.9.0" + }, + "urllib3": { + "hashes": [ + "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11", + "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.4" + }, + "yamldataclassconfig": { + "hashes": [ + "sha256:5b06a5ced890f2e18ec2a42b8e57d6659f2134554eb8402319b22c5993a5725f", + "sha256:dbbaa499875b9187b1446279440aa3e8e1dc1d5878fe46a27272b0d4b70b85a4" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==1.5.0" + } + }, + "develop": { + "asttokens": { + "hashes": [ + "sha256:4622110b2a6f30b77e1473affaa97e711bc2f07d3f10848420ff1898edbe94f3", + "sha256:6b0ac9e93fb0335014d382b8fa9b3afa7df546984258005da0b9e7095b3deb1c" + ], + "version": "==2.2.1" + }, + "autoflake": { + "hashes": [ + "sha256:62e1f74a0fdad898a96fee6f99fe8241af90ad99c7110c884b35855778412251", + "sha256:de409b009a34c1c2a7cc2aae84c4c05047f9773594317c6a6968bd497600d4a0" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==2.2.0" + }, + "backcall": { + "hashes": [ + "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e", + "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255" + ], + "version": "==0.2.0" + }, + "black": { + "hashes": [ + "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3", + "sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb", + "sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087", + "sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320", + "sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6", + "sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3", + "sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc", + "sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f", + "sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587", + "sha256:642496b675095d423f9b8448243336f8ec71c9d4d57ec17bf795b67f08132a91", + "sha256:6d1c6022b86f83b632d06f2b02774134def5d4d4f1dac8bef16d90cda18ba28a", + "sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad", + "sha256:831d8f54c3a8c8cf55f64d0422ee875eecac26f5f649fb6c1df65316b67c8926", + "sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9", + "sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be", + "sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd", + "sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96", + "sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491", + "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2", + "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a", + "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f", + "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==23.7.0" + }, + "click": { + "hashes": [ + "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" + ], + "markers": "python_version >= '3.7'", + "version": "==8.1.7" + }, + "colorama": { + "hashes": [ + "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", + "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" + ], + "markers": "sys_platform == 'win32'", + "version": "==0.4.6" + }, + "comm": { + "hashes": [ + "sha256:354e40a59c9dd6db50c5cc6b4acc887d82e9603787f83b68c01a80a923984d15", + "sha256:6d52794cba11b36ed9860999cd10fd02d6b2eac177068fdd585e1e2f8a96e67a" + ], + "markers": "python_version >= '3.6'", + "version": "==0.1.4" + }, + "contourpy": { + "hashes": [ + "sha256:052cc634bf903c604ef1a00a5aa093c54f81a2612faedaa43295809ffdde885e", + "sha256:084eaa568400cfaf7179b847ac871582199b1b44d5699198e9602ecbbb5f6104", + "sha256:0b6616375d7de55797d7a66ee7d087efe27f03d336c27cf1f32c02b8c1a5ac70", + "sha256:0b7b04ed0961647691cfe5d82115dd072af7ce8846d31a5fac6c142dcce8b882", + "sha256:143dde50520a9f90e4a2703f367cf8ec96a73042b72e68fcd184e1279962eb6f", + "sha256:17cfaf5ec9862bc93af1ec1f302457371c34e688fbd381f4035a06cd47324f48", + "sha256:181cbace49874f4358e2929aaf7ba84006acb76694102e88dd15af861996c16e", + "sha256:189ceb1525eb0655ab8487a9a9c41f42a73ba52d6789754788d1883fb06b2d8a", + "sha256:18a64814ae7bce73925131381603fff0116e2df25230dfc80d6d690aa6e20b37", + "sha256:1f0cbd657e9bde94cd0e33aa7df94fb73c1ab7799378d3b3f902eb8eb2e04a3a", + "sha256:1f795597073b09d631782e7245016a4323cf1cf0b4e06eef7ea6627e06a37ff2", + "sha256:25ae46595e22f93592d39a7eac3d638cda552c3e1160255258b695f7b58e5655", + "sha256:27bc79200c742f9746d7dd51a734ee326a292d77e7d94c8af6e08d1e6c15d545", + "sha256:2b836d22bd2c7bb2700348e4521b25e077255ebb6ab68e351ab5aa91ca27e027", + "sha256:30f511c05fab7f12e0b1b7730ebdc2ec8deedcfb505bc27eb570ff47c51a8f15", + "sha256:317267d915490d1e84577924bd61ba71bf8681a30e0d6c545f577363157e5e94", + "sha256:397b0ac8a12880412da3551a8cb5a187d3298a72802b45a3bd1805e204ad8439", + "sha256:438ba416d02f82b692e371858143970ed2eb6337d9cdbbede0d8ad9f3d7dd17d", + "sha256:53cc3a40635abedbec7f1bde60f8c189c49e84ac180c665f2cd7c162cc454baa", + "sha256:5d123a5bc63cd34c27ff9c7ac1cd978909e9c71da12e05be0231c608048bb2ae", + "sha256:62013a2cf68abc80dadfd2307299bfa8f5aa0dcaec5b2954caeb5fa094171103", + "sha256:89f06eff3ce2f4b3eb24c1055a26981bffe4e7264acd86f15b97e40530b794bc", + "sha256:90c81f22b4f572f8a2110b0b741bb64e5a6427e0a198b2cdc1fbaf85f352a3aa", + "sha256:911ff4fd53e26b019f898f32db0d4956c9d227d51338fb3b03ec72ff0084ee5f", + "sha256:9382a1c0bc46230fb881c36229bfa23d8c303b889b788b939365578d762b5c18", + "sha256:9f2931ed4741f98f74b410b16e5213f71dcccee67518970c42f64153ea9313b9", + "sha256:a67259c2b493b00e5a4d0f7bfae51fb4b3371395e47d079a4446e9b0f4d70e76", + "sha256:a698c6a7a432789e587168573a864a7ea374c6be8d4f31f9d87c001d5a843493", + "sha256:bc00bb4225d57bff7ebb634646c0ee2a1298402ec10a5fe7af79df9a51c1bfd9", + "sha256:bcb41692aa09aeb19c7c213411854402f29f6613845ad2453d30bf421fe68fed", + "sha256:d4f26b25b4f86087e7d75e63212756c38546e70f2a92d2be44f80114826e1cd4", + "sha256:d551f3a442655f3dcc1285723f9acd646ca5858834efeab4598d706206b09c9f", + "sha256:dffcc2ddec1782dd2f2ce1ef16f070861af4fb78c69862ce0aab801495dda6a3", + "sha256:e53046c3863828d21d531cc3b53786e6580eb1ba02477e8681009b6aa0870b21", + "sha256:e5cec36c5090e75a9ac9dbd0ff4a8cf7cecd60f1b6dc23a374c7d980a1cd710e", + "sha256:e7a117ce7df5a938fe035cad481b0189049e8d92433b4b33aa7fc609344aafa1", + "sha256:e94bef2580e25b5fdb183bf98a2faa2adc5b638736b2c0a4da98691da641316a", + "sha256:ed614aea8462735e7d70141374bd7650afd1c3f3cb0c2dbbcbe44e14331bf002", + "sha256:fb3b7d9e6243bfa1efb93ccfe64ec610d85cfe5aec2c25f97fbbd2e58b531256" + ], + "markers": "python_version >= '3.8'", + "version": "==1.1.0" + }, + "cycler": { + "hashes": [ + "sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3", + "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f" + ], + "markers": "python_version >= '3.6'", + "version": "==0.11.0" + }, + "debugpy": { + "hashes": [ + "sha256:038c51268367c9c935905a90b1c2d2dbfe304037c27ba9d19fe7409f8cdc710c", + "sha256:1093a5c541af079c13ac8c70ab8b24d1d35c8cacb676306cf11e57f699c02926", + "sha256:3370ef1b9951d15799ef7af41f8174194f3482ee689988379763ef61a5456426", + "sha256:38651c3639a4e8bbf0ca7e52d799f6abd07d622a193c406be375da4d510d968d", + "sha256:3de5d0f97c425dc49bce4293df6a04494309eedadd2b52c22e58d95107e178d9", + "sha256:4b9eba71c290852f959d2cf8a03af28afd3ca639ad374d393d53d367f7f685b2", + "sha256:65b28435a17cba4c09e739621173ff90c515f7b9e8ea469b92e3c28ef8e5cdfb", + "sha256:72f5d2ecead8125cf669e62784ef1e6300f4067b0f14d9f95ee00ae06fc7c4f7", + "sha256:85969d864c45f70c3996067cfa76a319bae749b04171f2cdeceebe4add316155", + "sha256:890f7ab9a683886a0f185786ffbda3b46495c4b929dab083b8c79d6825832a52", + "sha256:903bd61d5eb433b6c25b48eae5e23821d4c1a19e25c9610205f5aeaccae64e32", + "sha256:92b6dae8bfbd497c90596bbb69089acf7954164aea3228a99d7e43e5267f5b36", + "sha256:973a97ed3b434eab0f792719a484566c35328196540676685c975651266fccf9", + "sha256:d16882030860081e7dd5aa619f30dec3c2f9a421e69861125f83cc372c94e57d", + "sha256:d4ac7a4dba28801d184b7fc0e024da2635ca87d8b0a825c6087bb5168e3c0d28", + "sha256:eea8d8cfb9965ac41b99a61f8e755a8f50e9a20330938ad8271530210f54e09c", + "sha256:f0851403030f3975d6e2eaa4abf73232ab90b98f041e3c09ba33be2beda43fcf", + "sha256:fe87ec0182ef624855d05e6ed7e0b7cb1359d2ffa2a925f8ec2d22e98b75d0ca" + ], + "markers": "python_version >= '3.7'", + "version": "==1.6.7.post1" + }, + "decorator": { + "hashes": [ + "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330", + "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186" + ], + "markers": "python_version >= '3.5'", + "version": "==5.1.1" + }, + "exceptiongroup": { + "hashes": [ + "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9", + "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3" + ], + "markers": "python_version < '3.11'", + "version": "==1.1.3" + }, + "executing": { + "hashes": [ + "sha256:0314a69e37426e3608aada02473b4161d4caf5a4b244d1d0c48072b8fee7bacc", + "sha256:19da64c18d2d851112f09c287f8d3dbbdf725ab0e569077efb6cdcbd3497c107" + ], + "version": "==1.2.0" + }, + "flake8": { + "hashes": [ + "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23", + "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5" + ], + "index": "pypi", + "markers": "python_full_version >= '3.8.1'", + "version": "==6.1.0" + }, + "fonttools": { + "hashes": [ + "sha256:0eb79a2da5eb6457a6f8ab904838454accc7d4cccdaff1fd2bd3a0679ea33d64", + "sha256:113337c2d29665839b7d90b39f99b3cac731f72a0eda9306165a305c7c31d341", + "sha256:12a7c247d1b946829bfa2f331107a629ea77dc5391dfd34fdcd78efa61f354ca", + "sha256:179737095eb98332a2744e8f12037b2977f22948cf23ff96656928923ddf560a", + "sha256:19b7db825c8adee96fac0692e6e1ecd858cae9affb3b4812cdb9d934a898b29e", + "sha256:37983b6bdab42c501202500a2be3a572f50d4efe3237e0686ee9d5f794d76b35", + "sha256:3a35981d90feebeaef05e46e33e6b9e5b5e618504672ca9cd0ff96b171e4bfff", + "sha256:46a0ec8adbc6ff13494eb0c9c2e643b6f009ce7320cf640de106fb614e4d4360", + "sha256:4aa79366e442dbca6e2c8595645a3a605d9eeabdb7a094d745ed6106816bef5d", + "sha256:515607ec756d7865f23070682622c49d922901943697871fc292277cf1e71967", + "sha256:53eb5091ddc8b1199330bb7b4a8a2e7995ad5d43376cadce84523d8223ef3136", + "sha256:5d18fc642fd0ac29236ff88ecfccff229ec0386090a839dd3f1162e9a7944a40", + "sha256:5fb289b7a815638a7613d46bcf324c9106804725b2bb8ad913c12b6958ffc4ec", + "sha256:62f481ac772fd68901573956231aea3e4b1ad87b9b1089a61613a91e2b50bb9b", + "sha256:689508b918332fb40ce117131633647731d098b1b10d092234aa959b4251add5", + "sha256:68a02bbe020dc22ee0540e040117535f06df9358106d3775e8817d826047f3fd", + "sha256:6ed2662a3d9c832afa36405f8748c250be94ae5dfc5283d668308391f2102861", + "sha256:7286aed4ea271df9eab8d7a9b29e507094b51397812f7ce051ecd77915a6e26b", + "sha256:7cc7d685b8eeca7ae69dc6416833fbfea61660684b7089bca666067cb2937dcf", + "sha256:8708b98c278012ad267ee8a7433baeb809948855e81922878118464b274c909d", + "sha256:9398f244e28e0596e2ee6024f808b06060109e33ed38dcc9bded452fd9bbb853", + "sha256:9e36344e48af3e3bde867a1ca54f97c308735dd8697005c2d24a86054a114a71", + "sha256:a398bdadb055f8de69f62b0fc70625f7cbdab436bbb31eef5816e28cab083ee8", + "sha256:acb47f6f8680de24c1ab65ebde39dd035768e2a9b571a07c7b8da95f6c8815fd", + "sha256:be24fcb80493b2c94eae21df70017351851652a37de514de553435b256b2f249", + "sha256:c391cd5af88aacaf41dd7cfb96eeedfad297b5899a39e12f4c2c3706d0a3329d", + "sha256:c95b0724a6deea2c8c5d3222191783ced0a2f09bd6d33f93e563f6f1a4b3b3a4", + "sha256:c9b1ce7a45978b821a06d375b83763b27a3a5e8a2e4570b3065abad240a18760", + "sha256:db372213d39fa33af667c2aa586a0c1235e88e9c850f5dd5c8e1f17515861868", + "sha256:db55cbaea02a20b49fefbd8e9d62bd481aaabe1f2301dabc575acc6b358874fa", + "sha256:ed1a13a27f59d1fc1920394a7f596792e9d546c9ca5a044419dca70c37815d7c", + "sha256:f2b82f46917d8722e6b5eafeefb4fb585d23babd15d8246c664cd88a5bddd19c", + "sha256:f2f806990160d1ce42d287aa419df3ffc42dfefe60d473695fb048355fe0c6a0", + "sha256:f720fa82a11c0f9042376fd509b5ed88dab7e3cd602eee63a1af08883b37342b" + ], + "markers": "python_version >= '3.8'", + "version": "==4.42.1" + }, + "iniconfig": { + "hashes": [ + "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", + "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.0" + }, + "ipykernel": { + "hashes": [ + "sha256:050391364c0977e768e354bdb60cbbfbee7cbb943b1af1618382021136ffd42f", + "sha256:c8a2430b357073b37c76c21c52184db42f6b4b0e438e1eb7df3c4440d120497c" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==6.25.1" + }, + "ipython": { + "hashes": [ + "sha256:1d197b907b6ba441b692c48cf2a3a2de280dc0ac91a3405b39349a50272ca0a1", + "sha256:248aca623f5c99a6635bc3857677b7320b9b8039f99f070ee0d20a5ca5a8e6bf" + ], + "markers": "python_version >= '3.9'", + "version": "==8.14.0" + }, + "isort": { + "hashes": [ + "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504", + "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6" + ], + "index": "pypi", + "markers": "python_full_version >= '3.8.0'", + "version": "==5.12.0" + }, + "jedi": { + "hashes": [ + "sha256:bcf9894f1753969cbac8022a8c2eaee06bfa3724e4192470aaffe7eb6272b0c4", + "sha256:cb8ce23fbccff0025e9386b5cf85e892f94c9b822378f8da49970471335ac64e" + ], + "markers": "python_version >= '3.6'", + "version": "==0.19.0" + }, + "jupyter-client": { + "hashes": [ + "sha256:3af69921fe99617be1670399a0b857ad67275eefcfa291e2c81a160b7b650f5f", + "sha256:7441af0c0672edc5d28035e92ba5e32fadcfa8a4e608a434c228836a89df6158" + ], + "markers": "python_version >= '3.8'", + "version": "==8.3.0" + }, + "jupyter-core": { + "hashes": [ + "sha256:5ba5c7938a7f97a6b0481463f7ff0dbac7c15ba48cf46fa4035ca6e838aa1aba", + "sha256:ae9036db959a71ec1cac33081eeb040a79e681f08ab68b0883e9a676c7a90dce" + ], + "markers": "python_version >= '3.8'", + "version": "==5.3.1" + }, + "kiwisolver": { + "hashes": [ + "sha256:02f79693ec433cb4b5f51694e8477ae83b3205768a6fb48ffba60549080e295b", + "sha256:03baab2d6b4a54ddbb43bba1a3a2d1627e82d205c5cf8f4c924dc49284b87166", + "sha256:1041feb4cda8708ce73bb4dcb9ce1ccf49d553bf87c3954bdfa46f0c3f77252c", + "sha256:10ee06759482c78bdb864f4109886dff7b8a56529bc1609d4f1112b93fe6423c", + "sha256:1d1573129aa0fd901076e2bfb4275a35f5b7aa60fbfb984499d661ec950320b0", + "sha256:283dffbf061a4ec60391d51e6155e372a1f7a4f5b15d59c8505339454f8989e4", + "sha256:28bc5b299f48150b5f822ce68624e445040595a4ac3d59251703779836eceff9", + "sha256:2a66fdfb34e05b705620dd567f5a03f239a088d5a3f321e7b6ac3239d22aa286", + "sha256:2e307eb9bd99801f82789b44bb45e9f541961831c7311521b13a6c85afc09767", + "sha256:2e407cb4bd5a13984a6c2c0fe1845e4e41e96f183e5e5cd4d77a857d9693494c", + "sha256:2f5e60fabb7343a836360c4f0919b8cd0d6dbf08ad2ca6b9cf90bf0c76a3c4f6", + "sha256:36dafec3d6d6088d34e2de6b85f9d8e2324eb734162fba59d2ba9ed7a2043d5b", + "sha256:3fe20f63c9ecee44560d0e7f116b3a747a5d7203376abeea292ab3152334d004", + "sha256:41dae968a94b1ef1897cb322b39360a0812661dba7c682aa45098eb8e193dbdf", + "sha256:4bd472dbe5e136f96a4b18f295d159d7f26fd399136f5b17b08c4e5f498cd494", + "sha256:4ea39b0ccc4f5d803e3337dd46bcce60b702be4d86fd0b3d7531ef10fd99a1ac", + "sha256:5853eb494c71e267912275e5586fe281444eb5e722de4e131cddf9d442615626", + "sha256:5bce61af018b0cb2055e0e72e7d65290d822d3feee430b7b8203d8a855e78766", + "sha256:6295ecd49304dcf3bfbfa45d9a081c96509e95f4b9d0eb7ee4ec0530c4a96514", + "sha256:62ac9cc684da4cf1778d07a89bf5f81b35834cb96ca523d3a7fb32509380cbf6", + "sha256:70e7c2e7b750585569564e2e5ca9845acfaa5da56ac46df68414f29fea97be9f", + "sha256:7577c1987baa3adc4b3c62c33bd1118c3ef5c8ddef36f0f2c950ae0b199e100d", + "sha256:75facbe9606748f43428fc91a43edb46c7ff68889b91fa31f53b58894503a191", + "sha256:787518a6789009c159453da4d6b683f468ef7a65bbde796bcea803ccf191058d", + "sha256:78d6601aed50c74e0ef02f4204da1816147a6d3fbdc8b3872d263338a9052c51", + "sha256:7c43e1e1206cd421cd92e6b3280d4385d41d7166b3ed577ac20444b6995a445f", + "sha256:81e38381b782cc7e1e46c4e14cd997ee6040768101aefc8fa3c24a4cc58e98f8", + "sha256:841293b17ad704d70c578f1f0013c890e219952169ce8a24ebc063eecf775454", + "sha256:872b8ca05c40d309ed13eb2e582cab0c5a05e81e987ab9c521bf05ad1d5cf5cb", + "sha256:877272cf6b4b7e94c9614f9b10140e198d2186363728ed0f701c6eee1baec1da", + "sha256:8c808594c88a025d4e322d5bb549282c93c8e1ba71b790f539567932722d7bd8", + "sha256:8ed58b8acf29798b036d347791141767ccf65eee7f26bde03a71c944449e53de", + "sha256:91672bacaa030f92fc2f43b620d7b337fd9a5af28b0d6ed3f77afc43c4a64b5a", + "sha256:968f44fdbf6dd757d12920d63b566eeb4d5b395fd2d00d29d7ef00a00582aac9", + "sha256:9f85003f5dfa867e86d53fac6f7e6f30c045673fa27b603c397753bebadc3008", + "sha256:a553dadda40fef6bfa1456dc4be49b113aa92c2a9a9e8711e955618cd69622e3", + "sha256:a68b62a02953b9841730db7797422f983935aeefceb1679f0fc85cbfbd311c32", + "sha256:abbe9fa13da955feb8202e215c4018f4bb57469b1b78c7a4c5c7b93001699938", + "sha256:ad881edc7ccb9d65b0224f4e4d05a1e85cf62d73aab798943df6d48ab0cd79a1", + "sha256:b1792d939ec70abe76f5054d3f36ed5656021dcad1322d1cc996d4e54165cef9", + "sha256:b428ef021242344340460fa4c9185d0b1f66fbdbfecc6c63eff4b7c29fad429d", + "sha256:b533558eae785e33e8c148a8d9921692a9fe5aa516efbdff8606e7d87b9d5824", + "sha256:ba59c92039ec0a66103b1d5fe588fa546373587a7d68f5c96f743c3396afc04b", + "sha256:bc8d3bd6c72b2dd9decf16ce70e20abcb3274ba01b4e1c96031e0c4067d1e7cd", + "sha256:bc9db8a3efb3e403e4ecc6cd9489ea2bac94244f80c78e27c31dcc00d2790ac2", + "sha256:bf7d9fce9bcc4752ca4a1b80aabd38f6d19009ea5cbda0e0856983cf6d0023f5", + "sha256:c2dbb44c3f7e6c4d3487b31037b1bdbf424d97687c1747ce4ff2895795c9bf69", + "sha256:c79ebe8f3676a4c6630fd3f777f3cfecf9289666c84e775a67d1d358578dc2e3", + "sha256:c97528e64cb9ebeff9701e7938653a9951922f2a38bd847787d4a8e498cc83ae", + "sha256:d0611a0a2a518464c05ddd5a3a1a0e856ccc10e67079bb17f265ad19ab3c7597", + "sha256:d06adcfa62a4431d404c31216f0f8ac97397d799cd53800e9d3efc2fbb3cf14e", + "sha256:d41997519fcba4a1e46eb4a2fe31bc12f0ff957b2b81bac28db24744f333e955", + "sha256:d5b61785a9ce44e5a4b880272baa7cf6c8f48a5180c3e81c59553ba0cb0821ca", + "sha256:da152d8cdcab0e56e4f45eb08b9aea6455845ec83172092f09b0e077ece2cf7a", + "sha256:da7e547706e69e45d95e116e6939488d62174e033b763ab1496b4c29b76fabea", + "sha256:db5283d90da4174865d520e7366801a93777201e91e79bacbac6e6927cbceede", + "sha256:db608a6757adabb32f1cfe6066e39b3706d8c3aa69bbc353a5b61edad36a5cb4", + "sha256:e0ea21f66820452a3f5d1655f8704a60d66ba1191359b96541eaf457710a5fc6", + "sha256:e7da3fec7408813a7cebc9e4ec55afed2d0fd65c4754bc376bf03498d4e92686", + "sha256:e92a513161077b53447160b9bd8f522edfbed4bd9759e4c18ab05d7ef7e49408", + "sha256:ecb1fa0db7bf4cff9dac752abb19505a233c7f16684c5826d1f11ebd9472b871", + "sha256:efda5fc8cc1c61e4f639b8067d118e742b812c930f708e6667a5ce0d13499e29", + "sha256:f0a1dbdb5ecbef0d34eb77e56fcb3e95bbd7e50835d9782a45df81cc46949750", + "sha256:f0a71d85ecdd570ded8ac3d1c0f480842f49a40beb423bb8014539a9f32a5897", + "sha256:f4f270de01dd3e129a72efad823da90cc4d6aafb64c410c9033aba70db9f1ff0", + "sha256:f6cb459eea32a4e2cf18ba5fcece2dbdf496384413bc1bae15583f19e567f3b2", + "sha256:f8ad8285b01b0d4695102546b342b493b3ccc6781fc28c8c6a1bb63e95d22f09", + "sha256:f9f39e2f049db33a908319cf46624a569b36983c7c78318e9726a4cb8923b26c" + ], + "markers": "python_version >= '3.7'", + "version": "==1.4.4" + }, + "matplotlib": { + "hashes": [ + "sha256:070f8dddd1f5939e60aacb8fa08f19551f4b0140fab16a3669d5cd6e9cb28fc8", + "sha256:0c3cca3e842b11b55b52c6fb8bd6a4088693829acbfcdb3e815fa9b7d5c92c1b", + "sha256:0f506a1776ee94f9e131af1ac6efa6e5bc7cb606a3e389b0ccb6e657f60bb676", + "sha256:12f01b92ecd518e0697da4d97d163b2b3aa55eb3eb4e2c98235b3396d7dad55f", + "sha256:152ee0b569a37630d8628534c628456b28686e085d51394da6b71ef84c4da201", + "sha256:1c308b255efb9b06b23874236ec0f10f026673ad6515f602027cc8ac7805352d", + "sha256:1cd120fca3407a225168238b790bd5c528f0fafde6172b140a2f3ab7a4ea63e9", + "sha256:20f844d6be031948148ba49605c8b96dfe7d3711d1b63592830d650622458c11", + "sha256:23fb1750934e5f0128f9423db27c474aa32534cec21f7b2153262b066a581fd1", + "sha256:2699f7e73a76d4c110f4f25be9d2496d6ab4f17345307738557d345f099e07de", + "sha256:26bede320d77e469fdf1bde212de0ec889169b04f7f1179b8930d66f82b30cbc", + "sha256:2ecb5be2b2815431c81dc115667e33da0f5a1bcf6143980d180d09a717c4a12e", + "sha256:2f8e4a49493add46ad4a8c92f63e19d548b2b6ebbed75c6b4c7f46f57d36cdd1", + "sha256:305e3da477dc8607336ba10bac96986d6308d614706cae2efe7d3ffa60465b24", + "sha256:30e1409b857aa8a747c5d4f85f63a79e479835f8dffc52992ac1f3f25837b544", + "sha256:318c89edde72ff95d8df67d82aca03861240512994a597a435a1011ba18dbc7f", + "sha256:35d74ebdb3f71f112b36c2629cf32323adfbf42679e2751252acd468f5001c07", + "sha256:50e0a55ec74bf2d7a0ebf50ac580a209582c2dd0f7ab51bc270f1b4a0027454e", + "sha256:5dea00b62d28654b71ca92463656d80646675628d0828e08a5f3b57e12869e13", + "sha256:60c521e21031632aa0d87ca5ba0c1c05f3daacadb34c093585a0be6780f698e4", + "sha256:6515e878f91894c2e4340d81f0911857998ccaf04dbc1bba781e3d89cbf70608", + "sha256:6d2ff3c984b8a569bc1383cd468fc06b70d7b59d5c2854ca39f1436ae8394117", + "sha256:71667eb2ccca4c3537d9414b1bc00554cb7f91527c17ee4ec38027201f8f1603", + "sha256:717157e61b3a71d3d26ad4e1770dc85156c9af435659a25ee6407dc866cb258d", + "sha256:71f7a8c6b124e904db550f5b9fe483d28b896d4135e45c4ea381ad3b8a0e3256", + "sha256:936bba394682049919dda062d33435b3be211dc3dcaa011e09634f060ec878b2", + "sha256:a1733b8e84e7e40a9853e505fe68cc54339f97273bdfe6f3ed980095f769ddc7", + "sha256:a2c1590b90aa7bd741b54c62b78de05d4186271e34e2377e0289d943b3522273", + "sha256:a7e28d6396563955f7af437894a36bf2b279462239a41028323e04b85179058b", + "sha256:a8035ba590658bae7562786c9cc6ea1a84aa49d3afab157e414c9e2ea74f496d", + "sha256:a8cdb91dddb04436bd2f098b8fdf4b81352e68cf4d2c6756fcc414791076569b", + "sha256:ac60daa1dc83e8821eed155796b0f7888b6b916cf61d620a4ddd8200ac70cd64", + "sha256:af4860132c8c05261a5f5f8467f1b269bf1c7c23902d75f2be57c4a7f2394b3e", + "sha256:bc221ffbc2150458b1cd71cdd9ddd5bb37962b036e41b8be258280b5b01da1dd", + "sha256:ce55289d5659b5b12b3db4dc9b7075b70cef5631e56530f14b2945e8836f2d20", + "sha256:d9881356dc48e58910c53af82b57183879129fa30492be69058c5b0d9fddf391", + "sha256:dbcf59334ff645e6a67cd5f78b4b2cdb76384cdf587fa0d2dc85f634a72e1a3e", + "sha256:ebf577c7a6744e9e1bd3fee45fc74a02710b214f94e2bde344912d85e0c9af7c", + "sha256:f081c03f413f59390a80b3e351cc2b2ea0205839714dbc364519bcf51f4b56ca", + "sha256:fdbb46fad4fb47443b5b8ac76904b2e7a66556844f33370861b4788db0f8816a", + "sha256:fdcd28360dbb6203fb5219b1a5658df226ac9bebc2542a9e8f457de959d713d0" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==3.7.2" + }, + "matplotlib-inline": { + "hashes": [ + "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311", + "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304" + ], + "markers": "python_version >= '3.5'", + "version": "==0.1.6" + }, + "mccabe": { + "hashes": [ + "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", + "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e" + ], + "markers": "python_version >= '3.6'", + "version": "==0.7.0" + }, + "mypy-extensions": { + "hashes": [ + "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", + "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" + ], + "markers": "python_version >= '3.5'", + "version": "==1.0.0" + }, + "nest-asyncio": { + "hashes": [ + "sha256:5301c82941b550b3123a1ea772ba9a1c80bad3a182be8c1a5ae6ad3be57a9657", + "sha256:6a80f7b98f24d9083ed24608977c09dd608d83f91cccc24c9d2cba6d10e01c10" + ], + "markers": "python_version >= '3.5'", + "version": "==1.5.7" + }, + "numpy": { + "hashes": [ + "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f", + "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61", + "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7", + "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400", + "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef", + "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2", + "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d", + "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc", + "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835", + "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706", + "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5", + "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4", + "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6", + "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463", + "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a", + "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f", + "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e", + "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e", + "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694", + "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8", + "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64", + "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d", + "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc", + "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254", + "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2", + "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1", + "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810", + "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==1.24.4" + }, + "packaging": { + "hashes": [ + "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61", + "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f" + ], + "markers": "python_version >= '3.7'", + "version": "==23.1" + }, + "parso": { + "hashes": [ + "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0", + "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75" + ], + "markers": "python_version >= '3.6'", + "version": "==0.8.3" + }, + "pathspec": { + "hashes": [ + "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20", + "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3" + ], + "markers": "python_version >= '3.7'", + "version": "==0.11.2" + }, + "pickleshare": { + "hashes": [ + "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca", + "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56" + ], + "version": "==0.7.5" + }, + "pillow": { + "hashes": [ + "sha256:00e65f5e822decd501e374b0650146063fbb30a7264b4d2744bdd7b913e0cab5", + "sha256:040586f7d37b34547153fa383f7f9aed68b738992380ac911447bb78f2abe530", + "sha256:0b6eb5502f45a60a3f411c63187db83a3d3107887ad0d036c13ce836f8a36f1d", + "sha256:1ce91b6ec08d866b14413d3f0bbdea7e24dfdc8e59f562bb77bc3fe60b6144ca", + "sha256:1f62406a884ae75fb2f818694469519fb685cc7eaff05d3451a9ebe55c646891", + "sha256:22c10cc517668d44b211717fd9775799ccec4124b9a7f7b3635fc5386e584992", + "sha256:3400aae60685b06bb96f99a21e1ada7bc7a413d5f49bce739828ecd9391bb8f7", + "sha256:349930d6e9c685c089284b013478d6f76e3a534e36ddfa912cde493f235372f3", + "sha256:368ab3dfb5f49e312231b6f27b8820c823652b7cd29cfbd34090565a015e99ba", + "sha256:38250a349b6b390ee6047a62c086d3817ac69022c127f8a5dc058c31ccef17f3", + "sha256:3a684105f7c32488f7153905a4e3015a3b6c7182e106fe3c37fbb5ef3e6994c3", + "sha256:3a82c40d706d9aa9734289740ce26460a11aeec2d9c79b7af87bb35f0073c12f", + "sha256:3b08d4cc24f471b2c8ca24ec060abf4bebc6b144cb89cba638c720546b1cf538", + "sha256:3ed64f9ca2f0a95411e88a4efbd7a29e5ce2cea36072c53dd9d26d9c76f753b3", + "sha256:3f07ea8d2f827d7d2a49ecf1639ec02d75ffd1b88dcc5b3a61bbb37a8759ad8d", + "sha256:520f2a520dc040512699f20fa1c363eed506e94248d71f85412b625026f6142c", + "sha256:5c6e3df6bdd396749bafd45314871b3d0af81ff935b2d188385e970052091017", + "sha256:608bfdee0d57cf297d32bcbb3c728dc1da0907519d1784962c5f0c68bb93e5a3", + "sha256:685ac03cc4ed5ebc15ad5c23bc555d68a87777586d970c2c3e216619a5476223", + "sha256:76de421f9c326da8f43d690110f0e79fe3ad1e54be811545d7d91898b4c8493e", + "sha256:76edb0a1fa2b4745fb0c99fb9fb98f8b180a1bbceb8be49b087e0b21867e77d3", + "sha256:7be600823e4c8631b74e4a0d38384c73f680e6105a7d3c6824fcf226c178c7e6", + "sha256:81ff539a12457809666fef6624684c008e00ff6bf455b4b89fd00a140eecd640", + "sha256:88af2003543cc40c80f6fca01411892ec52b11021b3dc22ec3bc9d5afd1c5334", + "sha256:8c11160913e3dd06c8ffdb5f233a4f254cb449f4dfc0f8f4549eda9e542c93d1", + "sha256:8f8182b523b2289f7c415f589118228d30ac8c355baa2f3194ced084dac2dbba", + "sha256:9211e7ad69d7c9401cfc0e23d49b69ca65ddd898976d660a2fa5904e3d7a9baa", + "sha256:92be919bbc9f7d09f7ae343c38f5bb21c973d2576c1d45600fce4b74bafa7ac0", + "sha256:9c82b5b3e043c7af0d95792d0d20ccf68f61a1fec6b3530e718b688422727396", + "sha256:9f7c16705f44e0504a3a2a14197c1f0b32a95731d251777dcb060aa83022cb2d", + "sha256:9fb218c8a12e51d7ead2a7c9e101a04982237d4855716af2e9499306728fb485", + "sha256:a74ba0c356aaa3bb8e3eb79606a87669e7ec6444be352870623025d75a14a2bf", + "sha256:b4f69b3700201b80bb82c3a97d5e9254084f6dd5fb5b16fc1a7b974260f89f43", + "sha256:bc2ec7c7b5d66b8ec9ce9f720dbb5fa4bace0f545acd34870eff4a369b44bf37", + "sha256:c189af0545965fa8d3b9613cfdb0cd37f9d71349e0f7750e1fd704648d475ed2", + "sha256:c1fbe7621c167ecaa38ad29643d77a9ce7311583761abf7836e1510c580bf3dd", + "sha256:c7cf14a27b0d6adfaebb3ae4153f1e516df54e47e42dcc073d7b3d76111a8d86", + "sha256:c9f72a021fbb792ce98306ffb0c348b3c9cb967dce0f12a49aa4c3d3fdefa967", + "sha256:cd25d2a9d2b36fcb318882481367956d2cf91329f6892fe5d385c346c0649629", + "sha256:ce543ed15570eedbb85df19b0a1a7314a9c8141a36ce089c0a894adbfccb4568", + "sha256:ce7b031a6fc11365970e6a5686d7ba8c63e4c1cf1ea143811acbb524295eabed", + "sha256:d35e3c8d9b1268cbf5d3670285feb3528f6680420eafe35cccc686b73c1e330f", + "sha256:d50b6aec14bc737742ca96e85d6d0a5f9bfbded018264b3b70ff9d8c33485551", + "sha256:d5d0dae4cfd56969d23d94dc8e89fb6a217be461c69090768227beb8ed28c0a3", + "sha256:d5db32e2a6ccbb3d34d87c87b432959e0db29755727afb37290e10f6e8e62614", + "sha256:d72e2ecc68a942e8cf9739619b7f408cc7b272b279b56b2c83c6123fcfa5cdff", + "sha256:d737a602fbd82afd892ca746392401b634e278cb65d55c4b7a8f48e9ef8d008d", + "sha256:d80cf684b541685fccdd84c485b31ce73fc5c9b5d7523bf1394ce134a60c6883", + "sha256:db24668940f82321e746773a4bc617bfac06ec831e5c88b643f91f122a785684", + "sha256:dbc02381779d412145331789b40cc7b11fdf449e5d94f6bc0b080db0a56ea3f0", + "sha256:dffe31a7f47b603318c609f378ebcd57f1554a3a6a8effbc59c3c69f804296de", + "sha256:edf4392b77bdc81f36e92d3a07a5cd072f90253197f4a52a55a8cec48a12483b", + "sha256:efe8c0681042536e0d06c11f48cebe759707c9e9abf880ee213541c5b46c5bf3", + "sha256:f31f9fdbfecb042d046f9d91270a0ba28368a723302786c0009ee9b9f1f60199", + "sha256:f88a0b92277de8e3ca715a0d79d68dc82807457dae3ab8699c758f07c20b3c51", + "sha256:faaf07ea35355b01a35cb442dd950d8f1bb5b040a7787791a535de13db15ed90" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==10.0.0" + }, + "platformdirs": { + "hashes": [ + "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d", + "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d" + ], + "markers": "python_version >= '3.7'", + "version": "==3.10.0" + }, + "pluggy": { + "hashes": [ + "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849", + "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3" + ], + "markers": "python_version >= '3.7'", + "version": "==1.2.0" + }, + "prompt-toolkit": { + "hashes": [ + "sha256:04505ade687dc26dc4284b1ad19a83be2f2afe83e7a828ace0c72f3a1df72aac", + "sha256:9dffbe1d8acf91e3de75f3b544e4842382fc06c6babe903ac9acb74dc6e08d88" + ], + "markers": "python_full_version >= '3.7.0'", + "version": "==3.0.39" + }, + "psutil": { + "hashes": [ + "sha256:104a5cc0e31baa2bcf67900be36acde157756b9c44017b86b2c049f11957887d", + "sha256:3c6f686f4225553615612f6d9bc21f1c0e305f75d7d8454f9b46e901778e7217", + "sha256:4aef137f3345082a3d3232187aeb4ac4ef959ba3d7c10c33dd73763fbc063da4", + "sha256:5410638e4df39c54d957fc51ce03048acd8e6d60abc0f5107af51e5fb566eb3c", + "sha256:5b9b8cb93f507e8dbaf22af6a2fd0ccbe8244bf30b1baad6b3954e935157ae3f", + "sha256:7a7dd9997128a0d928ed4fb2c2d57e5102bb6089027939f3b722f3a210f9a8da", + "sha256:89518112647f1276b03ca97b65cc7f64ca587b1eb0278383017c2a0dcc26cbe4", + "sha256:8c5f7c5a052d1d567db4ddd231a9d27a74e8e4a9c3f44b1032762bd7b9fdcd42", + "sha256:ab8ed1a1d77c95453db1ae00a3f9c50227ebd955437bcf2a574ba8adbf6a74d5", + "sha256:acf2aef9391710afded549ff602b5887d7a2349831ae4c26be7c807c0a39fac4", + "sha256:b258c0c1c9d145a1d5ceffab1134441c4c5113b2417fafff7315a917a026c3c9", + "sha256:be8929ce4313f9f8146caad4272f6abb8bf99fc6cf59344a3167ecd74f4f203f", + "sha256:c607bb3b57dc779d55e1554846352b4e358c10fff3abf3514a7a6601beebdb30", + "sha256:ea8518d152174e1249c4f2a1c89e3e6065941df2fa13a1ab45327716a23c2b48" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==5.9.5" + }, + "pure-eval": { + "hashes": [ + "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350", + "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3" + ], + "version": "==0.2.2" + }, + "pycodestyle": { + "hashes": [ + "sha256:259bcc17857d8a8b3b4a2327324b79e5f020a13c16074670f9c8c8f872ea76d0", + "sha256:5d1013ba8dc7895b548be5afb05740ca82454fd899971563d2ef625d090326f8" + ], + "markers": "python_version >= '3.8'", + "version": "==2.11.0" + }, + "pyflakes": { + "hashes": [ + "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774", + "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc" + ], + "markers": "python_version >= '3.8'", + "version": "==3.1.0" + }, + "pygments": { + "hashes": [ + "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692", + "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29" + ], + "markers": "python_version >= '3.7'", + "version": "==2.16.1" + }, + "pyparsing": { + "hashes": [ + "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb", + "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc" + ], + "markers": "python_full_version >= '3.6.8'", + "version": "==3.0.9" + }, + "pytest": { + "hashes": [ + "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32", + "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==7.4.0" + }, + "python-dateutil": { + "hashes": [ + "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", + "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.8.2" + }, + "pywin32": { + "hashes": [ + "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d", + "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65", + "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e", + "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b", + "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4", + "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040", + "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a", + "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36", + "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8", + "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e", + "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802", + "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a", + "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407", + "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0" + ], + "markers": "sys_platform == 'win32' and platform_python_implementation != 'PyPy'", + "version": "==306" + }, + "pyzmq": { + "hashes": [ + "sha256:019e59ef5c5256a2c7378f2fb8560fc2a9ff1d315755204295b2eab96b254d0a", + "sha256:034239843541ef7a1aee0c7b2cb7f6aafffb005ede965ae9cbd49d5ff4ff73cf", + "sha256:03b3f49b57264909aacd0741892f2aecf2f51fb053e7d8ac6767f6c700832f45", + "sha256:047a640f5c9c6ade7b1cc6680a0e28c9dd5a0825135acbd3569cc96ea00b2505", + "sha256:04ccbed567171579ec2cebb9c8a3e30801723c575601f9a990ab25bcac6b51e2", + "sha256:057e824b2aae50accc0f9a0570998adc021b372478a921506fddd6c02e60308e", + "sha256:11baebdd5fc5b475d484195e49bae2dc64b94a5208f7c89954e9e354fc609d8f", + "sha256:11c1d2aed9079c6b0c9550a7257a836b4a637feb334904610f06d70eb44c56d2", + "sha256:11d58723d44d6ed4dd677c5615b2ffb19d5c426636345567d6af82be4dff8a55", + "sha256:12720a53e61c3b99d87262294e2b375c915fea93c31fc2336898c26d7aed34cd", + "sha256:17ef5f01d25b67ca8f98120d5fa1d21efe9611604e8eb03a5147360f517dd1e2", + "sha256:18d43df3f2302d836f2a56f17e5663e398416e9dd74b205b179065e61f1a6edf", + "sha256:1a5d26fe8f32f137e784f768143728438877d69a586ddeaad898558dc971a5ae", + "sha256:1af379b33ef33757224da93e9da62e6471cf4a66d10078cf32bae8127d3d0d4a", + "sha256:1ccf825981640b8c34ae54231b7ed00271822ea1c6d8ba1090ebd4943759abf5", + "sha256:21eb4e609a154a57c520e3d5bfa0d97e49b6872ea057b7c85257b11e78068222", + "sha256:2243700cc5548cff20963f0ca92d3e5e436394375ab8a354bbea2b12911b20b0", + "sha256:255ca2b219f9e5a3a9ef3081512e1358bd4760ce77828e1028b818ff5610b87b", + "sha256:259c22485b71abacdfa8bf79720cd7bcf4b9d128b30ea554f01ae71fdbfdaa23", + "sha256:25f0e6b78220aba09815cd1f3a32b9c7cb3e02cb846d1cfc526b6595f6046618", + "sha256:273bc3959bcbff3f48606b28229b4721716598d76b5aaea2b4a9d0ab454ec062", + "sha256:292fe3fc5ad4a75bc8df0dfaee7d0babe8b1f4ceb596437213821f761b4589f9", + "sha256:2ca57a5be0389f2a65e6d3bb2962a971688cbdd30b4c0bd188c99e39c234f414", + "sha256:2d163a18819277e49911f7461567bda923461c50b19d169a062536fffe7cd9d2", + "sha256:2d81f1ddae3858b8299d1da72dd7d19dd36aab654c19671aa8a7e7fb02f6638a", + "sha256:2f957ce63d13c28730f7fd6b72333814221c84ca2421298f66e5143f81c9f91f", + "sha256:330f9e188d0d89080cde66dc7470f57d1926ff2fb5576227f14d5be7ab30b9fa", + "sha256:34c850ce7976d19ebe7b9d4b9bb8c9dfc7aac336c0958e2651b88cbd46682123", + "sha256:35b5ab8c28978fbbb86ea54958cd89f5176ce747c1fb3d87356cf698048a7790", + "sha256:3669cf8ee3520c2f13b2e0351c41fea919852b220988d2049249db10046a7afb", + "sha256:381469297409c5adf9a0e884c5eb5186ed33137badcbbb0560b86e910a2f1e76", + "sha256:3d0a409d3b28607cc427aa5c30a6f1e4452cc44e311f843e05edb28ab5e36da0", + "sha256:44e58a0554b21fc662f2712814a746635ed668d0fbc98b7cb9d74cb798d202e6", + "sha256:458dea649f2f02a0b244ae6aef8dc29325a2810aa26b07af8374dc2a9faf57e3", + "sha256:48e466162a24daf86f6b5ca72444d2bf39a5e58da5f96370078be67c67adc978", + "sha256:49d238cf4b69652257db66d0c623cd3e09b5d2e9576b56bc067a396133a00d4a", + "sha256:4ca1ed0bb2d850aa8471387882247c68f1e62a4af0ce9c8a1dbe0d2bf69e41fb", + "sha256:52533489f28d62eb1258a965f2aba28a82aa747202c8fa5a1c7a43b5db0e85c1", + "sha256:548d6482dc8aadbe7e79d1b5806585c8120bafa1ef841167bc9090522b610fa6", + "sha256:5619f3f5a4db5dbb572b095ea3cb5cc035335159d9da950830c9c4db2fbb6995", + "sha256:57459b68e5cd85b0be8184382cefd91959cafe79ae019e6b1ae6e2ba8a12cda7", + "sha256:5a34d2395073ef862b4032343cf0c32a712f3ab49d7ec4f42c9661e0294d106f", + "sha256:61706a6b6c24bdece85ff177fec393545a3191eeda35b07aaa1458a027ad1304", + "sha256:724c292bb26365659fc434e9567b3f1adbdb5e8d640c936ed901f49e03e5d32e", + "sha256:73461eed88a88c866656e08f89299720a38cb4e9d34ae6bf5df6f71102570f2e", + "sha256:76705c9325d72a81155bb6ab48d4312e0032bf045fb0754889133200f7a0d849", + "sha256:76c1c8efb3ca3a1818b837aea423ff8a07bbf7aafe9f2f6582b61a0458b1a329", + "sha256:77a41c26205d2353a4c94d02be51d6cbdf63c06fbc1295ea57dad7e2d3381b71", + "sha256:79986f3b4af059777111409ee517da24a529bdbd46da578b33f25580adcff728", + "sha256:7cff25c5b315e63b07a36f0c2bab32c58eafbe57d0dce61b614ef4c76058c115", + "sha256:7f7e58effd14b641c5e4dec8c7dab02fb67a13df90329e61c869b9cc607ef752", + "sha256:820c4a08195a681252f46926de10e29b6bbf3e17b30037bd4250d72dd3ddaab8", + "sha256:87e34f31ca8f168c56d6fbf99692cc8d3b445abb5bfd08c229ae992d7547a92a", + "sha256:8f03d3f0d01cb5a018debeb412441996a517b11c5c17ab2001aa0597c6d6882c", + "sha256:90f26dc6d5f241ba358bef79be9ce06de58d477ca8485e3291675436d3827cf8", + "sha256:955215ed0604dac5b01907424dfa28b40f2b2292d6493445dd34d0dfa72586a8", + "sha256:985bbb1316192b98f32e25e7b9958088431d853ac63aca1d2c236f40afb17c83", + "sha256:a382372898a07479bd34bda781008e4a954ed8750f17891e794521c3e21c2e1c", + "sha256:a882ac0a351288dd18ecae3326b8a49d10c61a68b01419f3a0b9a306190baf69", + "sha256:aa8d6cdc8b8aa19ceb319aaa2b660cdaccc533ec477eeb1309e2a291eaacc43a", + "sha256:abc719161780932c4e11aaebb203be3d6acc6b38d2f26c0f523b5b59d2fc1996", + "sha256:abf34e43c531bbb510ae7e8f5b2b1f2a8ab93219510e2b287a944432fad135f3", + "sha256:ade6d25bb29c4555d718ac6d1443a7386595528c33d6b133b258f65f963bb0f6", + "sha256:afea96f64efa98df4da6958bae37f1cbea7932c35878b185e5982821bc883369", + "sha256:b1579413ae492b05de5a6174574f8c44c2b9b122a42015c5292afa4be2507f28", + "sha256:b3451108ab861040754fa5208bca4a5496c65875710f76789a9ad27c801a0075", + "sha256:b9af3757495c1ee3b5c4e945c1df7be95562277c6e5bccc20a39aec50f826cd0", + "sha256:bc16ac425cc927d0a57d242589f87ee093884ea4804c05a13834d07c20db203c", + "sha256:c2910967e6ab16bf6fbeb1f771c89a7050947221ae12a5b0b60f3bca2ee19bca", + "sha256:c2b92812bd214018e50b6380ea3ac0c8bb01ac07fcc14c5f86a5bb25e74026e9", + "sha256:c2f20ce161ebdb0091a10c9ca0372e023ce24980d0e1f810f519da6f79c60800", + "sha256:c56d748ea50215abef7030c72b60dd723ed5b5c7e65e7bc2504e77843631c1a6", + "sha256:c7c133e93b405eb0d36fa430c94185bdd13c36204a8635470cccc200723c13bb", + "sha256:c9c6c9b2c2f80747a98f34ef491c4d7b1a8d4853937bb1492774992a120f475d", + "sha256:cbc8df5c6a88ba5ae385d8930da02201165408dde8d8322072e3e5ddd4f68e22", + "sha256:cff084c6933680d1f8b2f3b4ff5bbb88538a4aac00d199ac13f49d0698727ecb", + "sha256:d2045d6d9439a0078f2a34b57c7b18c4a6aef0bee37f22e4ec9f32456c852c71", + "sha256:d20a0ddb3e989e8807d83225a27e5c2eb2260eaa851532086e9e0fa0d5287d83", + "sha256:d457aed310f2670f59cc5b57dcfced452aeeed77f9da2b9763616bd57e4dbaae", + "sha256:d89528b4943d27029a2818f847c10c2cecc79fa9590f3cb1860459a5be7933eb", + "sha256:db0b2af416ba735c6304c47f75d348f498b92952f5e3e8bff449336d2728795d", + "sha256:deee9ca4727f53464daf089536e68b13e6104e84a37820a88b0a057b97bba2d2", + "sha256:df27ffddff4190667d40de7beba4a950b5ce78fe28a7dcc41d6f8a700a80a3c0", + "sha256:e0c95ddd4f6e9fca4e9e3afaa4f9df8552f0ba5d1004e89ef0a68e1f1f9807c7", + "sha256:e1c1be77bc5fb77d923850f82e55a928f8638f64a61f00ff18a67c7404faf008", + "sha256:e1ffa1c924e8c72778b9ccd386a7067cddf626884fd8277f503c48bb5f51c762", + "sha256:e2400a94f7dd9cb20cd012951a0cbf8249e3d554c63a9c0cdfd5cbb6c01d2dec", + "sha256:e61f091c3ba0c3578411ef505992d356a812fb200643eab27f4f70eed34a29ef", + "sha256:e8a701123029cc240cea61dd2d16ad57cab4691804143ce80ecd9286b464d180", + "sha256:eadbefd5e92ef8a345f0525b5cfd01cf4e4cc651a2cffb8f23c0dd184975d787", + "sha256:f32260e556a983bc5c7ed588d04c942c9a8f9c2e99213fec11a031e316874c7e", + "sha256:f8115e303280ba09f3898194791a153862cbf9eef722ad8f7f741987ee2a97c7", + "sha256:fedbdc753827cf014c01dbbee9c3be17e5a208dcd1bf8641ce2cd29580d1f0d4" + ], + "markers": "python_version >= '3.6'", + "version": "==25.1.1" + }, + "six": { + "hashes": [ + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.16.0" + }, + "stack-data": { + "hashes": [ + "sha256:32d2dd0376772d01b6cb9fc996f3c8b57a357089dec328ed4b6553d037eaf815", + "sha256:cbb2a53eb64e5785878201a97ed7c7b94883f48b87bfb0bbe8b623c74679e4a8" + ], + "version": "==0.6.2" + }, + "tomli": { + "hashes": [ + "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", + "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" + ], + "markers": "python_version < '3.11'", + "version": "==2.0.1" + }, + "tornado": { + "hashes": [ + "sha256:1bd19ca6c16882e4d37368e0152f99c099bad93e0950ce55e71daed74045908f", + "sha256:22d3c2fa10b5793da13c807e6fc38ff49a4f6e1e3868b0a6f4164768bb8e20f5", + "sha256:502fba735c84450974fec147340016ad928d29f1e91f49be168c0a4c18181e1d", + "sha256:65ceca9500383fbdf33a98c0087cb975b2ef3bfb874cb35b8de8740cf7f41bd3", + "sha256:71a8db65160a3c55d61839b7302a9a400074c9c753040455494e2af74e2501f2", + "sha256:7ac51f42808cca9b3613f51ffe2a965c8525cb1b00b7b2d56828b8045354f76a", + "sha256:7d01abc57ea0dbb51ddfed477dfe22719d376119844e33c661d873bf9c0e4a16", + "sha256:805d507b1f588320c26f7f097108eb4023bbaa984d63176d1652e184ba24270a", + "sha256:9dc4444c0defcd3929d5c1eb5706cbe1b116e762ff3e0deca8b715d14bf6ec17", + "sha256:ceb917a50cd35882b57600709dd5421a418c29ddc852da8bcdab1f0db33406b0", + "sha256:e7d8db41c0181c80d76c982aacc442c0783a2c54d6400fe028954201a2e032fe" + ], + "markers": "python_version >= '3.8'", + "version": "==6.3.3" + }, + "traitlets": { + "hashes": [ + "sha256:9e6ec080259b9a5940c797d58b613b5e31441c2257b87c2e795c5228ae80d2d8", + "sha256:f6cde21a9c68cf756af02035f72d5a723bf607e862e7be33ece505abf4a3bad9" + ], + "markers": "python_version >= '3.7'", + "version": "==5.9.0" + }, + "wcwidth": { + "hashes": [ + "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e", + "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0" + ], + "version": "==0.2.6" + } + } +} diff --git a/README.md b/README.md index 49025d46..deafba72 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ ![Python 3.8 3.9](https://github.com/f1tenth/f1tenth_gym/actions/workflows/ci.yml/badge.svg) ![Docker](https://github.com/f1tenth/f1tenth_gym/actions/workflows/docker.yml/badge.svg) +![Code Style](https://github.com/f1tenth/f1tenth_gym/actions/workflows/lint.yml/badge.svg) + # The F1TENTH Gym environment This is the repository of the F1TENTH Gym environment. diff --git a/docs/conf.py b/docs/conf.py index 0e049f02..c3decdd4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,23 +1,24 @@ +# flake8: noqa import os -import sys + import sphinx_rtd_theme -source_suffix = '.rst' -source_encoding = 'utf-8-sig' +source_suffix = ".rst" +source_encoding = "utf-8-sig" # -- Language ---------------------------------------------------------------- -env_tags = os.getenv('SPHINX_TAGS') -if env_tags != None: - for tag in env_tags.split(','): - print('Adding Sphinx tag: %s' % tag.strip()) +env_tags = os.getenv("SPHINX_TAGS") +if env_tags is not None: + for tag in env_tags.split(","): + print("Adding Sphinx tag: %s" % tag.strip()) tags.add(tag.strip()) -language = os.getenv('READTHEDOCS_LANGUAGE', 'en') -is_i18n = tags.has('i18n') +language = os.getenv("READTHEDOCS_LANGUAGE", "en") +is_i18n = tags.has("i18n") # -- Theme ------------------------------------------------------------------- -on_rtd = os.environ.get('READTHEDOCS', None) == 'True' -html_theme = 'sphinx_rtd_theme' +on_rtd = os.environ.get("READTHEDOCS", None) == "True" +html_theme = "sphinx_rtd_theme" html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] if on_rtd: using_rtd_theme = True @@ -25,40 +26,36 @@ # 'typekit_id': 'hiw1hhg', # 'analytics_id': '', # 'sticky_navigation': True # Set to False to disable the sticky nav while scrolling. - 'logo_only': False, # if we have a html_logo below, this shows /only/ the logo with no title text - 'collapse_navigation': False, # Collapse navigation (False makes it tree-like) - 'prev_next_buttons_location': 'bottom', + "logo_only": False, # if we have a html_logo below, this shows /only/ the logo with no title text + "collapse_navigation": False, # Collapse navigation (False makes it tree-like) + "prev_next_buttons_location": "bottom", # 'display_version': True, # Display the docs version # 'navigation_depth': 4, # Depth of the headers shown in the navigation bar } html_context = { - "display_github": not is_i18n, # Integrate GitHub - "github_user": "f1tenth", # Username - "github_repo": "f1tenth_gym", # Repo name - "github_version": "exp_py", # Version - "conf_py_path": "/docs/", # Path in the checkout to the docs root + "display_github": not is_i18n, # Integrate GitHub + "github_user": "f1tenth", # Username + "github_repo": "f1tenth_gym", # Repo name + "github_version": "exp_py", # Version + "conf_py_path": "/docs/", # Path in the checkout to the docs root } -html_favicon = 'assets/f1_stickers_02.png' +html_favicon = "assets/f1_stickers_02.png" -html_css_files = [ - 'css/custom.css' -] +html_css_files = ["css/custom.css"] -html_js_files = [ - 'css/custom.js' -] -html_logo = 'assets/f1tenth_gym.svg' +html_js_files = ["css/custom.js"] +html_logo = "assets/f1tenth_gym.svg" # -- Project information ----------------------------------------------------- -project = 'f1tenth_gym' +project = "f1tenth_gym" copyright = "2021, Hongrui Zheng, Matthew O'Kelly, Aman Sinha" -author = 'Hongrui Zheng' +author = "Hongrui Zheng" # The full version, including alpha/beta/rc tags -release = 'latest' -version = 'latest' +release = "latest" +version = "latest" # -- General configuration --------------------------------------------------- @@ -66,23 +63,20 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = [ "breathe", "sphinx_rtd_theme", "sphinx.ext.autosectionlabel" -] +extensions = ["breathe", "sphinx_rtd_theme", "sphinx.ext.autosectionlabel"] # Breathe configuration -breathe_projects = { - "f1tenth_gym":"./xml" - } +breathe_projects = {"f1tenth_gym": "./xml"} # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] \ No newline at end of file +html_static_path = ["_static"] diff --git a/examples/random_trackgen.py b/examples/random_trackgen.py index 2130e9c4..fb725652 100644 --- a/examples/random_trackgen.py +++ b/examples/random_trackgen.py @@ -30,13 +30,13 @@ - shapely - opencv-python """ +import math import pathlib import cv2 -import math +import matplotlib.pyplot as plt import numpy as np import shapely.geometry as shp -import matplotlib.pyplot as plt def main(args): @@ -54,7 +54,7 @@ def main(args): track, track_int, track_ext = create_track() convert_track(track, track_int, track_ext, i, outdir) print(f"[info] saved track {i} in {outdir}/") - except: + except Exception as _: # noqa: F841 print("[error] failed to create track. Retrying...") continue print() @@ -162,7 +162,7 @@ def create_track(): assert i1 != -1 assert i2 != -1 - track = track[i1 : i2 - 1] + track = track[i1: i2 - 1] first_beta = track[0][1] first_perp_x = math.cos(first_beta) first_perp_y = math.sin(first_beta) diff --git a/examples/waypoint_follow.py b/examples/waypoint_follow.py index 5a0ffcea..ee4763c1 100644 --- a/examples/waypoint_follow.py +++ b/examples/waypoint_follow.py @@ -1,11 +1,8 @@ import time -import yaml + import gymnasium as gym import numpy as np -from argparse import Namespace - from numba import njit - from pyglet.gl import GL_POINTS """ @@ -233,7 +230,7 @@ def _get_current_waypoint(self, waypoints, lookahead_distance, position, theta): lookahead_point, i2, t2 = first_point_on_trajectory_intersecting_circle( position, lookahead_distance, wpts, t1, wrap=True ) - if i2 == None: + if i2 is None: return None current_waypoint = np.empty((3,), dtype=np.float32) # x, y diff --git a/gym/f110_gym/envs/__init__.py b/gym/f110_gym/envs/__init__.py index 1cf17c53..33077f3c 100644 --- a/gym/f110_gym/envs/__init__.py +++ b/gym/f110_gym/envs/__init__.py @@ -1,6 +1 @@ -from f110_gym.envs.integrator import * -from f110_gym.envs.dynamic_models import * -from f110_gym.envs.f110_env import F110Env -from f110_gym.envs.laser_models import * -from f110_gym.envs.base_classes import * -from f110_gym.envs.collision_models import * +from .f110_env import F110Env # noqa: F401 This is needed to make easy imports work diff --git a/gym/f110_gym/envs/action.py b/gym/f110_gym/envs/action.py index a4372f3b..cc779fa5 100644 --- a/gym/f110_gym/envs/action.py +++ b/gym/f110_gym/envs/action.py @@ -1,6 +1,6 @@ from abc import abstractmethod from enum import Enum -from typing import Any, Tuple, Dict +from typing import Any, Dict, Tuple import gymnasium as gym import numpy as np diff --git a/gym/f110_gym/envs/base_classes.py b/gym/f110_gym/envs/base_classes.py index b2156d1d..055817f8 100644 --- a/gym/f110_gym/envs/base_classes.py +++ b/gym/f110_gym/envs/base_classes.py @@ -27,12 +27,11 @@ Author: Hongrui Zheng """ import numpy as np - -from f110_gym.envs import DynamicModel +from f110_gym.envs.dynamic_models import DynamicModel from f110_gym.envs.action import CarAction +from f110_gym.envs.collision_models import collision_multiple, get_vertices from f110_gym.envs.integrator import EulerIntegrator, IntegratorType from f110_gym.envs.laser_models import ScanSimulator2D, check_ttc_jit, ray_cast -from f110_gym.envs.collision_models import get_vertices, collision_multiple class RaceCar(object): @@ -496,8 +495,6 @@ def step(self, control_inputs): observations (dict): dictionary for observations: poses of agents, current laser scan of each agent, collision indicators, etc. """ - agent_scans = [] - # looping over agents for i, agent in enumerate(self.agents): # update each agent's pose @@ -513,7 +510,7 @@ def step(self, control_inputs): for i, agent in enumerate(self.agents): # update agent's information on other agents opp_poses = np.concatenate( - (self.agent_poses[0:i, :], self.agent_poses[i + 1 :, :]), axis=0 + (self.agent_poses[0:i, :], self.agent_poses[i + 1:, :]), axis=0 ) agent.update_opp_poses(opp_poses) diff --git a/gym/f110_gym/envs/collision_models.py b/gym/f110_gym/envs/collision_models.py index 8961cdc8..87547ab6 100644 --- a/gym/f110_gym/envs/collision_models.py +++ b/gym/f110_gym/envs/collision_models.py @@ -271,89 +271,3 @@ def get_vertices(pose, length, width): [[rl[0], rl[1]], [rr[0], rr[1]], [fr[0], fr[1]], [fl[0], fl[1]]] ) return vertices - - -""" -Unit test for GJK collision checks -Author: Hongrui Zheng -""" - -import time -import unittest - - -class CollisionTests(unittest.TestCase): - def setUp(self): - # test params - np.random.seed(1234) - - # Collision check body - self.vertices1 = np.asarray([[4, 11.0], [5, 5], [9, 9], [10, 10]]) - - # car size - self.length = 0.32 - self.width = 0.22 - - def test_get_vert(self): - test_pose = np.array([2.3, 6.7, 0.8]) - vertices = get_vertices(test_pose, self.length, self.width) - rect = np.vstack((vertices, vertices[0, :])) - import matplotlib.pyplot as plt - - plt.scatter(test_pose[0], test_pose[1], c="red") - plt.plot(rect[:, 0], rect[:, 1]) - plt.xlim([1, 4]) - plt.ylim([5, 8]) - plt.axes().set_aspect("equal") - plt.show() - self.assertTrue(vertices.shape == (4, 2)) - - def test_get_vert_fps(self): - test_pose = np.array([2.3, 6.7, 0.8]) - start = time.time() - for _ in range(1000): - vertices = get_vertices(test_pose, self.length, self.width) - elapsed = time.time() - start - fps = 1000 / elapsed - print("get vertices fps:", fps) - self.assertTrue(fps > 500) - - def test_random_collision(self): - # perturb the body by a small amount and make sure it all collides with the original body - for _ in range(1000): - a = self.vertices1 + np.random.normal(size=(self.vertices1.shape)) / 100.0 - b = self.vertices1 + np.random.normal(size=(self.vertices1.shape)) / 100.0 - self.assertTrue(collision(a, b)) - - def test_multiple_collisions(self): - a = self.vertices1 + np.random.normal(size=(self.vertices1.shape)) / 100.0 - b = self.vertices1 + np.random.normal(size=(self.vertices1.shape)) / 100.0 - c = self.vertices1 + np.random.normal(size=(self.vertices1.shape)) / 100.0 - d = self.vertices1 + np.random.normal(size=(self.vertices1.shape)) / 100.0 - e = self.vertices1 + np.random.normal(size=(self.vertices1.shape)) / 100.0 - f = self.vertices1 + np.random.normal(size=(self.vertices1.shape)) / 100.0 - g = self.vertices1 + 10.0 - allv = np.stack((a, b, c, d, e, f, g)) - collisions, collision_idx = collision_multiple(allv) - self.assertTrue( - np.all(collisions == np.array([1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0])) - ) - self.assertTrue( - np.all(collision_idx == np.array([5.0, 5.0, 5.0, 5.0, 5.0, 4.0, -1.0])) - ) - - def test_fps(self): - # also perturb the body but mainly want to test GJK speed - start = time.time() - for _ in range(1000): - a = self.vertices1 + np.random.normal(size=(self.vertices1.shape)) / 100.0 - b = self.vertices1 + np.random.normal(size=(self.vertices1.shape)) / 100.0 - collision(a, b) - elapsed = time.time() - start - fps = 1000 / elapsed - print("gjk fps:", fps) - self.assertTrue(fps > 500) - - -if __name__ == "__main__": - unittest.main() diff --git a/gym/f110_gym/envs/cubic_spline.py b/gym/f110_gym/envs/cubic_spline.py index 925b4d4d..8b1a0fab 100644 --- a/gym/f110_gym/envs/cubic_spline.py +++ b/gym/f110_gym/envs/cubic_spline.py @@ -2,9 +2,10 @@ Code from Cubic spline planner Author: Atsushi Sakai(@Atsushi_twi) """ +import bisect import math + import numpy as np -import bisect class CubicSpline1D: diff --git a/gym/f110_gym/envs/dynamic_models.py b/gym/f110_gym/envs/dynamic_models.py index f4a60e32..d1f5e7a5 100644 --- a/gym/f110_gym/envs/dynamic_models.py +++ b/gym/f110_gym/envs/dynamic_models.py @@ -1,14 +1,22 @@ # Copyright 2020 Technical University of Munich, Professorship of Cyber-Physical Systems, Matthew O'Kelly, Aman Sinha, Hongrui Zheng -# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: -# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. -# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. """ @@ -33,7 +41,7 @@ class DynamicModel(Enum): def from_string(model: str): if model == "ks": warnings.warn( - f"Chosen model is KS. This is different from previous versions of the gym." + "Chosen model is KS. This is different from previous versions of the gym." ) return DynamicModel.KS elif model == "st": @@ -451,3 +459,91 @@ def pid(speed, steer, current_speed, current_steer, max_sv, max_a, max_v, min_v) accl = kp * vel_diff return accl, sv + + +def func_KS( + x, + t, + u, + mu, + C_Sf, + C_Sr, + lf, + lr, + h, + m, + I, + s_min, + s_max, + sv_min, + sv_max, + v_switch, + a_max, + v_min, + v_max, +): + f = vehicle_dynamics_ks( + x, + u, + mu, + C_Sf, + C_Sr, + lf, + lr, + h, + m, + I, + s_min, + s_max, + sv_min, + sv_max, + v_switch, + a_max, + v_min, + v_max, + ) + return f + + +def func_ST( + x, + t, + u, + mu, + C_Sf, + C_Sr, + lf, + lr, + h, + m, + I, + s_min, + s_max, + sv_min, + sv_max, + v_switch, + a_max, + v_min, + v_max, +): + f = vehicle_dynamics_st( + x, + u, + mu, + C_Sf, + C_Sr, + lf, + lr, + h, + m, + I, + s_min, + s_max, + sv_min, + sv_max, + v_switch, + a_max, + v_min, + v_max, + ) + return f diff --git a/gym/f110_gym/envs/f110_env.py b/gym/f110_gym/envs/f110_env.py index ce5c933e..7353fe3c 100644 --- a/gym/f110_gym/envs/f110_env.py +++ b/gym/f110_gym/envs/f110_env.py @@ -24,31 +24,24 @@ Author: Hongrui Zheng """ +import time + # gym imports import gymnasium as gym - -from f110_gym.envs import IntegratorType -from f110_gym.envs.action import CarActionEnum, from_single_to_multi_action_space - -from f110_gym.envs.track import Track - -# base classes -from f110_gym.envs.base_classes import Simulator, DynamicModel -from f110_gym.envs.observation import observation_factory - -from f110_gym.envs.utils import deep_update - - # others import numpy as np -import os -import time - # gl import pyglet +from f110_gym.envs.integrator import IntegratorType +from f110_gym.envs.action import (CarActionEnum, + from_single_to_multi_action_space) +# base classes +from f110_gym.envs.base_classes import DynamicModel, Simulator +from f110_gym.envs.observation import observation_factory +from f110_gym.envs.track import Track +from f110_gym.envs.utils import deep_update pyglet.options["debug_gl"] = False -from pyglet import gl # rendering VIDEO_W = 600 @@ -183,12 +176,6 @@ def __init__(self, config: dict = None, render_mode=None, **kwargs): self.render_obs = None self.render_mode = render_mode - def __del__(self): - """ - Finalizer, does cleanup - """ - pass - @classmethod def default_config(cls) -> dict: """ diff --git a/gym/f110_gym/envs/f110_env_backup.py b/gym/f110_gym/envs/f110_env_backup.py index 5fd37721..4e60cdfc 100644 --- a/gym/f110_gym/envs/f110_env_backup.py +++ b/gym/f110_gym/envs/f110_env_backup.py @@ -24,31 +24,23 @@ Author: Hongrui Zheng """ -# gym imports -import gym -from gym import error, spaces, utils -from gym.utils import seeding - -# zmq imports -import zmq - -# protobuf import -import sim_requests_pb2 - -# others -import numpy as np - -from numba import njit -from scipy.ndimage import distance_transform_edt as edt - -from PIL import Image -import sys import os import signal import subprocess -import math +import sys + +# others +import numpy as np +# protobuf import +import sim_requests_pb2 import yaml -import csv +# zmq imports +import zmq +from PIL import Image + +# gym imports +import gym +from gym import utils # from matplotlib.pyplot import imshow # import matplotlib.pyplot as plt @@ -158,7 +150,7 @@ def __init__(self): # self.socket.connect('tcp://localhost:6666') self.port = min_port + tries break - except: + except Exception as _: # noqa: F841 tries = tries + 1 # print('Gym env - retrying for ' + str(tries) + ' times') diff --git a/gym/f110_gym/envs/integrator.py b/gym/f110_gym/envs/integrator.py index ec048807..57eb9cb5 100644 --- a/gym/f110_gym/envs/integrator.py +++ b/gym/f110_gym/envs/integrator.py @@ -11,7 +11,7 @@ class IntegratorType(Enum): def from_string(integrator: str): if integrator == "rk4": warnings.warn( - f"Chosen integrator is RK4. This is different from previous versions of the gym." + "Chosen integrator is RK4. This is different from previous versions of the gym." ) return RK4Integrator() elif integrator == "euler": diff --git a/gym/f110_gym/envs/laser_models.py b/gym/f110_gym/envs/laser_models.py index 450a2967..e8888bd4 100644 --- a/gym/f110_gym/envs/laser_models.py +++ b/gym/f110_gym/envs/laser_models.py @@ -26,17 +26,12 @@ Author: Hongrui Zheng """ -import numpy as np -from numba import njit -from scipy.ndimage import distance_transform_edt as edt -from PIL import Image -import os -import yaml - import unittest -import timeit +import numpy as np from f110_gym.envs.track import Track +from numba import njit +from scipy.ndimage import distance_transform_edt as edt def get_dt(bitmap, resolution): @@ -628,7 +623,7 @@ def test_fps(self): start = time.time() for i in range(10000): x_test = i / 10000 - scan = scan_sim.scan(np.array([x_test, 0.0, 0.0]), scan_rng) + scan_sim.scan(np.array([x_test, 0.0, 0.0]), scan_rng) end = time.time() fps = 10000 / (end - start) # print('FPS test') @@ -673,7 +668,7 @@ def main(): scan_rng = np.random.default_rng(seed=12345) scan_sim = ScanSimulator2D(num_beams, fov) scan_sim.set_map(map_path, map_ext) - scan = scan_sim.scan(np.array([0.0, 0.0, 0.0]), scan_rng) + scan_sim.scan(np.array([0.0, 0.0, 0.0]), scan_rng) # fps test import time @@ -681,7 +676,7 @@ def main(): start = time.time() for i in range(10000): x_test = i / 10000 - scan = scan_sim.scan(np.array([x_test, 0.0, 0.0]), scan_rng) + scan_sim.scan(np.array([x_test, 0.0, 0.0]), scan_rng) end = time.time() fps = (end - start) / 10000 print("FPS test") @@ -707,7 +702,7 @@ def update(i): line.set_data(theta, current_scan) return (line,) - ani = FuncAnimation(fig, update, frames=num_iter, blit=True) + FuncAnimation(fig, update, frames=num_iter, blit=True) plt.show() diff --git a/gym/f110_gym/envs/observation.py b/gym/f110_gym/envs/observation.py index 7993b751..2849fd4f 100644 --- a/gym/f110_gym/envs/observation.py +++ b/gym/f110_gym/envs/observation.py @@ -17,7 +17,7 @@ class Observation: :param kwargs: Additional arguments. """ - def __init__(self, env: "F110Env"): + def __init__(self, env): self.env = env @abstractmethod @@ -30,7 +30,7 @@ def observe(self): class OriginalObservation(Observation): - def __init__(self, env: "F110Env"): + def __init__(self, env): super().__init__(env) def space(self): @@ -152,7 +152,7 @@ def observe(self): class FeaturesObservation(Observation): - def __init__(self, env: "F110Env", features: List[str]): + def __init__(self, env, features: List[str]): super().__init__(env) self.features = features @@ -263,7 +263,7 @@ def observe(self): return obs -def observation_factory(env: "F110Env", type: str, **kwargs) -> Observation: +def observation_factory(env, type: str, **kwargs) -> Observation: if type == "original": return OriginalObservation(env) elif type == "features": diff --git a/gym/f110_gym/envs/rendering.py b/gym/f110_gym/envs/rendering.py index 44e06e4c..9d863f43 100644 --- a/gym/f110_gym/envs/rendering.py +++ b/gym/f110_gym/envs/rendering.py @@ -20,25 +20,21 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +# flake8: noqa """ Rendering engine for f1tenth gym env based on pyglet and OpenGL Author: Hongrui Zheng """ -# opengl stuff -import pyglet -from pyglet.gl import * - # other import numpy as np -from PIL import Image -import yaml - -from f110_gym.envs.track import Track - +# opengl stuff +import pyglet # helpers from f110_gym.envs.collision_models import get_vertices +from f110_gym.envs.track import Track +from pyglet.gl import * # zooming constants ZOOM_IN_FACTOR = 1.2 diff --git a/gym/f110_gym/envs/track.py b/gym/f110_gym/envs/track.py index e5e23c8a..2bb99400 100644 --- a/gym/f110_gym/envs/track.py +++ b/gym/f110_gym/envs/track.py @@ -1,8 +1,8 @@ -import os import pathlib import tarfile from dataclasses import dataclass from typing import Tuple +import tempfile import numpy as np import requests @@ -137,12 +137,14 @@ def find_track_dir(track_name): if tracks_r.status_code == 404: raise FileNotFoundError(f"No maps exists for {track_name}.") - with open("/tmp/" + track_name + ".tar.xz", "wb") as f: + tempdir = tempfile.gettempdir() + "/" + + with open(tempdir + track_name + ".tar.xz", "wb") as f: f.write(tracks_r.content) # extract print("Extracting Files for: " + track_name) - tracks_file = tarfile.open("/tmp/" + track_name + ".tar.xz") + tracks_file = tarfile.open(tempdir + track_name + ".tar.xz") tracks_file.extractall(map_dir) tracks_file.close() @@ -237,6 +239,7 @@ def from_track_name(track: str): raceline=raceline, ) except Exception as ex: + print(ex) raise FileNotFoundError(f"could not load track {track}") from ex diff --git a/gym/f110_gym/envs/utils.py b/gym/f110_gym/envs/utils.py index 5b84e30d..2717354c 100644 --- a/gym/f110_gym/envs/utils.py +++ b/gym/f110_gym/envs/utils.py @@ -1,7 +1,5 @@ -from typing import Dict, Any - # types -from typing import TypeVar +from typing import Any, Dict, TypeVar KeyType = TypeVar("KeyType") diff --git a/gym/f110_gym/test/pyglet_test_.py b/gym/f110_gym/test/pyglet_test_.py deleted file mode 100644 index 8d0d355d..00000000 --- a/gym/f110_gym/test/pyglet_test_.py +++ /dev/null @@ -1,208 +0,0 @@ -import numpy as np -from PIL import Image -import yaml - -from pyglet.gl import * -import pyglet -from pyglet import font, graphics, window - -import argparse - - -class Camera: - """A simple 2D camera that contains the speed and offset.""" - - def __init__( - self, window: pyglet.window.Window, scroll_speed=1, min_zoom=1, max_zoom=4 - ): - assert ( - min_zoom <= max_zoom - ), "Minimum zoom must not be greater than maximum zoom" - self._window = window - self.scroll_speed = scroll_speed - self.max_zoom = max_zoom - self.min_zoom = min_zoom - self.offset_x = 0 - self.offset_y = 0 - self._zoom = max(min(1, self.max_zoom), self.min_zoom) - - @property - def zoom(self): - return self._zoom - - @zoom.setter - def zoom(self, value): - """Here we set zoom, clamp value to minimum of min_zoom and max of max_zoom.""" - self._zoom = max(min(value, self.max_zoom), self.min_zoom) - - @property - def position(self): - """Query the current offset.""" - return self.offset_x, self.offset_y - - @position.setter - def position(self, value): - """Set the scroll offset directly.""" - self.offset_x, self.offset_y = value - - def move(self, axis_x, axis_y): - """Move axis direction with scroll_speed. - Example: Move left -> move(-1, 0) - """ - self.offset_x += self.scroll_speed * axis_x - self.offset_y += self.scroll_speed * axis_y - - def begin(self): - # Set the current camera offset so you can draw your scene. - - # Translate using the offset. - view_matrix = self._window.view.translate( - -self.offset_x * self._zoom, -self.offset_y * self._zoom, 0 - ) - # Scale by zoom level. - view_matrix = view_matrix.scale(self._zoom, self._zoom, 1) - - self._window.view = view_matrix - - def end(self): - # Since this is a matrix, you will need to reverse the translate after rendering otherwise - # it will multiply the current offset every draw update pushing it further and further away. - - # Reverse scale, since that was the last transform. - view_matrix = self._window.view.scale(1 / self._zoom, 1 / self._zoom, 1) - # Reverse translate. - view_matrix = view_matrix.translate( - self.offset_x * self._zoom, self.offset_y * self._zoom, 0 - ) - - self._window.view = view_matrix - - def __enter__(self): - self.begin() - - def __exit__(self, exception_type, exception_value, traceback): - self.end() - - -class CenteredCamera(Camera): - """A simple 2D camera class. 0, 0 will be the center of the screen, as opposed to the bottom left.""" - - def begin(self): - x = -self._window.width // 2 / self._zoom + self.offset_x - y = -self._window.height // 2 / self._zoom + self.offset_y - - view_matrix = self._window.view.translate(-x * self._zoom, -y * self._zoom, 0) - view_matrix = view_matrix.scale(self._zoom, self._zoom, 1) - self._window.view = view_matrix - - def end(self): - x = -self._window.width // 2 / self._zoom + self.offset_x - y = -self._window.height // 2 / self._zoom + self.offset_y - - view_matrix = self._window.view.scale(1 / self._zoom, 1 / self._zoom, 1) - view_matrix = view_matrix.translate(x * self._zoom, y * self._zoom, 0) - self._window.view = view_matrix - - -parser = argparse.ArgumentParser() -parser.add_argument( - "--map_path", type=str, required=True, help="Path to the map without extensions" -) -parser.add_argument( - "--map_ext", type=str, required=True, help="Extension of the map image file" -) -args = parser.parse_args() - -# load map yaml -with open(args.map_path + ".yaml", "r") as yaml_stream: - try: - map_metada = yaml.safe_load(yaml_stream) - map_resolution = map_metada["resolution"] - origin = map_metada["origin"] - origin_x = origin[0] - origin_y = origin[1] - except yaml.YAMLError as ex: - print(ex) - -# load map image -map_img = np.array( - Image.open(args.map_path + args.map_ext).transpose(Image.FLIP_TOP_BOTTOM) -).astype(np.float64) -map_height = map_img.shape[0] -map_width = map_img.shape[1] - -# convert map pixels to coordinates -range_x = np.arange(map_width) -range_y = np.arange(map_height) -map_x, map_y = np.meshgrid(range_x, range_y) -map_x = (map_x * map_resolution + origin_x).flatten() -map_y = (map_y * map_resolution + origin_y).flatten() -map_z = np.zeros(map_y.shape) -map_coords = np.vstack((map_x, map_y, map_z)) - -# mask and only leave the obstacle points -map_mask = map_img == 0.0 -map_mask_flat = map_mask.flatten() -map_points = map_coords[:, map_mask_flat].T - -# prep opengl -try: - # Try and create a window with multisampling (antialiasing) - config = Config( - sample_buffers=1, - samples=4, - depth_size=16, - double_buffer=True, - ) - window = window.Window(resizable=True, config=config) -except window.NoSuchConfigException: - # Fall back to no multisampling for old hardware - window = window.Window(resizable=True) - -glClearColor(18 / 255, 4 / 255, 88 / 255, 1.0) -glEnable(GL_DEPTH_TEST) -glTranslatef(25, -5, -60) - -cam = Camera(window) - - -@window.event -def on_resize(width, height): - # Override the default on_resize handler to create a 3D projection - glViewport(0, 0, width, height) - glMatrixMode(GL_PROJECTION) - glLoadIdentity() - gluPerspective(60.0, width / float(height), 0.1, 1000.0) - glMatrixMode(GL_MODELVIEW) - return pyglet.event.EVENT_HANDLED - - -batch = graphics.Batch() - -points = [] -for i in range(map_points.shape[0]): - particle = batch.add( - 1, - GL_POINTS, - None, - ("v3f/stream", [map_points[i, 0], map_points[i, 1], map_points[i, 2]]), - ) - points.append(particle) - - -def loop(dt): - print(pyglet.clock.get_fps()) - pass - - -@window.event -def on_draw(): - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) - glColor3f(254 / 255, 117 / 255, 254 / 255) - cam.begin() - batch.draw() - cam.end() - - -pyglet.clock.schedule(loop) -pyglet.app.run() diff --git a/gym/f110_gym/test/pyglet_test_camera.py b/gym/f110_gym/test/pyglet_test_camera.py deleted file mode 100644 index 6f5f973c..00000000 --- a/gym/f110_gym/test/pyglet_test_camera.py +++ /dev/null @@ -1,127 +0,0 @@ -import pyglet -from pyglet.gl import * - -# Zooming constants -ZOOM_IN_FACTOR = 1.2 -ZOOM_OUT_FACTOR = 1 / ZOOM_IN_FACTOR - - -class App(pyglet.window.Window): - def __init__(self, width, height, *args, **kwargs): - conf = Config(sample_buffers=1, samples=4, depth_size=16, double_buffer=True) - super().__init__(width, height, config=conf, *args, **kwargs) - - # Initialize camera values - self.left = 0 - self.right = width - self.bottom = 0 - self.top = height - self.zoom_level = 1 - self.zoomed_width = width - self.zoomed_height = height - - def init_gl(self, width, height): - # Set clear color - glClearColor(0 / 255, 0 / 255, 0 / 255, 0 / 255) - - # Set antialiasing - glEnable(GL_LINE_SMOOTH) - glEnable(GL_POLYGON_SMOOTH) - glHint(GL_LINE_SMOOTH_HINT, GL_NICEST) - - # Set alpha blending - glEnable(GL_BLEND) - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) - - # Set viewport - glViewport(0, 0, width, height) - - def on_resize(self, width, height): - super().on_resize(width, height) - size = self.get_size() - self.left = 0 - self.right = size[0] - self.bottom = 0 - self.top = size[1] - self.zoomed_width = size[0] - self.zoomed_height = size[1] - - # # Set window values - # self.width = width - # self.height = height - # # Initialize OpenGL context - # self.init_gl(width, height) - # self.width = width - # self.height = height - # pass - - def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers): - # Move camera - self.left -= dx * self.zoom_level - self.right -= dx * self.zoom_level - self.bottom -= dy * self.zoom_level - self.top -= dy * self.zoom_level - - def on_mouse_scroll(self, x, y, dx, dy): - # Get scale factor - f = ZOOM_IN_FACTOR if dy > 0 else ZOOM_OUT_FACTOR if dy < 0 else 1 - # If zoom_level is in the proper range - if 0.2 < self.zoom_level * f < 5: - self.zoom_level *= f - - size = self.get_size() - - mouse_x = x / size[0] - mouse_y = y / size[1] - - mouse_x_in_world = self.left + mouse_x * self.zoomed_width - mouse_y_in_world = self.bottom + mouse_y * self.zoomed_height - - self.zoomed_width *= f - self.zoomed_height *= f - - self.left = mouse_x_in_world - mouse_x * self.zoomed_width - self.right = mouse_x_in_world + (1 - mouse_x) * self.zoomed_width - self.bottom = mouse_y_in_world - mouse_y * self.zoomed_height - self.top = mouse_y_in_world + (1 - mouse_y) * self.zoomed_height - - def on_draw(self): - # Initialize Projection matrix - glMatrixMode(GL_PROJECTION) - glLoadIdentity() - - # Initialize Modelview matrix - glMatrixMode(GL_MODELVIEW) - glLoadIdentity() - # Save the default modelview matrix - glPushMatrix() - - # Clear window with ClearColor - glClear(GL_COLOR_BUFFER_BIT) - - # Set orthographic projection matrix - glOrtho(self.left, self.right, self.bottom, self.top, 1, -1) - - # Draw quad - glBegin(GL_QUADS) - glColor3ub(0xFF, 0, 0) - glVertex2i(10, 10) - - glColor3ub(0xFF, 0xFF, 0) - glVertex2i(110, 10) - - glColor3ub(0, 0xFF, 0) - glVertex2i(110, 110) - - glColor3ub(0, 0, 0xFF) - glVertex2i(10, 110) - glEnd() - - # Remove default modelview matrix - glPopMatrix() - - def run(self): - pyglet.app.run() - - -App(800, 800, resizable=True).run() diff --git a/gym/f110_gym/test/test_collision_checks.py b/gym/f110_gym/test/test_collision_checks.py deleted file mode 100644 index 14d9cb9a..00000000 --- a/gym/f110_gym/test/test_collision_checks.py +++ /dev/null @@ -1,311 +0,0 @@ -# MIT License - -# Copyright (c) 2020 Joseph Auckley, Matthew O'Kelly, Aman Sinha, Hongrui Zheng - -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. - -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - - -""" -Prototype of Utility functions and GJK algorithm for Collision checks between vehicles -Originally from https://github.com/kroitor/gjk.c -Author: Hongrui Zheng -""" - -import numpy as np -from numba import njit - - -@njit(cache=True) -def perpendicular(pt): - """ - Return a 2-vector's perpendicular vector - - Args: - pt (np.ndarray, (2,)): input vector - - Returns: - pt (np.ndarray, (2,)): perpendicular vector - """ - temp = pt[0] - pt[0] = pt[1] - pt[1] = -1 * temp - return pt - - -@njit(cache=True) -def tripleProduct(a, b, c): - """ - Return triple product of three vectors - - Args: - a, b, c (np.ndarray, (2,)): input vectors - - Returns: - (np.ndarray, (2,)): triple product - """ - ac = a.dot(c) - bc = b.dot(c) - return b * ac - a * bc - - -@njit(cache=True) -def avgPoint(vertices): - """ - Return the average point of multiple vertices - - Args: - vertices (np.ndarray, (n, 2)): the vertices we want to find avg on - - Returns: - avg (np.ndarray, (2,)): average point of the vertices - """ - return np.sum(vertices, axis=0) / vertices.shape[0] - - -@njit(cache=True) -def indexOfFurthestPoint(vertices, d): - """ - Return the index of the vertex furthest away along a direction in the list of vertices - - Args: - vertices (np.ndarray, (n, 2)): the vertices we want to find avg on - - Returns: - idx (int): index of the furthest point - """ - return np.argmax(vertices.dot(d)) - - -@njit(cache=True) -def support(vertices1, vertices2, d): - """ - Minkowski sum support function for GJK - - Args: - vertices1 (np.ndarray, (n, 2)): vertices of the first body - vertices2 (np.ndarray, (n, 2)): vertices of the second body - d (np.ndarray, (2, )): direction to find the support along - - Returns: - support (np.ndarray, (n, 2)): Minkowski sum - """ - i = indexOfFurthestPoint(vertices1, d) - j = indexOfFurthestPoint(vertices2, -d) - return vertices1[i] - vertices2[j] - - -@njit(cache=True) -def collision(vertices1, vertices2): - """ - GJK test to see whether two bodies overlap - - Args: - vertices1 (np.ndarray, (n, 2)): vertices of the first body - vertices2 (np.ndarray, (n, 2)): vertices of the second body - - Returns: - overlap (boolean): True if two bodies collide - """ - index = 0 - simplex = np.empty((3, 2)) - - position1 = avgPoint(vertices1) - position2 = avgPoint(vertices2) - - d = position1 - position2 - - if d[0] == 0 and d[1] == 0: - d[0] = 1.0 - - a = support(vertices1, vertices2, d) - simplex[index, :] = a - - if d.dot(a) <= 0: - return False - - d = -a - - iter_count = 0 - while iter_count < 1e3: - a = support(vertices1, vertices2, d) - index += 1 - simplex[index, :] = a - if d.dot(a) <= 0: - return False - - ao = -a - - if index < 2: - b = simplex[0, :] - ab = b - a - d = tripleProduct(ab, ao, ab) - if np.linalg.norm(d) < 1e-10: - d = perpendicular(ab) - continue - - b = simplex[1, :] - c = simplex[0, :] - ab = b - a - ac = c - a - - acperp = tripleProduct(ab, ac, ac) - - if acperp.dot(ao) >= 0: - d = acperp - else: - abperp = tripleProduct(ac, ab, ab) - if abperp.dot(ao) < 0: - return True - simplex[0, :] = simplex[1, :] - d = abperp - - simplex[1, :] = simplex[2, :] - index -= 1 - - iter_count += 1 - return False - - -""" -Utility functions for getting vertices by pose and shape -""" - - -@njit(cache=True) -def get_trmtx(pose): - """ - Get transformation matrix of vehicle frame -> global frame - - Args: - pose (np.ndarray (3, )): current pose of the vehicle - - return: - H (np.ndarray (4, 4)): transformation matrix - """ - x = pose[0] - y = pose[1] - th = pose[2] - cos = np.cos(th) - sin = np.sin(th) - H = np.array( - [ - [cos, -sin, 0.0, x], - [sin, cos, 0.0, y], - [0.0, 0.0, 1.0, 0.0], - [0.0, 0.0, 0.0, 1.0], - ] - ) - return H - - -@njit(cache=True) -def get_vertices(pose, length, width): - """ - Utility function to return vertices of the car body given pose and size - - Args: - pose (np.ndarray, (3, )): current world coordinate pose of the vehicle - length (float): car length - width (float): car width - - Returns: - vertices (np.ndarray, (4, 2)): corner vertices of the vehicle body - """ - H = get_trmtx(pose) - rl = H.dot(np.asarray([[-length / 2], [width / 2], [0.0], [1.0]])).flatten() - rr = H.dot(np.asarray([[-length / 2], [-width / 2], [0.0], [1.0]])).flatten() - fl = H.dot(np.asarray([[length / 2], [width / 2], [0.0], [1.0]])).flatten() - fr = H.dot(np.asarray([[length / 2], [-width / 2], [0.0], [1.0]])).flatten() - rl = rl / rl[3] - rr = rr / rr[3] - fl = fl / fl[3] - fr = fr / fr[3] - vertices = np.asarray( - [[rl[0], rl[1]], [rr[0], rr[1]], [fr[0], fr[1]], [fl[0], fl[1]]] - ) - return vertices - - -""" -Unit test for GJK collision checks -Author: Hongrui Zheng -""" - -import time -import unittest - - -class CollisionTests(unittest.TestCase): - def setUp(self): - # test params - np.random.seed(1234) - - # Collision check body - self.vertices1 = np.asarray([[4, 11.0], [5, 5], [9, 9], [10, 10]]) - - # car size - self.length = 0.32 - self.width = 0.22 - - def test_get_vert(self): - test_pose = np.array([2.3, 6.7, 0.8]) - vertices = get_vertices(test_pose, self.length, self.width) - rect = np.vstack((vertices, vertices[0, :])) - import matplotlib.pyplot as plt - - plt.scatter(test_pose[0], test_pose[1], c="red") - plt.plot(rect[:, 0], rect[:, 1]) - plt.xlim([1, 4]) - plt.ylim([5, 8]) - plt.axes().set_aspect("equal") - plt.show() - self.assertTrue(vertices.shape == (4, 2)) - - def test_get_vert_fps(self): - test_pose = np.array([2.3, 6.7, 0.8]) - start = time.time() - for _ in range(1000): - vertices = get_vertices(test_pose, self.length, self.width) - elapsed = time.time() - start - fps = 1000 / elapsed - print("get vertices fps:", fps) - self.assertTrue(fps > 500) - - def test_random_collision(self): - # perturb the body by a small amount and make sure it all collides with the original body - for _ in range(1000): - a = self.vertices1 + np.random.normal(size=(self.vertices1.shape)) / 100.0 - b = self.vertices1 + np.random.normal(size=(self.vertices1.shape)) / 100.0 - self.assertTrue(collision(a, b)) - - def test_fps(self): - # also perturb the body but mainly want to test GJK speed - start = time.time() - for _ in range(1000): - a = self.vertices1 + np.random.normal(size=(self.vertices1.shape)) / 100.0 - b = self.vertices1 + np.random.normal(size=(self.vertices1.shape)) / 100.0 - collision(a, b) - elapsed = time.time() - start - fps = 1000 / elapsed - print("gjk fps:", fps) - self.assertTrue(fps > 500) - - -if __name__ == "__main__": - unittest.main() diff --git a/gym/f110_gym/test/test_scan_sim.py b/gym/f110_gym/test/test_scan_sim.py deleted file mode 100644 index 41d8b49c..00000000 --- a/gym/f110_gym/test/test_scan_sim.py +++ /dev/null @@ -1,392 +0,0 @@ -# MIT License - -# Copyright (c) 2020 Joseph Auckley, Matthew O'Kelly, Aman Sinha, Hongrui Zheng - -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. - -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - - -""" -Prototype of Utility functions and classes for simulating 2D LIDAR scans -Author: Hongrui Zheng -""" - -import numpy as np -from numba import njit -from scipy.ndimage import distance_transform_edt as edt -from PIL import Image -import os -import yaml - -import unittest -import timeit - -from f110_gym.envs import ScanSimulator2D - - -def get_dt(bitmap, resolution): - """ - Distance transformation, returns the distance matrix from the input bitmap. - Uses scipy.ndimage, cannot be JITted. - - Args: - bitmap (numpy.ndarray, (n, m)): input binary bitmap of the environment, where 0 is obstacles, and 255 (or anything > 0) is freespace - resolution (float): resolution of the input bitmap (m/cell) - - Returns: - dt (numpy.ndarray, (n, m)): output distance matrix, where each cell has the corresponding distance (in meters) to the closest obstacle - """ - dt = resolution * edt(bitmap) - return dt - - -@njit(cache=True) -def xy_2_rc(x, y, orig_x, orig_y, orig_c, orig_s, height, width, resolution): - """ - Translate (x, y) coordinate into (r, c) in the matrix - - Args: - x (float): coordinate in x (m) - y (float): coordinate in y (m) - orig_x (float): x coordinate of the map origin (m) - orig_y (float): y coordinate of the map origin (m) - - Returns: - r (int): row number in the transform matrix of the given point - c (int): column number in the transform matrix of the given point - """ - # translation - x_trans = x - orig_x - y_trans = y - orig_y - - # rotation - x_rot = x_trans * orig_c + y_trans * orig_s - y_rot = -x_trans * orig_s + y_trans * orig_c - - # clip the state to be a cell - if ( - x_rot < 0 - or x_rot >= width * resolution - or y_rot < 0 - or y_rot >= height * resolution - ): - c = -1 - r = -1 - else: - c = int(x_rot / resolution) - r = int(y_rot / resolution) - - return r, c - - -@njit(cache=True) -def distance_transform( - x, y, orig_x, orig_y, orig_c, orig_s, height, width, resolution, dt -): - """ - Look up corresponding distance in the distance matrix - - Args: - x (float): x coordinate of the lookup point - y (float): y coordinate of the lookup point - orig_x (float): x coordinate of the map origin (m) - orig_y (float): y coordinate of the map origin (m) - - Returns: - distance (float): corresponding shortest distance to obstacle in meters - """ - r, c = xy_2_rc(x, y, orig_x, orig_y, orig_c, orig_s, height, width, resolution) - distance = dt[r, c] - return distance - - -@njit(cache=True) -def trace_ray( - x, - y, - theta_index, - sines, - cosines, - eps, - orig_x, - orig_y, - orig_c, - orig_s, - height, - width, - resolution, - dt, - max_range, -): - """ - Find the length of a specific ray at a specific scan angle theta - Purely math calculation and loops, should be JITted. - - Args: - x (float): current x coordinate of the ego (scan) frame - y (float): current y coordinate of the ego (scan) frame - theta_index(int): current index of the scan beam in the scan range - sines (numpy.ndarray (n, )): pre-calculated sines of the angle array - cosines (numpy.ndarray (n, )): pre-calculated cosines ... - - Returns: - total_distance (float): the distance to first obstacle on the current scan beam - """ - - # int casting, and index precal trigs - theta_index_ = int(theta_index) - s = sines[theta_index_] - c = cosines[theta_index_] - - # distance to nearest initialization - dist_to_nearest = distance_transform( - x, y, orig_x, orig_y, orig_c, orig_s, height, width, resolution, dt - ) - total_dist = dist_to_nearest - - # ray tracing iterations - while dist_to_nearest > eps and total_dist <= max_range: - # move in the direction of the ray by dist_to_nearest - x += dist_to_nearest * c - y += dist_to_nearest * s - - # update dist_to_nearest for current point on ray - # also keeps track of total ray length - dist_to_nearest = distance_transform( - x, y, orig_x, orig_y, orig_c, orig_s, height, width, resolution, dt - ) - total_dist += dist_to_nearest - - return total_dist - - -@njit(cache=True) -def get_scan( - pose, - theta_dis, - fov, - num_beams, - theta_index_increment, - sines, - cosines, - eps, - orig_x, - orig_y, - orig_c, - orig_s, - height, - width, - resolution, - dt, - max_range, -): - """ - Perform the scan for each discretized angle of each beam of the laser, loop heavy, should be JITted - - Args: - pose (numpy.ndarray(3, )): current pose of the scan frame in the map - theta_dis (int): number of steps to discretize the angles between 0 and 2pi for look up - fov (float): field of view of the laser scan - num_beams (int): number of beams in the scan - theta_index_increment (float): increment between angle indices after discretization - - Returns: - scan (numpy.ndarray(n, )): resulting laser scan at the pose, n=num_beams - """ - # empty scan array init - scan = np.empty((num_beams,)) - - # make theta discrete by mapping the range [-pi, pi] onto [0, theta_dis] - theta_index = theta_dis * (pose[2] - fov / 2.0) / (2.0 * np.pi) - - # make sure it's wrapped properly - theta_index = np.fmod(theta_index, theta_dis) - while theta_index < 0: - theta_index += theta_dis - - # sweep through each beam - for i in range(0, num_beams): - # trace the current beam - scan[i] = trace_ray( - pose[0], - pose[1], - theta_index, - sines, - cosines, - eps, - orig_x, - orig_y, - orig_c, - orig_s, - height, - width, - resolution, - dt, - max_range, - ) - - # increment the beam index - theta_index += theta_index_increment - - # make sure it stays in the range [0, theta_dis) - while theta_index >= theta_dis: - theta_index -= theta_dis - - return scan - - -""" -Unit test for the 2D scan simulator class -Author: Hongrui Zheng - -Test cases: - 1, 2: Comparison between generated scan array of the new simulator and the legacy C++ simulator, - generated data used, MSE is used as the metric - 2. FPS test, should be greater than 500 -""" - - -class ScanTests(unittest.TestCase): - def setUp(self): - # test params - self.num_beams = 1080 - self.fov = 4.7 - - self.num_test = 10 - self.test_poses = np.zeros((self.num_test, 3)) - self.test_poses[:, 2] = np.linspace(-1.0, 1.0, num=self.num_test) - - # legacy gym data - wdir = os.path.dirname(os.path.abspath(__file__)) - sample_scan = np.load(f"{wdir}/legacy_scan.npz") - self.berlin_scan = sample_scan["berlin"] - self.skirk_scan = sample_scan["skirk"] - - def test_map_berlin(self): - scan_rng = np.random.default_rng(seed=12345) - scan_sim = ScanSimulator2D(self.num_beams, self.fov) - new_berlin = np.empty((self.num_test, self.num_beams)) - scan_sim.set_map(map_name="Berlin") - # scan gen loop - for i in range(self.num_test): - test_pose = self.test_poses[i] - new_berlin[i, :] = scan_sim.scan(pose=test_pose, rng=scan_rng) - diff = self.berlin_scan - new_berlin - mse = np.mean(diff**2) - # print('Levine distance test, norm: ' + str(norm)) - - # plotting - import matplotlib.pyplot as plt - - theta = np.linspace(-self.fov / 2.0, self.fov / 2.0, num=self.num_beams) - plt.polar(theta, new_berlin[1, :], ".", lw=0) - plt.polar(theta, self.berlin_scan[1, :], ".", lw=0) - plt.show() - - self.assertLess(mse, 2.0) - - def test_map_skirk(self): - scan_rng = np.random.default_rng(seed=12345) - scan_sim = ScanSimulator2D(self.num_beams, self.fov) - new_skirk = np.empty((self.num_test, self.num_beams)) - scan_sim.set_map(map_name="Skirk") - print("map set") - # scan gen loop - for i in range(self.num_test): - test_pose = self.test_poses[i] - new_skirk[i, :] = scan_sim.scan(pose=test_pose, rng=scan_rng) - diff = self.skirk_scan - new_skirk - mse = np.mean(diff**2) - print("skirk distance test, mse: " + str(mse)) - - # plotting - import matplotlib.pyplot as plt - - theta = np.linspace(-self.fov / 2.0, self.fov / 2.0, num=self.num_beams) - plt.polar(theta, new_skirk[1, :], ".", lw=0) - plt.polar(theta, self.skirk_scan[1, :], ".", lw=0) - plt.show() - - self.assertLess(mse, 2.0) - - def test_fps(self): - # scan fps should be greater than 500 - scan_rng = np.random.default_rng(seed=12345) - scan_sim = ScanSimulator2D(self.num_beams, self.fov) - scan_sim.set_map(map_name="Skirk") - - import time - - start = time.time() - for i in range(10000): - x_test = i / 10000 - scan = scan_sim.scan(pose=np.array([x_test, 0.0, 0.0]), rng=scan_rng) - end = time.time() - fps = 10000 / (end - start) - - self.assertGreater(fps, 500.0) - - -def main(): - num_beams = 1080 - fov = 4.7 - # map_path = '../envs/maps/Berlin_map.yaml' - scan_rng = np.random.default_rng(seed=12345) - scan_sim = ScanSimulator2D(num_beams, fov) - scan_sim.set_map(map_name="Example") - scan = scan_sim.scan(np.array([0.0, 0.0, 0.0])) - - # fps test - import time - - start = time.time() - for i in range(10000): - x_test = i / 10000 - scan = scan_sim.scan(np.array([x_test, 0.0, 0.0]), rng=scan_rng) - end = time.time() - fps = (end - start) / 10000 - print("FPS test") - print("Elapsed time: " + str(end - start) + " , FPS: " + str(1 / fps)) - - # visualization - import matplotlib.pyplot as plt - from matplotlib.animation import FuncAnimation - - num_iter = 100 - theta = np.linspace(-fov / 2.0, fov / 2.0, num=num_beams) - fig = plt.figure() - ax = fig.add_subplot(111, projection="polar") - ax.set_ylim(0, 70) - (line,) = ax.plot([], [], ".", lw=0) - - def update(i): - # x_ani = i * 3. / num_iter - theta_ani = -i * 2 * np.pi / num_iter - x_ani = 0.0 - current_scan = scan_sim.scan(np.array([x_ani, 0.0, theta_ani])) - print(np.max(current_scan)) - line.set_data(theta, current_scan) - return (line,) - - ani = FuncAnimation(fig, update, frames=num_iter, blit=True) - plt.show() - - -if __name__ == "__main__": - # test.main() - main() diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..a59f8a19 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,15 @@ +[tool:pytest] +minversion = 6.0 +addopts = -ra -q +testpaths = + tests + integration + +[flake8] +extend-ignore = E203, E501, +max-line-length = 120 +exclude = + .git, + __pycache__, + build, + dist \ No newline at end of file diff --git a/setup.py b/setup.py index 85b33204..8d7de462 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ package_dir={"": "gym"}, install_requires=[ "gymnasium", - "numpy<=1.22.0,>=1.18.0", + "numpy<=1.25.0,>=1.18.0", "Pillow>=9.0.1", "scipy>=1.7.3", "numba>=0.55.2", @@ -18,5 +18,18 @@ "pyopengl", "yamldataclassconfig", "requests", + "shapely", + "opencv-python", ], + extras_require={ + 'dev': [ + 'pytest', + 'flake8', + 'black', + 'ipykernel', + 'isort', + 'autoflake', + 'matplotlib' + ] + } ) diff --git a/gym/f110_gym/test/__init__.py b/tests/__init__.py similarity index 100% rename from gym/f110_gym/test/__init__.py rename to tests/__init__.py diff --git a/gym/f110_gym/test/legacy_scan.npz b/tests/legacy_scan.npz similarity index 100% rename from gym/f110_gym/test/legacy_scan.npz rename to tests/legacy_scan.npz diff --git a/gym/f110_gym/test/legacy_scan_gen.py b/tests/legacy_scan_gen.py similarity index 99% rename from gym/f110_gym/test/legacy_scan_gen.py rename to tests/legacy_scan_gen.py index 96beed5a..e69c8770 100644 --- a/gym/f110_gym/test/legacy_scan_gen.py +++ b/tests/legacy_scan_gen.py @@ -35,8 +35,8 @@ """ import numpy as np + import gym -import matplotlib.pyplot as plt thetas = np.linspace(-2.35, 2.35, num=1080) diff --git a/tests/test_collision_checks.py b/tests/test_collision_checks.py new file mode 100644 index 00000000..88e4b4fc --- /dev/null +++ b/tests/test_collision_checks.py @@ -0,0 +1,85 @@ +# MIT License +import time +import unittest + +import numpy as np +from f110_gym.envs.collision_models import get_vertices, collision + + +# Copyright (c) 2020 Joseph Auckley, Matthew O'Kelly, Aman Sinha, Hongrui Zheng + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +class CollisionTests(unittest.TestCase): + def setUp(self): + # test params + np.random.seed(1234) + + # Collision check body + self.vertices1 = np.asarray([[4, 11.0], [5, 5], [9, 9], [10, 10]]) + + # car size + self.length = 0.32 + self.width = 0.22 + + def test_get_vert(self, debug=False): + test_pose = np.array([2.3, 6.7, 0.8]) + vertices = get_vertices(test_pose, self.length, self.width) + rect = np.vstack((vertices, vertices[0, :])) + if debug: + import matplotlib.pyplot as plt + + plt.scatter(test_pose[0], test_pose[1], c="red") + plt.plot(rect[:, 0], rect[:, 1]) + plt.xlim([1, 4]) + plt.ylim([5, 8]) + plt.axes().set_aspect("equal") + plt.show() + self.assertTrue(vertices.shape == (4, 2)) + + def test_get_vert_fps(self): + test_pose = np.array([2.3, 6.7, 0.8]) + start = time.time() + for _ in range(1000): + get_vertices(test_pose, self.length, self.width) + elapsed = time.time() - start + fps = 1000 / elapsed + print("get vertices fps:", fps) + self.assertGreater(fps, 500) + + def test_random_collision(self): + # perturb the body by a small amount and make sure it all collides with the original body + for _ in range(1000): + a = self.vertices1 + np.random.normal(size=(self.vertices1.shape)) / 100.0 + b = self.vertices1 + np.random.normal(size=(self.vertices1.shape)) / 100.0 + self.assertTrue(collision(a, b)) + + def test_fps(self): + # also perturb the body but mainly want to test GJK speed + start = time.time() + for _ in range(1000): + a = self.vertices1 + np.random.normal(size=(self.vertices1.shape)) / 100.0 + b = self.vertices1 + np.random.normal(size=(self.vertices1.shape)) / 100.0 + collision(a, b) + elapsed = time.time() - start + fps = 1000 / elapsed + print("gjk fps:", fps) + # self.assertGreater(fps, 500) This is a platform dependent test, not ideal. + diff --git a/gym/f110_gym/test/test_dynamics.py b/tests/test_dynamics.py similarity index 52% rename from gym/f110_gym/test/test_dynamics.py rename to tests/test_dynamics.py index 7bce8f43..03ddfc1e 100644 --- a/gym/f110_gym/test/test_dynamics.py +++ b/tests/test_dynamics.py @@ -12,108 +12,18 @@ """ -Prototype of vehicle dynamics functions and classes for simulating 2D Single +Prototype of vehicle dynamics functions and classes for simulating 2D Single Track dynamic model Following the implementation of commanroad's Single Track Dynamics model Original implementation: https://gitlab.lrz.de/tum-cps/commonroad-vehicle-models/ Author: Hongrui Zheng """ -import numpy as np -from numba import njit - -import unittest import time +import unittest -from f110_gym.envs import vehicle_dynamics_ks, vehicle_dynamics_st - - -def func_KS( - x, - t, - u, - mu, - C_Sf, - C_Sr, - lf, - lr, - h, - m, - I, - s_min, - s_max, - sv_min, - sv_max, - v_switch, - a_max, - v_min, - v_max, -): - f = vehicle_dynamics_ks( - x, - u, - mu, - C_Sf, - C_Sr, - lf, - lr, - h, - m, - I, - s_min, - s_max, - sv_min, - sv_max, - v_switch, - a_max, - v_min, - v_max, - ) - return f - - -def func_ST( - x, - t, - u, - mu, - C_Sf, - C_Sr, - lf, - lr, - h, - m, - I, - s_min, - s_max, - sv_min, - sv_max, - v_switch, - a_max, - v_min, - v_max, -): - f = vehicle_dynamics_st( - x, - u, - mu, - C_Sf, - C_Sr, - lf, - lr, - h, - m, - I, - s_min, - s_max, - sv_min, - sv_max, - v_switch, - a_max, - v_min, - v_max, - ) - return f +import numpy as np +from f110_gym.envs.dynamic_models import vehicle_dynamics_ks, vehicle_dynamics_st, func_KS, func_ST class DynamicsTest(unittest.TestCase): @@ -126,7 +36,7 @@ def setUp(self): self.lr = 0.3048 * 4.667707 self.h = 0.3048 * 2.01355 self.m = 4.4482216152605 / 0.3048 * 74.91452 - self.I = 4.4482216152605 * 0.3048 * 1321.416 + self.I = 4.4482216152605 * 0.3048 * 1321.416 # noqa: E741 # steering constraints self.s_min = -1.066 # minimum steering angle [rad] @@ -259,7 +169,7 @@ def test_zeroinit_roll(self): from scipy.integrate import odeint # testing for zero initial state, zero input singularities - g = 9.81 + # g = 9.81 t_start = 0.0 t_final = 1.0 delta0 = 0.0 @@ -528,11 +438,11 @@ def test_zeroinit_acc(self): self.assertTrue(all(abs(x_acc_st[-1] - x_acc_st_gt) < 1e-2)) self.assertTrue(all(abs(x_acc_ks[-1] - x_acc_ks_gt) < 1e-2)) - def test_zeroinit_rollleft(self): + def test_zeroinit_rollleft_kinematic(self): from scipy.integrate import odeint # testing for zero initial state, rolling and steering left input singularities - g = 9.81 + # g = 9.81 t_start = 0.0 t_final = 1.0 delta0 = 0.0 @@ -544,7 +454,6 @@ def test_zeroinit_rollleft(self): initial_state = [0, sy0, delta0, vel0, Psi0, dotPsi0, beta0] x0_KS = np.array(initial_state[0:5]) - x0_ST = np.array(initial_state) # time vector t = np.arange(t_start, t_final, 1e-4) @@ -552,31 +461,6 @@ def test_zeroinit_rollleft(self): # set decel input u = np.array([0.15, 0.0]) - # simulate single-track model - x_left_st = odeint( - func_ST, - x0_ST, - t, - args=( - u, - self.mu, - self.C_Sf, - self.C_Sr, - self.lf, - self.lr, - self.h, - self.m, - self.I, - self.s_min, - self.s_max, - self.sv_min, - self.sv_max, - self.v_switch, - self.a_max, - self.v_min, - self.v_max, - ), - ) # simulate kinematic single-track model x_left_ks = odeint( func_KS, @@ -602,17 +486,6 @@ def test_zeroinit_rollleft(self): self.v_max, ), ) - - # ground truth for single-track model - x_left_st_gt = [ - 0.0000000000000000, - 0.0000000000000000, - 0.1500000000000000, - 0.0000000000000000, - 0.0000000000000000, - 0.0000000000000000, - 0.0000000000000000, - ] # ground truth for kinematic single-track model x_left_ks_gt = [ 0.0000000000000000, @@ -621,428 +494,13 @@ def test_zeroinit_rollleft(self): 0.0000000000000000, 0.0000000000000000, ] + np.testing.assert_array_almost_equal(x_left_ks[-1], x_left_ks_gt, decimal=2) - self.assertTrue(all(abs(x_left_st[-1] - x_left_st_gt) < 1e-2)) - self.assertTrue(all(abs(x_left_ks[-1] - x_left_ks_gt) < 1e-2)) - - -class DynamicsTest2(unittest.TestCase): - def setUp(self): - # test params - self.mu = 1.0489 - self.C_Sf = 21.92 / 1.0489 - self.C_Sr = 21.92 / 1.0489 - self.lf = 0.3048 * 3.793293 - self.lr = 0.3048 * 4.667707 - self.h = 0.3048 * 2.01355 - self.m = 4.4482216152605 / 0.3048 * 74.91452 - self.I = 4.4482216152605 * 0.3048 * 1321.416 - - # steering constraints - self.s_min = -1.066 # minimum steering angle [rad] - self.s_max = 1.066 # maximum steering angle [rad] - self.sv_min = -0.4 # minimum steering velocity [rad/s] - self.sv_max = 0.4 # maximum steering velocity [rad/s] - - # longitudinal constraints - self.v_min = -13.6 # minimum velocity [m/s] - self.v_max = 50.8 # minimum velocity [m/s] - self.v_switch = 7.319 # switching velocity [m/s] - self.a_max = 11.5 # maximum absolute acceleration [m/s^2] - - def test_derivatives(self): - # ground truth derivatives - f_ks_gt = [ - 16.3475935934250209, - 0.4819314886013121, - 0.1500000000000000, - 5.1464424102339752, - 0.2401426578627629, - ] - f_st_gt = [ - 15.7213512030862397, - 0.0925527979719355, - 0.1500000000000000, - 5.3536773276413925, - 0.0529001056654038, - 0.6435589397748606, - 0.0313297971641291, - ] - - # system dynamics - g = 9.81 - x_ks = np.array( - [ - 3.9579422297936526, - 0.0391650102771405, - 0.0378491427211811, - 16.3546957860883566, - 0.0294717351052816, - ] - ) - x_st = np.array( - [ - 2.0233348142065677, - 0.0041907137716636, - 0.0197545248559617, - 15.7216236334290116, - 0.0025857914776859, - 0.0529001056654038, - 0.0033012170610298, - ] - ) - v_delta = 0.15 - acc = 0.63 * g - u = np.array([v_delta, acc]) - - f_ks = vehicle_dynamics_ks( - x_ks, - u, - self.mu, - self.C_Sf, - self.C_Sr, - self.lf, - self.lr, - self.h, - self.m, - self.I, - self.s_min, - self.s_max, - self.sv_min, - self.sv_max, - self.v_switch, - self.a_max, - self.v_min, - self.v_max, - ) - f_st = vehicle_dynamics_st( - x_st, - u, - self.mu, - self.C_Sf, - self.C_Sr, - self.lf, - self.lr, - self.h, - self.m, - self.I, - self.s_min, - self.s_max, - self.sv_min, - self.sv_max, - self.v_switch, - self.a_max, - self.v_min, - self.v_max, - ) - - start = time.time() - for i in range(10000): - f_st = vehicle_dynamics_st( - x_st, - u, - self.mu, - self.C_Sf, - self.C_Sr, - self.lf, - self.lr, - self.h, - self.m, - self.I, - self.s_min, - self.s_max, - self.sv_min, - self.sv_max, - self.v_switch, - self.a_max, - self.v_min, - self.v_max, - ) - duration = time.time() - start - avg_fps = 10000 / duration - - self.assertAlmostEqual(np.max(np.abs(f_ks_gt - f_ks)), 0.0) - self.assertAlmostEqual(np.max(np.abs(f_st_gt - f_st)), 0.0) - self.assertGreater(avg_fps, 5000) - - def test_zeroinit_roll(self): - from scipy.integrate import odeint - - # testing for zero initial state, zero input singularities - g = 9.81 - t_start = 0.0 - t_final = 1.0 - delta0 = 0.0 - vel0 = 0.0 - Psi0 = 0.0 - dotPsi0 = 0.0 - beta0 = 0.0 - sy0 = 0.0 - initial_state = [0, sy0, delta0, vel0, Psi0, dotPsi0, beta0] - - x0_KS = np.array(initial_state[0:5]) - x0_ST = np.array(initial_state) - - # time vector - t = np.arange(t_start, t_final, 1e-4) - - # set input: rolling car (velocity should stay constant) - u = np.array([0.0, 0.0]) - - # simulate single-track model - x_roll_st = odeint( - func_ST, - x0_ST, - t, - args=( - u, - self.mu, - self.C_Sf, - self.C_Sr, - self.lf, - self.lr, - self.h, - self.m, - self.I, - self.s_min, - self.s_max, - self.sv_min, - self.sv_max, - self.v_switch, - self.a_max, - self.v_min, - self.v_max, - ), - ) - # simulate kinematic single-track model - x_roll_ks = odeint( - func_KS, - x0_KS, - t, - args=( - u, - self.mu, - self.C_Sf, - self.C_Sr, - self.lf, - self.lr, - self.h, - self.m, - self.I, - self.s_min, - self.s_max, - self.sv_min, - self.sv_max, - self.v_switch, - self.a_max, - self.v_min, - self.v_max, - ), - ) - - self.assertTrue(all(x_roll_st[-1] == x0_ST)) - self.assertTrue(all(x_roll_ks[-1] == x0_KS)) - - def test_zeroinit_dec(self): - from scipy.integrate import odeint - - # testing for zero initial state, decelerating input singularities - g = 9.81 - t_start = 0.0 - t_final = 1.0 - delta0 = 0.0 - vel0 = 0.0 - Psi0 = 0.0 - dotPsi0 = 0.0 - beta0 = 0.0 - sy0 = 0.0 - initial_state = [0, sy0, delta0, vel0, Psi0, dotPsi0, beta0] - - x0_KS = np.array(initial_state[0:5]) - x0_ST = np.array(initial_state) - - # time vector - t = np.arange(t_start, t_final, 1e-4) - - # set decel input - u = np.array([0.0, -0.7 * g]) - - # simulate single-track model - x_dec_st = odeint( - func_ST, - x0_ST, - t, - args=( - u, - self.mu, - self.C_Sf, - self.C_Sr, - self.lf, - self.lr, - self.h, - self.m, - self.I, - self.s_min, - self.s_max, - self.sv_min, - self.sv_max, - self.v_switch, - self.a_max, - self.v_min, - self.v_max, - ), - ) - # simulate kinematic single-track model - x_dec_ks = odeint( - func_KS, - x0_KS, - t, - args=( - u, - self.mu, - self.C_Sf, - self.C_Sr, - self.lf, - self.lr, - self.h, - self.m, - self.I, - self.s_min, - self.s_max, - self.sv_min, - self.sv_max, - self.v_switch, - self.a_max, - self.v_min, - self.v_max, - ), - ) - - # ground truth for single-track model - x_dec_st_gt = [ - -3.4335000000000013, - 0.0000000000000000, - 0.0000000000000000, - -6.8670000000000018, - 0.0000000000000000, - 0.0000000000000000, - 0.0000000000000000, - ] - # ground truth for kinematic single-track model - x_dec_ks_gt = [ - -3.4335000000000013, - 0.0000000000000000, - 0.0000000000000000, - -6.8670000000000018, - 0.0000000000000000, - ] - - self.assertTrue(all(abs(x_dec_st[-1] - x_dec_st_gt) < 1e-2)) - self.assertTrue(all(abs(x_dec_ks[-1] - x_dec_ks_gt) < 1e-2)) - - def test_zeroinit_acc(self): - from scipy.integrate import odeint - - # testing for zero initial state, accelerating with left steer input singularities - # wheel spin and velocity should increase more wheel spin at rear - g = 9.81 - t_start = 0.0 - t_final = 1.0 - delta0 = 0.0 - vel0 = 0.0 - Psi0 = 0.0 - dotPsi0 = 0.0 - beta0 = 0.0 - sy0 = 0.0 - initial_state = [0, sy0, delta0, vel0, Psi0, dotPsi0, beta0] - - x0_KS = np.array(initial_state[0:5]) - x0_ST = np.array(initial_state) - - # time vector - t = np.arange(t_start, t_final, 1e-4) - - # set decel input - u = np.array([0.15, 0.63 * g]) - - # simulate single-track model - x_acc_st = odeint( - func_ST, - x0_ST, - t, - args=( - u, - self.mu, - self.C_Sf, - self.C_Sr, - self.lf, - self.lr, - self.h, - self.m, - self.I, - self.s_min, - self.s_max, - self.sv_min, - self.sv_max, - self.v_switch, - self.a_max, - self.v_min, - self.v_max, - ), - ) - # simulate kinematic single-track model - x_acc_ks = odeint( - func_KS, - x0_KS, - t, - args=( - u, - self.mu, - self.C_Sf, - self.C_Sr, - self.lf, - self.lr, - self.h, - self.m, - self.I, - self.s_min, - self.s_max, - self.sv_min, - self.sv_max, - self.v_switch, - self.a_max, - self.v_min, - self.v_max, - ), - ) - - # ground truth for single-track model - x_acc_st_gt = [ - 3.0731976046859715, - 0.2869835398304389, - 0.1500000000000000, - 6.1802999999999999, - 0.1097747074946325, - 0.3248268063223301, - 0.0697547542798040, - ] - # ground truth for kinematic single-track model - x_acc_ks_gt = [ - 3.0845676868494927, - 0.1484249221523042, - 0.1500000000000000, - 6.1803000000000017, - 0.1203664469224163, - ] - - self.assertTrue(all(abs(x_acc_st[-1] - x_acc_st_gt) < 1e-2)) - self.assertTrue(all(abs(x_acc_ks[-1] - x_acc_ks_gt) < 1e-2)) - - def test_zeroinit_rollleft(self): + def test_zeroinit_rollleft_singletrack(self): from scipy.integrate import odeint # testing for zero initial state, rolling and steering left input singularities - g = 9.81 + # g = 9.81 t_start = 0.0 t_final = 1.0 delta0 = 0.0 @@ -1053,7 +511,6 @@ def test_zeroinit_rollleft(self): sy0 = 0.0 initial_state = [0, sy0, delta0, vel0, Psi0, dotPsi0, beta0] - x0_KS = np.array(initial_state[0:5]) x0_ST = np.array(initial_state) # time vector @@ -1087,31 +544,6 @@ def test_zeroinit_rollleft(self): self.v_max, ), ) - # simulate kinematic single-track model - x_left_ks = odeint( - func_KS, - x0_KS, - t, - args=( - u, - self.mu, - self.C_Sf, - self.C_Sr, - self.lf, - self.lr, - self.h, - self.m, - self.I, - self.s_min, - self.s_max, - self.sv_min, - self.sv_max, - self.v_switch, - self.a_max, - self.v_min, - self.v_max, - ), - ) # ground truth for single-track model x_left_st_gt = [ @@ -1123,17 +555,8 @@ def test_zeroinit_rollleft(self): 0.0000000000000000, 0.0000000000000000, ] - # ground truth for kinematic single-track model - x_left_ks_gt = [ - 0.0000000000000000, - 0.0000000000000000, - 0.1500000000000000, - 0.0000000000000000, - 0.0000000000000000, - ] - self.assertTrue(all(abs(x_left_st[-1] - x_left_st_gt) < 1e-2)) - self.assertTrue(all(abs(x_left_ks[-1] - x_left_ks_gt) < 1e-2)) + np.testing.assert_array_almost_equal(x_left_st[-1], x_left_st_gt, decimal=2) if __name__ == "__main__": diff --git a/gym/f110_gym/test/test_f110_env.py b/tests/test_f110_env.py similarity index 97% rename from gym/f110_gym/test/test_f110_env.py rename to tests/test_f110_env.py index e47f97d8..c4ba7fa7 100644 --- a/gym/f110_gym/test/test_f110_env.py +++ b/tests/test_f110_env.py @@ -1,17 +1,12 @@ -import pathlib import unittest -from argparse import Namespace -import numpy as np -import yaml import gymnasium as gym - +import numpy as np from f110_gym.envs.utils import deep_update class TestEnvInterface(unittest.TestCase): def _make_env(self, config={}): - import f110_gym conf = { "map": "Example", @@ -33,14 +28,13 @@ def test_gymnasium_api(self): from gymnasium.utils.env_checker import check_env env = self._make_env() - check_env(env.unwrapped) + check_env(env.unwrapped, skip_render_check=True) def test_configure_method(self): """ Test that the configure method works as expected, and that the parameters are correctly updated in the simulator and agents. """ - import f110_gym # create a base environment and use configure() to change the width config_ext = {"params": {"width": 15.0}} diff --git a/gym/f110_gym/test/test_observation.py b/tests/test_observation.py similarity index 96% rename from gym/f110_gym/test/test_observation.py rename to tests/test_observation.py index 9a7c02cf..d88b345b 100644 --- a/gym/f110_gym/test/test_observation.py +++ b/tests/test_observation.py @@ -1,22 +1,16 @@ -import pathlib import unittest -from argparse import Namespace +import gymnasium as gym import numpy as np -import yaml -from gymnasium.spaces import Box - from f110_gym.envs import F110Env -from f110_gym.envs.observation import FeaturesObservation, observation_factory -import gymnasium as gym - +from f110_gym.envs.observation import observation_factory from f110_gym.envs.utils import deep_update +from gymnasium.spaces import Box class TestObservationInterface(unittest.TestCase): @staticmethod def _make_env(config={}) -> F110Env: - import f110_gym conf = { "map": "Example", @@ -129,7 +123,7 @@ def test_unexisting_obs_space(self): """ env = self._make_env() with self.assertRaises(ValueError): - obs = observation_factory(env, vehicle_id=0, type="unexisting_obs_type") + observation_factory(env, vehicle_id=0, type="unexisting_obs_type") def test_kinematic_obs_space(self): """ @@ -227,14 +221,12 @@ def test_consistency_observe_space(self): ) def test_gymnasium_api(self): - import f110_gym from gymnasium.utils.env_checker import check_env - import gymnasium as gym obs_type_ids = ["kinematic_state", "dynamic_state", "original"] for obs_type_id in obs_type_ids: env = self._make_env(config={"observation_config": {"type": obs_type_id}}) check_env( - env.unwrapped, f"Observation {obs_type_id} breaks the gymnasium API" + env.unwrapped, f"Observation {obs_type_id} breaks the gymnasium API", skip_render_check=True ) diff --git a/tests/test_scan_sim.py b/tests/test_scan_sim.py new file mode 100644 index 00000000..6883385b --- /dev/null +++ b/tests/test_scan_sim.py @@ -0,0 +1,115 @@ +# MIT License + +# Copyright (c) 2020 Joseph Auckley, Matthew O'Kelly, Aman Sinha, Hongrui Zheng + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +""" +Prototype of Utility functions and classes for simulating 2D LIDAR scans +Author: Hongrui Zheng +""" + +import os +import unittest + +import numpy as np +from f110_gym.envs.laser_models import ScanSimulator2D + + +class ScanTests(unittest.TestCase): + def setUp(self): + # test params + self.num_beams = 1080 + self.fov = 4.7 + + self.num_test = 10 + self.test_poses = np.zeros((self.num_test, 3)) + self.test_poses[:, 2] = np.linspace(-1.0, 1.0, num=self.num_test) + + # legacy gym data + wdir = os.path.dirname(os.path.abspath(__file__)) + sample_scan = np.load(f"{wdir}/legacy_scan.npz") + self.berlin_scan = sample_scan["berlin"] + self.skirk_scan = sample_scan["skirk"] + + def test_map_berlin(self, debug=False): + scan_rng = np.random.default_rng(seed=12345) + scan_sim = ScanSimulator2D(self.num_beams, self.fov) + new_berlin = np.empty((self.num_test, self.num_beams)) + scan_sim.set_map(map_name="Berlin") + # scan gen loop + for i in range(self.num_test): + test_pose = self.test_poses[i] + new_berlin[i, :] = scan_sim.scan(pose=test_pose, rng=scan_rng) + diff = self.berlin_scan - new_berlin + mse = np.mean(diff**2) + # print('Levine distance test, norm: ' + str(norm)) + + if debug: + # plotting + import matplotlib.pyplot as plt + + theta = np.linspace(-self.fov / 2.0, self.fov / 2.0, num=self.num_beams) + plt.polar(theta, new_berlin[1, :], ".", lw=0) + plt.polar(theta, self.berlin_scan[1, :], ".", lw=0) + plt.show() + + self.assertLess(mse, 2.0) + + def test_map_skirk(self, debug=False): + scan_rng = np.random.default_rng(seed=12345) + scan_sim = ScanSimulator2D(self.num_beams, self.fov) + new_skirk = np.empty((self.num_test, self.num_beams)) + scan_sim.set_map(map_name="Skirk") + print("map set") + # scan gen loop + for i in range(self.num_test): + test_pose = self.test_poses[i] + new_skirk[i, :] = scan_sim.scan(pose=test_pose, rng=scan_rng) + diff = self.skirk_scan - new_skirk + mse = np.mean(diff**2) + print("skirk distance test, mse: " + str(mse)) + + if debug: + # plotting + import matplotlib.pyplot as plt + + theta = np.linspace(-self.fov / 2.0, self.fov / 2.0, num=self.num_beams) + plt.polar(theta, new_skirk[1, :], ".", lw=0) + plt.polar(theta, self.skirk_scan[1, :], ".", lw=0) + plt.show() + + self.assertLess(mse, 2.0) + + def test_fps(self): + # scan fps should be greater than 500 + scan_rng = np.random.default_rng(seed=12345) + scan_sim = ScanSimulator2D(self.num_beams, self.fov) + scan_sim.set_map(map_name="Skirk") + + import time + + start = time.time() + for i in range(10000): + x_test = i / 10000 + scan_sim.scan(pose=np.array([x_test, 0.0, 0.0]), rng=scan_rng) + end = time.time() + fps = 10000 / (end - start) + + self.assertGreater(fps, 500.0) diff --git a/gym/f110_gym/test/test_track.py b/tests/test_track.py similarity index 95% rename from gym/f110_gym/test/test_track.py rename to tests/test_track.py index 06c66d58..447d08da 100644 --- a/gym/f110_gym/test/test_track.py +++ b/tests/test_track.py @@ -3,8 +3,7 @@ import unittest import numpy as np - -from f110_gym.envs.track import Track, find_track_dir, Raceline +from f110_gym.envs.track import Raceline, Track, find_track_dir class TestTrack(unittest.TestCase): @@ -55,7 +54,7 @@ def test_map_dir_structure(self): - [Trackname_raceline.csv] # raceline (optional) - [Trackname_centerline.csv] # centerline (optional) """ - mapdir = pathlib.Path(__file__).parent.parent / "maps" + mapdir = pathlib.Path(__file__).parent.parent / "gym" / "f110_gym" / "maps" for trackdir in mapdir.iterdir(): if trackdir.is_file(): continue @@ -90,12 +89,12 @@ def test_map_dir_structure(self): if file_raceline.exists(): # try to load raceline files # it will raise an assertion error if the file format are not valid - raceline = Raceline.from_raceline_file(file_raceline) + Raceline.from_raceline_file(file_raceline) if file_centerline.exists(): # try to load raceline files # it will raise an assertion error if the file format are not valid - centerline = Raceline.from_centerline_file(file_centerline) + Raceline.from_centerline_file(file_centerline) def test_download_racetrack(self): import shutil diff --git a/gym/f110_gym/test/test_utils.py b/tests/test_utils.py similarity index 97% rename from gym/f110_gym/test/test_utils.py rename to tests/test_utils.py index 2ed2f586..c2fefbe5 100644 --- a/gym/f110_gym/test/test_utils.py +++ b/tests/test_utils.py @@ -7,7 +7,6 @@ def test_deep_update(self): Test that the deep_update function works as expected with nested dictionaries, by comparing two environments with different mu values. """ - import f110_gym import gymnasium as gym default_env = gym.make("f110_gym:f110-v0")